Skip to content

Commit c8953dd

Browse files
committed
update code
1 parent 29b51fa commit c8953dd

File tree

1 file changed

+135
-133
lines changed

1 file changed

+135
-133
lines changed

sandpit/cons_smooth_tom_v2.md

Lines changed: 135 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ jupytext:
44
extension: .md
55
format_name: myst
66
format_version: 0.13
7-
jupytext_version: 1.14.4
7+
jupytext_version: 1.14.5
88
kernelspec:
99
display_name: Python 3 (ipykernel)
1010
language: python
@@ -22,6 +22,7 @@ In this notebook, we'll present some useful models of economic dynamics using o
2222
```{code-cell} ipython3
2323
import numpy as np
2424
import matplotlib.pyplot as plt
25+
from collections import namedtuple
2526
```
2627

2728
+++ {"user_expressions": []}
@@ -70,14 +71,29 @@ Below, we'll describe how to execute these steps using linear algebra -- matrix
7071

7172
We shall eventually evaluate alternative budget feasible consumption paths $\vec c$ using the following **welfare criterion**
7273

73-
$$
74+
```{math}
75+
:label: welfare
76+
7477
W = \sum_{t=0}^T \beta^t (g_1 c_t - \frac{g_2}{2} c_t^2 )
75-
$$
78+
```
7679

7780
where $g_1 > 0, g_2 > 0$.
7881

7982
We shall see that when $\beta R = 1$ (a condition assumed by Milton Friedman and Robert Hall), this criterion assigns higher welfare to **smoother** consumption paths.
8083

84+
Here we use default parameters $R = 1.05$, $g_1 = 1$, $g_2 = 1/2$, and $T = 65$.
85+
86+
We create a namedtuple to store these parameters with default values.
87+
88+
```{code-cell} ipython3
89+
ConsumptionSmoothing = namedtuple("ConsumptionSmoothing", ["R", "g1", "g2", "β_seq", "T"])
90+
91+
def creat_cs_model(R=1.05, g1=1, g2=1/2, T=65):
92+
β = 1/R
93+
β_seq = np.array([β**i for i in range(T+1)])
94+
return ConsumptionSmoothing(R=1.05, g1=1, g2=1/2, β_seq=β_seq, T=65)
95+
```
96+
8197
+++ {"user_expressions": []}
8298

8399
## Difference equations with linear algebra ##
@@ -209,7 +225,29 @@ $$
209225

210226
This is the consumption-smoothing model in a nutshell.
211227

212-
We'll put the model through some paces with Python code below.
228+
We implement this model in `compute_optimal`
229+
230+
```{code-cell} ipython3
231+
def compute_optimal(model, a0, y_seq):
232+
R, T = model.R, model.T
233+
234+
# non-financial wealth
235+
h0 = model.β_seq @ y_seq # since β = 1/R
236+
237+
# c0
238+
c0 = (1 - 1/R) / (1 - (1/R)**(T+1)) * (a0 + h0)
239+
c_seq = c0*np.ones(T+1)
240+
241+
# verify
242+
A = np.diag(-R*np.ones(T), k=-1) + np.eye(T+1)
243+
b = y_seq - c_seq
244+
b[0] = b[0] + a0
245+
246+
a_seq = np.linalg.inv(A) @ b
247+
a_seq = np.concatenate([[a0], a_seq])
248+
249+
return c_seq, a_seq
250+
```
213251

214252
+++ {"user_expressions": []}
215253

@@ -218,13 +256,11 @@ We'll put the model through some paces with Python code below.
218256
As promised, we'll provide step by step instructions on how to use linear algebra, readily implemented
219257
in Python, to solve the consumption smoothing model.
220258

221-
**Note to programmer teammate:**
222-
223259
In the calculations below, please we'll set default values of $R > 1$, e.g., $R = 1.05$, and $\beta = R^{-1}$.
224260

225261
#### Step 1 ####
226262

227-
For some $T+1 \times 1$ $y$ vector, use matrix algebra to compute
263+
For some $(T+1) \times 1$ $y$ vector, use matrix algebra to compute
228264

229265
$$
230266
\sum_{t=0}^T R^{-t} y_t = \begin{bmatrix} 1 & R^{-1} & \cdots & R^{-T} \end{bmatrix}
@@ -275,8 +311,53 @@ $$
275311

276312
Let's verify this with our Python code.
277313

314+
We use an example where the consumer inherits $a_0<0$ (which can be interpreted as a student debt).
315+
316+
The income process $\{y_t\}_{t=0}^{T}$ is constant and positive up to $t=45$ and then becomes zero afterward.
317+
318+
```{code-cell} ipython3
319+
# Financial wealth
320+
a0 = -2 # such as "student debt"
321+
322+
# Income process
323+
y_seq = np.concatenate([np.ones(46), np.zeros(20)])
324+
325+
cs_model = creat_cs_model()
326+
c_seq, a_seq = compute_optimal(cs_model, a0, y_seq)
327+
328+
print('check a_T+1=0:', np.abs(a_seq[-1] - 0) <= 1e-8)
329+
```
330+
331+
```{code-cell} ipython3
332+
# Sequence Length
333+
T = cs_model.T
334+
335+
plt.plot(range(T+1), y_seq, label='income')
336+
plt.plot(range(T+1), c_seq, label='consumption')
337+
plt.plot(range(T+2), a_seq, label='asset')
338+
plt.plot(range(T+2), np.zeros(T+2), '--')
339+
340+
plt.legend()
341+
plt.xlabel(r'$t$')
342+
plt.ylabel(r'$c_t,y_t,a_t$')
343+
plt.show()
344+
```
345+
346+
+++ {"user_expressions": []}
347+
348+
We can evaluate the welfare using the formula {numref}`welfare`
349+
350+
```{code-cell} ipython3
351+
def welfare(model, c_seq):
352+
β_seq, g1, g2 = model.β_seq, model.g1, model.g2
353+
354+
u_seq = g1 * c_seq - g2/2 * c_seq**2
355+
return β_seq @ u_seq
278356
357+
print('Welfare:', welfare(cs_model, c_seq))
358+
```
279359

360+
+++ {"user_expressions": []}
280361

281362
### Feasible consumption variations ###
282363

@@ -331,158 +412,79 @@ Given $R$, we thus have a two parameter class of budget feasible variations $\ve
331412
to compute alternative consumption paths, then evaluate their welfare.
332413

333414
**Note to John:** We can do some fun simple experiments with these variations -- we can use
334-
graphs to show that, when $\beta R =1$ and starting from the smooth path, all nontrivial budget-feasible variations lower welfare according to the criterion above.
335-
336-
We can even use the Python numpy grad command to compute derivatives of welfare with respect to our two parameters.
337-
338-
We are teaching the key idea beneath the **calculus of variations**.
339-
340-
```{code-cell} ipython3
341-
class Consumption_smoothing:
342-
"A class of the Permanent Income model of consumption"
343-
344-
def __init__(self, R, y_seq, a0, g1, g2, T):
345-
self.a0, self.y_seq, self.R, self.β = a0, y_seq, R, 1/R # set β = 1/R
346-
self.g1, self.g2 = g1, g2 # welfare parameter
347-
self.T = T
348-
349-
self.β_seq = np.array([self.β**i for i in range(T+1)])
350-
351-
def compute_optimal(self, verbose=1):
352-
R, y_seq, a0, T = self.R, self.y_seq, self.a0, self.T
353-
354-
# non-financial wealth
355-
h0 = self.β_seq @ y_seq # since β = 1/R
356-
357-
# c0
358-
c0 = (1 - 1/R) / (1 - (1/R)**(T+1)) * (a0 + h0)
359-
c_seq = c0*np.ones(T+1)
360-
361-
# verify
362-
A = np.diag(-R*np.ones(T), k=-1) + np.eye(T+1)
363-
b = y_seq - c_seq
364-
b[0] = b[0] + a0
365-
366-
a_seq = np.linalg.inv(A) @ b
367-
a_seq = np.concatenate([[a0], a_seq])
368-
369-
# check that a_T+1 = 0
370-
if verbose==1:
371-
print('check a_T+1=0:', np.abs(a_seq[-1] - 0) <= 1e-8)
372-
373-
return c_seq, a_seq
374-
375-
def welfare(self, c_seq):
376-
β_seq, g1, g2 = self.β_seq, self.g1, self.g2
377-
378-
u_seq = g1 * c_seq - g2/2 * c_seq**2
379-
return β_seq @ u_seq
380-
381-
382-
def compute_variation(self, ξ1, ϕ, verbose=1):
383-
R, T, β_seq = self.R, self.T, self.β_seq
384-
385-
ξ0 = ξ1*((1 - 1/R) / (1 - (1/R)**(T+1))) * ((1 - (ϕ/R)**(T+1)) / (1 - ϕ/R))
386-
v_seq = np.array([(ξ1*ϕ**t - ξ0) for t in range(T+1)])
387-
388-
# check if it is feasible
389-
if verbose==1:
390-
print('check feasible:', np.round(β_seq @ v_seq, 7)==0) # since β = 1/R
391-
392-
c_opt, _ = self.compute_optimal(verbose=verbose)
393-
cvar_seq = c_opt + v_seq
394-
395-
return cvar_seq
396-
```
415+
graphs to show that, when $\beta R = 1$ and starting from the smooth path, all nontrivial budget-feasible variations lower welfare according to the criterion above.
397416

398-
+++ {"user_expressions": []}
399-
400-
Below is an example where the consumer inherits $a_0<0$ (which can be interpreted as a student debt).
401-
402-
The income process $\{y_t\}_{t=0}^{T}$ is constant and positive up to $t=45$ and then becomes zero afterward.
417+
Now let's compute and visualize the variations
403418

404419
```{code-cell} ipython3
405-
# parameters
406-
T=65
407-
R = 1.05
408-
g1 = 1
409-
g2 = 1/2
410-
411-
# financial wealth
412-
a0 = -2 # such as "student debt"
420+
def compute_variation(model, ξ1, ϕ, a0, y_seq, verbose=1):
421+
R, T, β_seq = model.R, model.T, model.β_seq
413422
414-
# income process
415-
y_seq = np.concatenate([np.ones(46), np.zeros(20)])
416-
417-
# create an instance
418-
mc = Consumption_smoothing(R=R, y_seq=y_seq, a0=a0, g1=g1, g2=g2, T=T)
419-
c_seq, a_seq = mc.compute_optimal()
420-
421-
# compute welfare
422-
print('Welfare:', mc.welfare(c_seq))
423-
```
423+
ξ0 = ξ1*((1 - 1/R) / (1 - (1/R)**(T+1))) * ((1 - (ϕ/R)**(T+1)) / (1 - ϕ/R))
424+
v_seq = np.array([(ξ1*ϕ**t - ξ0) for t in range(T+1)])
425+
426+
if verbose == 1:
427+
print('check feasible:', np.isclose(β_seq @ v_seq, 0)) # since β = 1/R
424428
425-
```{code-cell} ipython3
426-
plt.plot(range(T+1), y_seq, label='income')
427-
plt.plot(range(T+1), c_seq, label='consumption')
428-
plt.plot(range(T+2), a_seq, label='asset')
429-
plt.plot(range(T+2), np.zeros(T+2), '--')
429+
c_opt, _ = compute_optimal(model, a0, y_seq)
430+
cvar_seq = c_opt + v_seq
430431
431-
plt.legend()
432-
plt.xlabel(r'$t$')
433-
plt.ylabel(r'$c_t,y_t,a_t$')
434-
plt.show()
432+
return cvar_seq
435433
```
436434

437435
+++ {"user_expressions": []}
438436

439-
We can visualize how $\xi_1$ and $\phi$ controls **budget-feasible variations**.
440-
441-
```{code-cell} ipython3
442-
# visualize variational paths
443-
cvar_seq1 = mc.compute_variation(ξ1=.01, ϕ=.95)
444-
cvar_seq2 = mc.compute_variation(ξ1=.05, ϕ=.95)
445-
cvar_seq3 = mc.compute_variation(ξ1=.01, ϕ=1.02)
446-
cvar_seq4 = mc.compute_variation(ξ1=.05, ϕ=1.02)
447-
```
437+
We visualize variations with $\xi_1 \in \{.01, .05\}$ and $\phi \in \{.95, 1.02\}$
448438

449439
```{code-cell} ipython3
450-
print('welfare of optimal c: ', mc.welfare(c_seq))
451-
print('variation 1: ', mc.welfare(cvar_seq1))
452-
print('variation 2:', mc.welfare(cvar_seq2))
453-
print('variation 3: ', mc.welfare(cvar_seq3))
454-
print('variation 4:', mc.welfare(cvar_seq4))
455-
```
440+
fig, ax = plt.subplots()
441+
442+
ξ1s = [.01, .05]
443+
ϕs= [.95, 1.02]
444+
colors = {.01: 'tab:blue', .05: 'tab:green'}
445+
446+
params = np.array(np.meshgrid(ξ1s, ϕs)).T.reshape(-1, 2)
447+
448+
for i, param in enumerate(params):
449+
ξ1, ϕ = param
450+
print(f'variation {i}: ξ1={ξ1}, ϕ={ϕ}')
451+
cvar_seq = compute_variation(model=cs_model,
452+
ξ1=ξ1, ϕ=ϕ, a0=a0,
453+
y_seq=y_seq)
454+
print(f'welfare={welfare(cs_model, cvar_seq)}')
455+
print('-'*64)
456+
if i % 2 == 0:
457+
ls = '-.'
458+
else:
459+
ls = '-'
460+
ax.plot(range(T+1), cvar_seq, ls=ls, color=colors[ξ1], label=fr'$\xi_1 = {ξ1}, \phi = {ϕ}$')
456461
457-
```{code-cell} ipython3
458462
plt.plot(range(T+1), c_seq, color='orange', label=r'Optimal $\vec{c}$ ')
459-
plt.plot(range(T+1), cvar_seq1, color='tab:blue', label=r'$\xi_1 = 0.01, \phi = 0.95$')
460-
plt.plot(range(T+1), cvar_seq2, color='tab:blue', ls='-.', label=r'$\xi_1 = 0.05, \phi = 0.95$')
461-
plt.plot(range(T+1), cvar_seq3, color='tab:green', label=r'$\xi_1 = 0.01, \phi = 1.02$')
462-
plt.plot(range(T+1), cvar_seq4, color='tab:green', ls='-.', label=r'$\xi_1 = 0.05, \phi = 1.02$')
463-
464463
465464
plt.legend()
466465
plt.xlabel(r'$t$')
467466
plt.ylabel(r'$c_t$')
468467
plt.show()
469468
```
470469

470+
+++ {"user_expressions": []}
471+
472+
We can even use the Python numpy grad command to compute derivatives of welfare with respect to our two parameters.
473+
474+
We are teaching the key idea beneath the **calculus of variations**.
475+
471476
```{code-cell} ipython3
472-
def welfare_ϕ(mc, ξ1, ϕ):
473-
"Compute welfare of variation sequence for given ϕ, ξ1 with an instance of our model mc"
474-
cvar_seq = mc.compute_variation(ξ1=ξ1, ϕ=ϕ, verbose=0)
475-
return mc.welfare(cvar_seq)
477+
def welfare_ϕ(ξ1, ϕ):
478+
"Compute welfare of variation sequence for given ϕ, ξ1 with a consumption smoothing model"
479+
cvar_seq = compute_variation(cs_model, ξ1=ξ1, ϕ=ϕ, a0=a0,
480+
y_seq=y_seq, verbose=0)
481+
return welfare(cs_model, cvar_seq)
476482
477-
welfare_φ = np.vectorize(welfare_φ)
483+
welfare_ϕ_vec = np.vectorize(welfare_ϕ)
478484
ξ1_arr = np.linspace(-0.5, 0.5, 20)
479485
480-
plt.plot(ξ1_arr, welfare_φ(mc, ξ1=ξ1_arr , ϕ=1.02))
486+
plt.plot(ξ1_arr, welfare_ϕ_vec(ξ1_arr, 1.02))
481487
plt.ylabel('welfare')
482488
plt.xlabel(r'$\xi_1$')
483489
plt.show()
484490
```
485-
486-
```{code-cell} ipython3
487-
488-
```

0 commit comments

Comments
 (0)