{
"cells": [
{
"cell_type": "markdown",
"id": "7a39747a",
"metadata": {},
"source": [
"# Inflation Rate Laffer Curves"
]
},
{
"cell_type": "markdown",
"id": "fc8ef6d2",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"We study stationary and dynamic *Laffer curves* in the inflation tax rate in a non-linear version of the model studied in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html).\n",
"\n",
"We use the log-linear version of the demand function for money that [[Cagan, 1956](https://intro.quantecon.org/zreferences.html#id112)]\n",
"used in his classic paper in place of the linear demand function used in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html).\n",
"\n",
"That change requires that we modify parts of our analysis.\n",
"\n",
"In particular, our dynamic system is no longer linear in state variables.\n",
"\n",
"Nevertheless, the economic logic underlying an analysis based on what we called ‘‘method 2’’ remains unchanged.\n",
"\n",
"We shall discover qualitatively similar outcomes to those that we studied in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html).\n",
"\n",
"That lecture presented a linear version of the model in this lecture.\n",
"\n",
"As in that lecture, we discussed these topics:\n",
"\n",
"- an **inflation tax** that a government gathers by printing paper or electronic money \n",
"- a dynamic **Laffer curve** in the inflation tax rate that has two stationary equilibria \n",
"- perverse dynamics under rational expectations in which the system converges to the higher stationary inflation tax rate \n",
"- a peculiar comparative stationary-state analysis connected with that stationary inflation rate that asserts that inflation can be *reduced* by running *higher* government deficits \n",
"\n",
"\n",
"These outcomes will set the stage for the analysis of [Laffer Curves with Adaptive Expectations](https://intro.quantecon.org/laffer_adaptive.html) that studies a version of the present model that uses a version of “adaptive expectations” instead of rational expectations.\n",
"\n",
"That lecture will show that\n",
"\n",
"- replacing rational expectations with adaptive expectations leaves the two stationary inflation rates unchanged, but that $ \\ldots $ \n",
"- it reverses the perverse dynamics by making the *lower* stationary inflation rate the one to which the system typically converges \n",
"- a more plausible comparative dynamic outcome emerges in which now inflation can be *reduced* by running *lower* government deficits "
]
},
{
"cell_type": "markdown",
"id": "a6ccdf78",
"metadata": {},
"source": [
"## The Model\n",
"\n",
"Let\n",
"\n",
"- $ m_t $ be the log of the money supply at the beginning of time $ t $ \n",
"- $ p_t $ be the log of the price level at time $ t $ \n",
"\n",
"\n",
"The demand function for money is\n",
"\n",
"\n",
"\n",
"$$\n",
"m_{t+1} - p_t = -\\alpha (p_{t+1} - p_t) \\tag{30.1}\n",
"$$\n",
"\n",
"where $ \\alpha \\geq 0 $.\n",
"\n",
"The law of motion of the money supply is\n",
"\n",
"\n",
"\n",
"$$\n",
"\\exp(m_{t+1}) - \\exp(m_t) = g \\exp(p_t) \\tag{30.2}\n",
"$$\n",
"\n",
"where $ g $ is the part of government expenditures financed by printing money."
]
},
{
"cell_type": "markdown",
"id": "9f8a4b92",
"metadata": {},
"source": [
"## \n",
"\n",
"Please notice that while equation [(30.1)](#equation-eq-mdemand) is linear in logs of the money supply and price level, equation [(30.2)](#equation-eq-msupply) is linear in levels. This will require adapting the equilibrium computation methods that we deployed in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html)."
]
},
{
"cell_type": "markdown",
"id": "af3ae67e",
"metadata": {},
"source": [
"## Limiting Values of Inflation Rate\n",
"\n",
"We can compute the two prospective limiting values for $ \\overline \\pi $ by studying the steady-state Laffer curve.\n",
"\n",
"Thus, in a *steady state*\n",
"\n",
"$$\n",
"m_{t+1} - m_t = p_{t+1} - p_t = x \\quad \\forall t ,\n",
"$$\n",
"\n",
"where $ x > 0 $ is a common rate of growth of logarithms of the money supply and price level.\n",
"\n",
"A few lines of algebra yields the following equation that $ x $ satisfies\n",
"\n",
"\n",
"\n",
"$$\n",
"\\exp(-\\alpha x) - \\exp(-(1 + \\alpha) x) = g \\tag{30.3}\n",
"$$\n",
"\n",
"where we require that\n",
"\n",
"\n",
"\n",
"$$\n",
"g \\leq \\max_{x \\geq 0} \\{\\exp(-\\alpha x) - \\exp(-(1 + \\alpha) x) \\}, \\tag{30.4}\n",
"$$\n",
"\n",
"so that it is feasible to finance $ g $ by printing money.\n",
"\n",
"The left side of [(30.3)](#equation-eq-steadypi) is steady state revenue raised by printing money.\n",
"\n",
"The right side of [(30.3)](#equation-eq-steadypi) is the quantity of time $ t $ goods that the government raises by printing money.\n",
"\n",
"Soon we’ll plot the left and right sides of equation [(30.3)](#equation-eq-steadypi).\n",
"\n",
"But first we’ll write code that computes a steady-state\n",
"$ \\overline \\pi $.\n",
"\n",
"Let’s start by importing some libraries"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9579d8a8",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from collections import namedtuple\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib.ticker import MaxNLocator\n",
"from scipy.optimize import fsolve "
]
},
{
"cell_type": "markdown",
"id": "7bf3d41e",
"metadata": {},
"source": [
"Let’s create a `namedtuple` to store the parameters of the model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9796f47b",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"CaganLaffer = namedtuple('CaganLaffer', \n",
" [\"m0\", # log of the money supply at t=0\n",
" \"α\", # sensitivity of money demand\n",
" \"λ\",\n",
" \"g\" ])\n",
"\n",
"# Create a Cagan Laffer model\n",
"def create_model(α=0.5, m0=np.log(100), g=0.35):\n",
" return CaganLaffer(α=α, m0=m0, λ=α/(1+α), g=g)\n",
"\n",
"model = create_model()"
]
},
{
"cell_type": "markdown",
"id": "5127eb85",
"metadata": {},
"source": [
"Now we write code that computes steady-state $ \\overline \\pi $s."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb18acf8",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"# Define formula for π_bar\n",
"def solve_π(x, α, g):\n",
" return np.exp(-α * x) - np.exp(-(1 + α) * x) - g\n",
"\n",
"def solve_π_bar(model, x0):\n",
" π_bar = fsolve(solve_π, x0=x0, xtol=1e-10, args=(model.α, model.g))[0]\n",
" return π_bar\n",
"\n",
"# Solve for the two steady state of π\n",
"π_l = solve_π_bar(model, x0=0.6)\n",
"π_u = solve_π_bar(model, x0=3.0)\n",
"print(f'The two steady state of π are: {π_l, π_u}')"
]
},
{
"cell_type": "markdown",
"id": "0ea71660",
"metadata": {},
"source": [
"We find two steady state $ \\overline \\pi $ values."
]
},
{
"cell_type": "markdown",
"id": "d7842ae7",
"metadata": {},
"source": [
"## Steady State Laffer curve\n",
"\n",
"The following figure plots the steady state Laffer curve together with the two stationary inflation rates."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d01da2ba",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"def compute_seign(x, α):\n",
" return np.exp(-α * x) - np.exp(-(1 + α) * x) \n",
"\n",
"def plot_laffer(model, πs):\n",
" α, g = model.α, model.g\n",
" \n",
" # Generate π values\n",
" x_values = np.linspace(0, 5, 1000)\n",
"\n",
" # Compute corresponding seigniorage values for the function\n",
" y_values = compute_seign(x_values, α)\n",
"\n",
" # Plot the function\n",
" plt.plot(x_values, y_values, \n",
" label=f'Laffer curve')\n",
" for π, label in zip(πs, ['$\\pi_l$', '$\\pi_u$']):\n",
" plt.text(π, plt.gca().get_ylim()[0]*2, \n",
" label, horizontalalignment='center',\n",
" color='brown', size=10)\n",
" plt.axvline(π, color='brown', linestyle='--')\n",
" plt.axhline(g, color='red', linewidth=0.5, \n",
" linestyle='--', label='g')\n",
" plt.xlabel('$\\pi$')\n",
" plt.ylabel('seigniorage')\n",
" plt.legend()\n",
" plt.show()\n",
"\n",
"# Steady state Laffer curve\n",
"plot_laffer(model, (π_l, π_u))"
]
},
{
"cell_type": "markdown",
"id": "4e15ac4d",
"metadata": {},
"source": [
"## Initial Price Levels\n",
"\n",
"Now that we have our hands on the two possible steady states, we can compute two functions $ \\underline p(m_0) $ and\n",
"$ \\overline p(m_0) $, which as initial conditions for $ p_t $ at time $ t $, imply that $ \\pi_t = \\overline \\pi $ for all $ t \\geq 0 $.\n",
"\n",
"The function $ \\underline p(m_0) $ will be associated with $ \\pi_l $ the lower steady-state inflation rate.\n",
"\n",
"The function $ \\overline p(m_0) $ will be associated with $ \\pi_u $ the lower steady-state inflation rate."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a377279",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"def solve_p0(p0, m0, α, g, π):\n",
" return np.log(np.exp(m0) + g * np.exp(p0)) + α * π - p0\n",
"\n",
"def solve_p0_bar(model, x0, π_bar):\n",
" p0_bar = fsolve(solve_p0, x0=x0, xtol=1e-20, args=(model.m0, \n",
" model.α, \n",
" model.g, \n",
" π_bar))[0]\n",
" return p0_bar\n",
"\n",
"# Compute two initial price levels associated with π_l and π_u\n",
"p0_l = solve_p0_bar(model, \n",
" x0=np.log(220), \n",
" π_bar=π_l)\n",
"p0_u = solve_p0_bar(model, \n",
" x0=np.log(220), \n",
" π_bar=π_u)\n",
"print(f'Associated initial p_0s are: {p0_l, p0_u}')"
]
},
{
"cell_type": "markdown",
"id": "04d817bb",
"metadata": {},
"source": [
"### Verification\n",
"\n",
"To start, let’s write some code to verify that if the initial log price level $ p_0 $ takes one\n",
"of the two values we just calculated, the inflation rate $ \\pi_t $ will be constant for all $ t \\geq 0 $.\n",
"\n",
"The following code verifies this."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6f61e7f",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"# Implement pseudo-code above\n",
"def simulate_seq(p0, model, num_steps):\n",
" λ, g = model.λ, model.g\n",
" π_seq, μ_seq, m_seq, p_seq = [], [], [model.m0], [p0]\n",
"\n",
" for t in range(num_steps):\n",
" \n",
" m_seq.append(np.log(np.exp(m_seq[t]) + g * np.exp(p_seq[t])))\n",
" p_seq.append(1/λ * p_seq[t] + (1 - 1/λ) * m_seq[t+1])\n",
"\n",
" μ_seq.append(m_seq[t+1]-m_seq[t])\n",
" π_seq.append(p_seq[t+1]-p_seq[t])\n",
"\n",
" return π_seq, μ_seq, m_seq, p_seq"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f6721c9",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"π_seq, μ_seq, m_seq, p_seq = simulate_seq(p0_l, model, 150)\n",
"\n",
"# Check π and μ at steady state\n",
"print('π_bar == μ_bar:', π_seq[-1] == μ_seq[-1])\n",
"\n",
"# Check steady state m_{t+1} - m_t and p_{t+1} - p_t \n",
"print('m_{t+1} - m_t:', m_seq[-1] - m_seq[-2])\n",
"print('p_{t+1} - p_t:', p_seq[-1] - p_seq[-2])\n",
"\n",
"# Check if exp(-αx) - exp(-(1 + α)x) = g\n",
"eq_g = lambda x: np.exp(-model.α * x) - np.exp(-(1 + model.α) * x)\n",
"\n",
"print('eq_g == g:', np.isclose(eq_g(m_seq[-1] - m_seq[-2]), model.g))"
]
},
{
"cell_type": "markdown",
"id": "2b022a2d",
"metadata": {},
"source": [
"## Computing an Equilibrium Sequence\n",
"\n",
"We’ll deploy a method similar to *Method 2* used in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html).\n",
"\n",
"We’ll take the time $ t $ state vector to be the pair $ (m_t, p_t) $.\n",
"\n",
"We’ll treat $ m_t $ as a `natural state variable` and $ p_t $ as a `jump` variable.\n",
"\n",
"Let\n",
"\n",
"$$\n",
"\\lambda \\equiv \\frac{\\alpha}{1+ \\alpha}\n",
"$$\n",
"\n",
"Let’s rewrite equation [(30.1)](#equation-eq-mdemand) as\n",
"\n",
"\n",
"\n",
"$$\n",
"p_t = (1-\\lambda) m_{t+1} + \\lambda p_{t+1} \\tag{30.5}\n",
"$$\n",
"\n",
"We’ll summarize our algorithm with the following pseudo-code.\n",
"\n",
"**Pseudo-code**\n",
"\n",
"The heart of the pseudo-code iterates on the following mapping from state vector $ (m_t, p_t) $ at time $ t $\n",
"to state vector $ (m_{t+1}, p_{t+1}) $ at time $ t+1 $.\n",
"\n",
"- starting from a given pair $ (m_t, p_t) $ at time $ t \\geq 0 $ \n",
" - solve [(30.2)](#equation-eq-msupply) for $ m_{t+1} $ \n",
" - solve [(30.5)](#equation-eq-mdemand2) for $ p_{t+1} = \\lambda^{-1} p_t + (1 - \\lambda^{-1}) m_{t+1} $ \n",
" - compute the inflation rate $ \\pi_t = p_{t+1} - p_t $ and growth of money supply $ \\mu_t = m_{t+1} - m_t $ \n",
"\n",
"\n",
"Next, compute the two functions $ \\underline p(m_0) $ and $ \\overline p(m_0) $ described above\n",
"\n",
"Now initiate the algorithm as follows.\n",
"\n",
"- set $ m_0 >0 $ \n",
"- set a value of $ p_0 \\in [\\underline p(m_0), \\overline p(m_0)] $ and form the pair $ (m_0, p_0) $ at time $ t =0 $ \n",
"\n",
"\n",
"Starting from $ (m_0, p_0) $ iterate on $ t $ to convergence of $ \\pi_t \\rightarrow \\overline \\pi $ and $ \\mu_t \\rightarrow \\overline \\mu $\n",
"\n",
"It will turn out that\n",
"\n",
"- if they exist, limiting values $ \\overline \\pi $ and $ \\overline \\mu $ will be equal \n",
"- if limiting values exist, there are two possible limiting values, one high, one low \n",
"- for almost all initial log price levels $ p_0 $, the limiting $ \\overline \\pi = \\overline \\mu $ is\n",
" the higher value \n",
"- for each of the two possible limiting values $ \\overline \\pi $ ,there is a unique initial log price level $ p_0 $ that implies that $ \\pi_t = \\mu_t = \\overline \\mu $ for all $ t \\geq 0 $ \n",
" - this unique initial log price level solves $ \\log(\\exp(m_0) + g \\exp(p_0)) - p_0 = - \\alpha \\overline \\pi $ \n",
" - the preceding equation for $ p_0 $ comes from $ m_1 - p_0 = - \\alpha \\overline \\pi $ "
]
},
{
"cell_type": "markdown",
"id": "c65e4679",
"metadata": {},
"source": [
"## Slippery Side of Laffer Curve Dynamics\n",
"\n",
"We are now equipped to compute time series starting from different $ p_0 $ settings, like those in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b3cf083",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"def draw_iterations(p0s, model, line_params, p0_bars, num_steps):\n",
"\n",
" fig, axes = plt.subplots(4, 1, figsize=(8, 10), sharex=True)\n",
" \n",
" # Pre-compute time steps\n",
" time_steps = np.arange(num_steps) \n",
" \n",
" # Plot the first two y-axes in log scale\n",
" for ax in axes[:2]:\n",
" ax.set_yscale('log')\n",
"\n",
" # Iterate over p_0s and calculate a series of y_t\n",
" for p0 in p0s:\n",
" π_seq, μ_seq, m_seq, p_seq = simulate_seq(p0, model, num_steps)\n",
"\n",
" # Plot m_t\n",
" axes[0].plot(time_steps, m_seq[1:], **line_params)\n",
"\n",
" # Plot p_t\n",
" axes[1].plot(time_steps, p_seq[1:], **line_params)\n",
" \n",
" # Plot π_t\n",
" axes[2].plot(time_steps, π_seq, **line_params)\n",
" \n",
" # Plot μ_t\n",
" axes[3].plot(time_steps, μ_seq, **line_params)\n",
" \n",
" # Draw labels\n",
" axes[0].set_ylabel('$m_t$')\n",
" axes[1].set_ylabel('$p_t$')\n",
" axes[2].set_ylabel('$\\pi_t$')\n",
" axes[3].set_ylabel('$\\mu_t$')\n",
" axes[3].set_xlabel('timestep')\n",
" \n",
" for p_0, label in [(p0_bars[0], '$p_0=p_l$'), (p0_bars[1], '$p_0=p_u$')]:\n",
" y = simulate_seq(p_0, model, 1)[0]\n",
" for ax in axes[2:]:\n",
" ax.axhline(y=y[0], color='grey', linestyle='--', lw=1.5, alpha=0.6)\n",
" ax.text(num_steps * 1.02, y[0], label, verticalalignment='center', \n",
" color='grey', size=10)\n",
" \n",
" # Enforce integar axis label\n",
" axes[3].xaxis.set_major_locator(MaxNLocator(integer=True))\n",
"\n",
" plt.tight_layout()\n",
" plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "25b9f99f",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"# Generate a sequence from p0_l to p0_u\n",
"p0s = np.arange(p0_l, p0_u, 0.1) \n",
"\n",
"line_params = {'lw': 1.5, \n",
" 'marker': 'o',\n",
" 'markersize': 3}\n",
"\n",
"p0_bars = (p0_l, p0_u)\n",
" \n",
"draw_iterations(p0s, model, line_params, p0_bars, num_steps=20)"
]
},
{
"cell_type": "markdown",
"id": "4b1fddf9",
"metadata": {},
"source": [
"Staring at the paths of price levels in Fig. 30.2 reveals that almost all paths converge to the *higher* inflation tax rate displayed in the stationary state Laffer curve. displayed in figure Fig. 30.1.\n",
"\n",
"Thus, we have reconfirmed what we have called the “perverse” dynamics under rational expectations in which the system converges to the higher of two possible stationary inflation tax rates.\n",
"\n",
"Those dynamics are “perverse” not only in the sense that they imply that the monetary and fiscal authorities that have chosen to finance government expenditures eventually impose a higher inflation tax than required to finance government expenditures, but because of the following “counterintuitive” situation that we can deduce by staring at the stationary state Laffer curve displayed in figure Fig. 30.1:\n",
"\n",
"- the figure indicates that inflation can be *reduced* by running *higher* government deficits, i.e., by raising more resources through printing money. \n",
"\n",
"\n",
">**Note**\n",
">\n",
">The same qualitative outcomes prevail in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html) that studies a linear version of the model in this lecture.\n",
"\n",
"We discovered that\n",
"\n",
"- all but one of the equilibrium paths converge to limits in which the higher of two possible stationary inflation tax prevails \n",
"- there is a unique equilibrium path associated with “plausible” statements about how reductions in government deficits affect a stationary inflation rate \n",
"\n",
"\n",
"As in [Money Financed Government Deficits and Price Levels](https://intro.quantecon.org/money_inflation.html),\n",
"on grounds of plausibility, we again recommend selecting the unique equilibrium that converges to the lower stationary inflation tax rate.\n",
"\n",
"As we shall see, we accepting this recommendation is a key ingredient of outcomes of the “unpleasant arithmetic” that we describe in [Some Unpleasant Monetarist Arithmetic](https://intro.quantecon.org/unpleasant.html).\n",
"\n",
"In [Laffer Curves with Adaptive Expectations](https://intro.quantecon.org/laffer_adaptive.html), we shall explore how [[Bruno and Fischer, 1990](https://intro.quantecon.org/zreferences.html#id296)] and others justified our equilibrium selection in other ways."
]
}
],
"metadata": {
"date": 1727242854.9625943,
"filename": "money_inflation_nonlinear.md",
"kernelspec": {
"display_name": "Python",
"language": "python3",
"name": "python3"
},
"title": "Inflation Rate Laffer Curves"
},
"nbformat": 4,
"nbformat_minor": 5
}