65. Stability in Linear Rational Expectations Models#
Contents
In addition to what’s in Anaconda, this lecture deploys the following libraries:
!pip install quantecon
Show code cell output
Requirement already satisfied: quantecon in /opt/conda/envs/quantecon/lib/python3.12/site-packages (0.8.0)
Requirement already satisfied: numba>=0.49.0 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from quantecon) (0.60.0)
Requirement already satisfied: numpy>=1.17.0 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.26.4)
Requirement already satisfied: requests in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from quantecon) (2.32.3)
Requirement already satisfied: scipy>=1.5.0 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.13.1)
Requirement already satisfied: sympy in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.13.1)
Requirement already satisfied: llvmlite<0.44,>=0.43.0dev0 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from numba>=0.49.0->quantecon) (0.43.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2024.8.30)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/envs/quantecon/lib/python3.12/site-packages (from sympy->quantecon) (1.3.0)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable.It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
import matplotlib.pyplot as plt
import numpy as np
import quantecon as qe
from sympy import init_printing, symbols, Matrix
init_printing()
65.1. Overview#
This lecture studies stability in the context of an elementary rational expectations model.
We study a rational expectations version of Philip Cagan’s model [Cagan, 1956] linking the price level to the money supply.
Cagan did not use a rational expectations version of his model, but Sargent [Sargent, 1977] did.
We study a rational expectations version of this model because it is intrinsically interesting and because it has a mathematical structure that appears in virtually all linear rational expectations model, namely, that a key endogenous variable equals a mathematical expectation of a geometric sum of future values of another variable.
The model determines the price level or rate of inflation as a function of the money supply or the rate of change in the money supply.
In this lecture, we’ll encounter:
a convenient formula for the expectation of geometric sum of future values of a variable
a way of solving an expectational difference equation by mapping it into a vector first-order difference equation and appropriately manipulating an eigen decomposition of the transition matrix in order to impose stability
a way to use a Big
, little argument to allow apparent feedback from endogenous to exogenous variables within a rational expectations equilibriuma use of eigenvector decompositions of matrices that allowed Blanchard and Khan (1981) [Blanchard and Kahn, 1980] and Whiteman (1983) [Whiteman, 1983] to solve a class of linear rational expectations models
how to use SymPy to get analytical formulas for some key objects comprising a rational expectations equilibrium
Matrix decompositions employed here are described in more depth in this lecture Lagrangian formulations.
We formulate a version of Cagan’s model under rational expectations as an expectational difference equation whose solution is a rational expectations equilibrium.
We’ll start this lecture with a quick review of deterministic (i.e., non-random) first-order and second-order linear difference equations.
65.2. Linear Difference Equations#
We’ll use the backward shift or lag operator
The lag operator
We’ll deploy
Further, the inverse
We’ll often use the equality
The algebra of lag and forward shift operators can simplify representing and solving linear difference equations.
65.2.1. First Order#
We want to solve a linear first-order scalar difference equation.
Let
Let
Then
has solutions
or
for any real number
You can verify this fact by applying
To pin down
Now let
Rewrite equation (65.1) as
or
A solution is
for any
To verify that this is a solution, check the consequences of operating
on both sides of equation (65.5) by
For any bounded
Solution (65.5) exists when
When
The distributed lead in
65.2.2. Second Order#
Now consider the second order difference equation
where
We seek a bounded sequence
or
Thus, we obtained equation (65.7) by
solving a stable root (in this case
Equation (65.7) has a form that we shall encounter often.
is called the feedback part is called the feedforward part
65.3. Illustration: Cagan’s Model#
Now let’s use linear difference equations to represent and solve Sargent’s [Sargent, 1977] rational expectations version of Cagan’s model [Cagan, 1956] that connects the price level to the public’s anticipations of future money supplies.
Cagan did not use a rational expectations version of his model, but Sargent [Sargent, 1977]
Let
be the log of the demand for money be the log of the supply of money be the log of the price level
It follows that
The logarithm of the demand for real money balances
Equate the demand for log money
where
(We note that the characteristic polynomial if
Solving the first order difference equation (65.8) forward gives
which is the unique stable solution of difference equation (65.8) among a class of more general solutions
that is indexed by the real number
Because we want to focus on stable solutions, we set
Equation (65.10) attributes perfect foresight about the money supply sequence to the holders of real balances.
We begin by assuming that the log of the money supply is exogenous in the sense that it is an autonomous process that does not feed back on the log of the price level.
In particular, we assume that the log of the money supply is described by the linear state space system
where
Variables appearing in the vector
We’ll start with an example in which
An example of such an
where the zeros of the characteristic polynomial
(Please see this QuantEcon lecture for more about characteristic polynomials and their role in solving linear difference equations.)
We seek a stable or non-explosive solution of the difference equation (65.8) that obeys the system comprised of (65.8)-(65.11).
By stable or non-explosive, we mean that neither
This requires that we shut down the term
The solution we are after is
where
65.4. Some Python Code#
We’ll construct examples that illustrate (65.11).
Our first example takes as the law of motion for the log money supply the second order difference equation
that is parameterized by
To capture this parameterization with system (65.9) we set
Here is Python code
λ = .9
α = 0
ρ1 = .9
ρ2 = .05
A = np.array([[1, 0, 0],
[α, ρ1, ρ2],
[0, 1, 0]])
G = np.array([[0, 1, 0]])
The matrix
It is associated with the
We can verify that the two eigenvalues of
eigvals = np.linalg.eigvals(A)
print(eigvals)
[-0.05249378 0.95249378 1. ]
(abs(eigvals) <= 1).all()
True
Now let’s compute
# compute the solution, i.e. forumula (3)
F = (1 - λ) * G @ np.linalg.inv(np.eye(A.shape[0]) - λ * A)
print("F= ",F)
F= [[0. 0.66889632 0.03010033]]
Now let’s simulate paths of
# set the initial state
x0 = np.array([1, 1, 0])
T = 100 # length of simulation
m_seq = np.empty(T+1)
p_seq = np.empty(T+1)
[m_seq[0]] = G @ x0
[p_seq[0]] = F @ x0
# simulate for T periods
x_old = x0
for t in range(T):
x = A @ x_old
[m_seq[t+1]] = G @ x
[p_seq[t+1]] = F @ x
x_old = x
plt.figure()
plt.plot(range(T+1), m_seq, label=r'$m_t$')
plt.plot(range(T+1), p_seq, label=r'$p_t$')
plt.xlabel('t')
plt.title(rf'λ={λ}, α={α}, $ρ_1$={ρ1}, $ρ_2$={ρ2}')
plt.legend()
plt.show()

In the above graph, why is the log of the price level always less than the log of the money supply?
Because
according to equation (65.9),
is a geometric weighted average of current and future values of , andit happens that in this example future
’s are always less than the current
65.5. Alternative Code#
We could also have run the simulation using the quantecon LinearStateSpace code.
The following code block performs the calculation with that code.
# construct a LinearStateSpace instance
# stack G and F
G_ext = np.vstack([G, F])
C = np.zeros((A.shape[0], 1))
ss = qe.LinearStateSpace(A, C, G_ext, mu_0=x0)
T = 100
# simulate using LinearStateSpace
x, y = ss.simulate(ts_length=T)
# plot
plt.figure()
plt.plot(range(T), y[0,:], label='$m_t$')
plt.plot(range(T), y[1,:], label='$p_t$')
plt.xlabel('t')
plt.title(f'λ={λ}, α={α}, $ρ_1$={ρ1}, $ρ_2$={ρ2}')
plt.legend()
plt.show()

65.5.1. Special Case#
To simplify our presentation in ways that will let focus on an important
idea, in the above second-order difference equation (65.14) that governs
and the state
Consequently, we can set
so that the log the log price level satisfies
Please keep these formulas in mind as we investigate an alternative
route to and interpretation of our formula for
65.6. Another Perspective#
Above, we imposed stability or non-explosiveness on the solution of the key difference equation (65.8) in Cagan’s model by solving the unstable root of the characteristic polynomial forward.
To shed light on the mechanics involved in imposing stability on a solution of a potentially unstable system of linear difference equations and to prepare the way for generalizations of our model in which the money supply is allowed to feed back on the price level itself, we stack equations (65.8) and (65.15) to form the system
or
where
Transition matrix
Because an eigenvalue of
To substantiate this claim, we can use the eigenvector matrix
decomposition of
Here
Note that
so that
For almost all initial vectors
To explore this outcome in more detail, we can use the following transformation
that allows us to represent the dynamics in a way that isolates the source of the propensity of paths to diverge:
Staring at this equation indicates that unless
the path of
Equation (65.19) also leads us to conclude that there is a unique setting
for the initial vector
The required setting of
But note that since
Sometimes this situation is described by saying that while
Thus, in a nutshell the unique value of the vector
The component
where
where
Solving this equation for
This is the unique stabilizing value of
65.6.1. Refining the Formula#
We can get an even more convenient formula for
To get this formula, first note that because
which implies that
Therefore,
So we can write
It can be verified that this formula replicates itself over time in the sense that
To implement formula (65.23), we want to compute
By hand it can be verified that the eigenvector associated with the
stable eigenvalue
Notice that if we set
a formula that is equivalent with
where
65.6.2. Remarks about Feedback#
We have expressed (65.16) in what superficially appears to be a form in
which
A tell-tale sign that we should look beyond its superficial “feedback”
form is that
it has one eigenvalue
that is less than one in modulus that does not imperil stability, butit has a second eigenvalue
that exceeds one in modulus and that makes an unstable matrix
We’ll keep these observations in mind as we turn now to a case in which the log money supply actually does feed back on the log of the price level.
65.7. Log money Supply Feeds Back on Log Price Level#
An arrangement of eigenvalues that split around unity, with one being below unity and another being greater than unity, sometimes prevails when there is feedback from the log price level to the log money supply.
Let the feedback rule be
where
Warning: If things are to fit together as we
wish to deliver a stable system for some initial value
The forward-looking equation (65.8) continues to describe equality between the demand and supply of money.
We assume that equations (65.8) and (65.24) govern
The transition matrix
now becomes
We take
Our approach is identical with the one followed above and is based on an eigenvalue decomposition in which, cross our fingers, one eigenvalue exceeds unity and the other is less than unity in absolute value.
When
We’ll just calculate them and apply the same algorithm that we used above.
That algorithm remains valid so long as the eigenvalues split around unity as before.
Again we assume that
Let’s write and execute some Python code that will let us explore how outcomes depend on
def construct_H(ρ, λ, δ):
"contruct matrix H given parameters."
H = np.empty((2, 2))
H[0, :] = ρ,δ
H[1, :] = - (1 - λ) / λ, 1 / λ
return H
def H_eigvals(ρ=.9, λ=.5, δ=0):
"compute the eigenvalues of matrix H given parameters."
# construct H matrix
H = construct_H(ρ, λ, δ)
# compute eigenvalues
eigvals = np.linalg.eigvals(H)
return eigvals
H_eigvals()
array([2. , 0.9])
Notice that a negative
# small negative δ
H_eigvals(δ=-0.05)
array([0.8562829, 2.0437171])
# large negative δ
H_eigvals(δ=-1.5)
array([0.10742784, 2.79257216])
A sufficiently small positive
# sufficiently small positive δ
H_eigvals(δ=0.05)
array([0.94750622, 1.95249378])
But a large enough positive
For example,
H_eigvals(δ=0.2)
array([1.12984379, 1.77015621])
We want to study systems in which one eigenvalue exceeds unity in
modulus while the other is less than unity in modulus, so we avoid
values of
That is, we want to avoid too much positive feedback from
def magic_p0(m0, ρ=.9, λ=.5, δ=0):
"""
Use the magic formula (8) to compute the level of p0
that makes the system stable.
"""
H = construct_H(ρ, λ, δ)
eigvals, Q = np.linalg.eig(H)
# find the index of the smaller eigenvalue
ind = 0 if eigvals[0] < eigvals[1] else 1
# verify that the eigenvalue is less than unity
if eigvals[ind] > 1:
print("both eigenvalues exceed unity in modulus")
return None
p0 = Q[1, ind] / Q[0, ind] * m0
return p0
Let’s plot how the solution
m_range = np.arange(0.1, 2., 0.1)
for δ in [-0.05, 0, 0.05]:
plt.plot(m_range, [magic_p0(m0, δ=δ) for m0 in m_range], label=f"δ={δ}")
plt.legend()
plt.xlabel(r"$m_0$")
plt.ylabel(r"$p_0$")
plt.show()

To look at things from a different angle, we can fix the initial value
m0 = 1
δ_range = np.linspace(-0.05, 0.05, 100)
plt.plot(δ_range, [magic_p0(m0, δ=δ) for δ in δ_range])
plt.xlabel(r'$\delta$')
plt.ylabel(r'$p_0$')
plt.title(rf'$m_0$={m0}')
plt.show()

Notice that when
magic_p0(1, δ=0.2)
both eigenvalues exceed unity in modulus
65.8. Big , Little Interpretation#
It is helpful to view our solutions of difference equations having feedback from the price level or inflation to money or the rate of money
creation in terms of the Big
This will help us sort out what is taken as given by the decision makers who use the
difference equation (65.9) to determine
Let’s write the stabilizing solution that we have computed using the eigenvector decomposition of
Then from
or
where
Apply formula (65.13) for
which implies that
so that we can anticipate that
We shall verify this equality in the next block of Python code that implements the following computations.
For the system with
so that there is feedback, we compute the stabilizing solution for in the form where as above.Recalling the system (65.11), (65.12), and (65.13) above, we define
and notice that it is Big and not little here. Then we form and as and and we compute from equation (65.13) above.We compute
and compare it with and check for the anticipated equality.
# set parameters
ρ = .9
λ = .5
δ = .05
# solve for F_star
H = construct_H(ρ, λ, δ)
eigvals, Q = np.linalg.eig(H)
ind = 0 if eigvals[0] < eigvals[1] else 1
F_star = Q[1, ind] / Q[0, ind]
F_star
# solve for F_check
A = np.empty((2, 2))
A[0, :] = ρ, δ
A[1, :] = F_star * A[0, :]
G = np.array([1, 0])
F_check= (1 - λ) * G @ np.linalg.inv(np.eye(2) - λ * A)
F_check
array([0.92755597, 0.02375311])
Compare
F_check[0] + F_check[1] * F_star, F_star
65.9. Fun with SymPy#
This section is a gift for readers who have made it this far.
It puts SymPy to work on our model.
Thus, we use Sympy to compute some key objects comprising the eigenvector decomposition of
We start by generating an
λ, δ, ρ = symbols('λ, δ, ρ')
H1 = Matrix([[ρ,δ], [- (1 - λ) / λ, λ ** -1]])
H1
H1.eigenvals()
H1.eigenvects()
Now let’s compute
H2 = Matrix([[ρ,0], [- (1 - λ) / λ, λ ** -1]])
H2
H2.eigenvals()
H2.eigenvects()
Below we do induce SymPy to do the following fun things for us analytically:
We compute the matrix
whose first column is the eigenvector associated with . and whose second column is the eigenvector associated with .We use SymPy to compute the inverse
of (both in symbols).We use SymPy to compute
(in symbols).Where
denotes the component of , we use SymPy to compute (again in symbols)
# construct Q
vec = []
for i, (eigval, _, eigvec) in enumerate(H2.eigenvects()):
vec.append(eigvec[0])
if eigval == ρ:
ind = i
Q = vec[ind].col_insert(1, vec[1-ind])
Q
Q_inv = Q ** (-1)
Q_inv
Q[1, 0] / Q[0, 0]
- Q_inv[1, 0] / Q_inv[1, 1]