{ "cells": [ { "cell_type": "markdown", "id": "30980980", "metadata": {}, "source": [ "# Session 1 — Optimal Growth — Course Notebook\n", "\n", "**Goals**\n", "1. Load the `OptimalGrowth` package (local `.py` module).\n", "2. Simulate a baseline economy.\n", "3. Compute and *display* the planner's **effective optimization window**.\n", "4. Provide **exercises** with empty cells for students to complete.\n", "5. Run **sensitivity analyses** (parameters **and** the planner's window size).\n", "\n", "> This notebook is self‑contained and uses the local file `OptimalGrowth.py` provided with the course materials.\n" ] }, { "cell_type": "markdown", "id": "session1-cell-1", "metadata": {}, "source": [ "## Session 1 — Objectives and prerequisites\n", "\n", "**Learning objectives**\n", "- Run the original `OptimalGrowth.py` model and identify its states, controls, and welfare objective.\n", "- Recover the baseline optimal saving path.\n", "- Perform controlled comparative statics for depreciation and capital share.\n", "\n", "**Prerequisites.** Slides for Session 1; constrained optimization intuition; NumPy arrays and Matplotlib.\n" ] }, { "cell_type": "markdown", "id": "x3a-setup-2-markdown", "metadata": { "x3a_technical_cell": true }, "source": [ "## Technical setup\n", "\n", "Run from this session directory. Install dependencies in the next cell; imports follow separately.\n", "\n", "**Dates.** Row zero contains only initial states (capital, productivity, population). Output, consumption, investment and saving are `NaN` there. Row j stores flows and the saving decision over the interval ending at j, using states from row j-1. These missing initial flows are intentional and must not be replaced by zero.\n", "\n", "Open this notebook together with the current OptimalGrowth.py from session1. After downloading updated files, restart the kernel and run all cells from the top. The import cell prints the module path so you can check which copy is used.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "x3a-setup-2-code", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:41.096202Z", "iopub.status.busy": "2026-09-22T16:35:41.096072Z", "iopub.status.idle": "2026-09-22T16:35:41.100958Z", "shell.execute_reply": "2026-09-22T16:35:41.100112Z" }, "x3a_technical_cell": true }, "outputs": [], "source": [ "# Exécutez cette cellule une fois pour vérifier/installer les paquets Python requis pour ce notebook.\n", "\n", "import sys\n", "import subprocess\n", "import importlib.util\n", "\n", "required = {\n", " \"matplotlib\": \"matplotlib\",\n", " \"numba\": \"numba\",\n", " \"numpy\": \"numpy\",\n", " \"pandas\": \"pandas\",\n", " \"scipy\": \"scipy\",\n", " \"tqdm\": \"tqdm\",\n", "}\n", "\n", "missing = [\n", " package\n", " for module, package in required.items()\n", " if importlib.util.find_spec(module) is None\n", "]\n", "\n", "if missing:\n", " print(\"Installing:\", \", \".join(missing))\n", " subprocess.check_call(\n", " [sys.executable, \"-m\", \"pip\", \"install\", *missing]\n", " )\n", "\n", "print(\"Python environment ready.\")\n" ] }, { "cell_type": "markdown", "id": "17317494", "metadata": {}, "source": [ "## 1) Setup & Imports\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4540afc4", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:41.102193Z", "iopub.status.busy": "2026-09-22T16:35:41.102062Z", "iopub.status.idle": "2026-09-22T16:35:41.809191Z", "shell.execute_reply": "2026-09-22T16:35:41.808071Z" } }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import importlib\n", "import OptimalGrowth as og\n", "\n", "# Reload the module if this notebook was already open when the file changed.\n", "importlib.reload(og)\n", "print(\"Model file used:\", og.__file__)\n" ] }, { "cell_type": "markdown", "id": "0e2155c1", "metadata": {}, "source": [ "## 2) Model Parameters (`Params`)\n", "\n", "Baseline shared with the slides: alpha = 0.30, delta = 0.06, rho = 0.015, gamma = 2, A = L = 1, K0 = 3, Delta = 1 year. The exercises below change one parameter at a time.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "98911bc3", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:41.810584Z", "iopub.status.busy": "2026-09-22T16:35:41.810338Z", "iopub.status.idle": "2026-09-22T16:35:41.813502Z", "shell.execute_reply": "2026-09-22T16:35:41.812644Z" } }, "outputs": [], "source": [ "# Inspect default parameters\n", "p = og.Params()\n", "print(p)\n", "print(\"\\nNumber of periods nT =\", p.nT, \"with step Δ =\", p.Delta, \"years\")\n", "print(\"Time runs from\", p.t0, \"to\", p.tT)" ] }, { "cell_type": "markdown", "id": "aae960ea", "metadata": {}, "source": [ "## 3) Initialize state and determine exogenous path\n", "\n", "Before solving the planner’s problem, we need to **set the environment of the economy**. \n", "This involves two steps:\n", "\n", "1. **Exogenous variables** — external drivers such as productivity and population, which follow paths we specify in advance. \n", "2. **Initial state variables** — endogenous stocks like capital, which determine where the economy starts from.\n", "\n", "We begin with the exogenous variables.\n", "\n", "### Setting exogenous states\n", "\n", "Exogenous state variables are given by $x_t \\in \\mathbb{R}^{N_x}$: productivity $A_t$, population $L_t$.\n", "\n", "They follow an exogenous process:\n", "$$\n", "x_t = g(x_{t-1}), \\quad g: \\mathbb{R}^{N_x} \\to \\mathbb{R}^{N_x}.\n", "$$\n", "\n", "In this simplified notebook, we use two exogenous variables, $$A_t=L_t=1$$ that we have **fixed both to 1** (flat paths) to isolate the core mechanics. \n", "We store these in the $y$ matrix later; other variables are left as `NaN` until solved.\n", "\n", "### Setting initial state variables\n", "We also initialize state variables.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "24fe338b-ebff-4d00-aaf7-17618f83b217", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:41.814666Z", "iopub.status.busy": "2026-09-22T16:35:41.814460Z", "iopub.status.idle": "2026-09-22T16:35:41.821879Z", "shell.execute_reply": "2026-09-22T16:35:41.821281Z" } }, "outputs": [], "source": [ "# Initialize the simulation matrix with exogenous drivers and initial states\n", "sim = og.init_states(p)\n", "\n", "# Convert to a tidy DataFrame for inspection (optional)\n", "df0 = og.mat_to_df(sim, p)\n", "df0.head()" ] }, { "cell_type": "markdown", "id": "1532e582-7c9a-4c95-b0a7-27c8fd75779d", "metadata": {}, "source": [ "## 4) The planner's optimization problem\n", "\n", "Given initial capital, productivity and population paths, choose saving to maximize discounted utility. This is a deterministic problem (there is no expectation operator).\n", "\n", "$$\n", "\\max_{s_0,\\ldots,s_{H-1}}\\sum_{t=0}^{H-1}\\beta^t L_t u(\\kappa C_t/L_t),\\qquad \\beta=(1+\\rho)^{-\\Delta},\\quad \\kappa=1000.\n", "$$\n", "\n", "The constraints are $Y_t=A_t K_t^\\alpha L_t^{1-\\alpha}$, $C_t=(1-s_t)Y_t$, $I_t=s_tY_t$, and $K_{t+1}=(1-\\delta)^\\Delta K_t+\\Delta I_t$, with $K_0$ given and $0\\le s_t<1$. Utility is $u(c)=(c^{1-\\gamma}-1)/(1-\\gamma)$ (or $\\log c$ when $\\gamma=1$).\n", "\n", "The implementation uses bound-constrained optimization, a small consumption floor, and a rolling finite window with zero terminal payoff. Its discount threshold determines the window, but is not by itself a bound on welfare truncation error. See **growth_model.pdf** for the compact problem, Euler equation and the exact mapping between mathematical dates and array rows.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "68f2c97d", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:41.823022Z", "iopub.status.busy": "2026-09-22T16:35:41.822899Z", "iopub.status.idle": "2026-09-22T16:35:44.249165Z", "shell.execute_reply": "2026-09-22T16:35:44.248643Z" } }, "outputs": [], "source": [ "# Initialize the model and choose the decision dates.\n", "sim = og.init_states(p)\n", "timevec = np.arange(1, p.nT)\n", "bounds = (0.0, 1.0)\n", "control_id = [p.i_s]\n", "\n", "# Compute the optimal saving path.\n", "sim_opt = og.run_optimal_policy(sim, timevec, p, bounds, control_id)\n", "\n", "# Reproduce the planner-window calculation, step by step.\n", "discount = 1.0\n", "T_planner = 1\n", "while discount > p.toly:\n", " T_planner = T_planner + 1\n", " discount = discount * (1.0 / (1.0 + p.rho)) ** p.Delta\n", "\n", "print(\"Planning window (periods):\", T_planner)\n", "print(\"Planning window (years):\", T_planner * p.Delta)\n" ] }, { "cell_type": "markdown", "id": "a22f67fb", "metadata": {}, "source": [ "### 3.a) Plots — Optimal Paths\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7ce3fe9f", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:44.250637Z", "iopub.status.busy": "2026-09-22T16:35:44.250472Z", "iopub.status.idle": "2026-09-22T16:35:44.576401Z", "shell.execute_reply": "2026-09-22T16:35:44.575495Z" } }, "outputs": [], "source": [ "# Charts rule for this course notebook (matplotlib only; one chart per figure)\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "years = sim_opt[:, p.i_time]\n", "\n", "# s_t\n", "plt.figure()\n", "plt.plot(years, sim_opt[:, p.i_s], linewidth=2)\n", "plt.title(\"Optimal saving rate $s_t$\")\n", "plt.xlabel(\"Year\"); plt.ylabel(\"Share\"); plt.grid(True)\n", "plt.show()\n", "\n", "# K_t\n", "plt.figure()\n", "plt.plot(years, sim_opt[:, p.i_K], linewidth=2)\n", "plt.title(\"Capital $K_t$\")\n", "plt.xlabel(\"Year\"); plt.ylabel(\"Level\"); plt.grid(True)\n", "plt.show()\n", "\n", "# Y, C, I (three lines in one figure is acceptable; still one plot figure)\n", "plt.figure()\n", "plt.plot(years, sim_opt[:, p.i_Y], linewidth=2, label=\"Y\")\n", "plt.plot(years, sim_opt[:, p.i_C], linewidth=2, label=\"C\")\n", "plt.plot(years, sim_opt[:, p.i_I], linewidth=2, label=\"I\")\n", "plt.title(\"Output split: $Y_t = C_t + I_t$\")\n", "plt.xlabel(\"Year\"); plt.grid(True); plt.legend()\n", "plt.show()\n", "\n", "# c_t per capita\n", "plt.figure()\n", "c = np.full(p.nT, np.nan)\n", "c[1:] = sim_opt[1:, p.i_C] / sim_opt[:-1, p.i_L]\n", "plt.plot(years, c, linewidth=2)\n", "plt.title(\"Per‑capita consumption $c_t$\")\n", "plt.xlabel(\"Year\"); plt.grid(True)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "capital-dynamics-explanation", "metadata": {}, "source": [ "### Capital accumulation and the steady state\n", "\n", "The slides and this example use the same baseline: $\\alpha=0.30$, $\\delta=0.06$, $\\rho=0.015$, $\\gamma=2$, $A=L=1$ and a one-year step. Initial capital is $K_0=3$.\n", "\n", "The steady state satisfies $\\alpha (K^*)^{\\alpha-1}=\\rho+\\delta$. The diagram plots the **change in capital**, $K_{t+1}-K_t$, against the capital at the **start** of the same interval, $K_t$. It is a change in levels, not a percentage growth rate.\n", "\n", "We use the baseline path below $K^*$ and solve the same model once more with initial capital above $K^*$. All other parameters stay unchanged. Positive changes mean capital accumulates; negative changes mean it decreases. Both paths approach $K^*$.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "capital-dynamics-figure", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:44.577694Z", "iopub.status.busy": "2026-09-22T16:35:44.577561Z", "iopub.status.idle": "2026-09-22T16:35:47.185695Z", "shell.execute_reply": "2026-09-22T16:35:47.185120Z" } }, "outputs": [], "source": [ "# Steady state for the baseline with A = L = 1 and a one-year step.\n", "K_star = (p.alpha / (p.rho + p.delta)) ** (1.0 / (1.0 - p.alpha))\n", "print(\"Steady-state capital:\", K_star)\n", "\n", "# Start above the steady state; keep the same default calibration.\n", "p_high = og.Params()\n", "p_high.K0 = 2.0 * K_star\n", "sim_high = og.init_states(p_high)\n", "sim_high = og.run_optimal_policy(\n", " sim_high, range(1, p_high.nT), p_high, (0.0, 1.0), [p_high.i_s]\n", ")\n", "\n", "# Match each change in capital with capital at the start of its interval.\n", "K_low = sim_opt[:-1, p.i_K]\n", "change_low = sim_opt[1:, p.i_K] - sim_opt[:-1, p.i_K]\n", "K_high = sim_high[:-1, p_high.i_K]\n", "change_high = sim_high[1:, p_high.i_K] - sim_high[:-1, p_high.i_K]\n", "\n", "plt.figure(figsize=(8, 4.3))\n", "plt.plot(K_low, change_low, linewidth=2, color=\"tab:blue\", label=\"Initially below the steady state\")\n", "plt.plot(K_high, change_high, linewidth=2, color=\"tab:orange\", label=\"Initially above the steady state\")\n", "plt.axhline(0, color=\"0.35\", linewidth=0.8)\n", "plt.axvline(K_star, color=\"0.45\", linestyle=\"--\", linewidth=1)\n", "plt.plot(K_star, 0, \"o\", color=\"black\", label=r\"Steady state $K^*$\")\n", "plt.xlabel(r\"Capital at the start of the interval, $K_t$\")\n", "plt.ylabel(r\"Change in capital, $K_{t+1}-K_t$\")\n", "plt.title(\"Capital accumulation and convergence\")\n", "plt.legend(fontsize=9)\n", "plt.grid(alpha=0.2)\n", "plt.tight_layout()\n", "# This is the same figure used in the slides.\n", "plt.savefig(\"images/phase_diagram.pdf\", bbox_inches=\"tight\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "ff159dbb", "metadata": {}, "source": [ "## 4) **Exercises** (fill in the code cells)\n", "\n", "### Exercise 1 — Reproduce the baseline and check feasibility\n", "- Recreate the baseline path using your own code calls.\n", "- Verify that resource feasibility holds period by period: \\(Y_t = C_t + I_t\\).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "22bcbe93", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:47.186903Z", "iopub.status.busy": "2026-09-22T16:35:47.186786Z", "iopub.status.idle": "2026-09-22T16:35:47.189004Z", "shell.execute_reply": "2026-09-22T16:35:47.188369Z" } }, "outputs": [], "source": [ "# TODO: Your code here.\n", "# Hints:\n", "# p1 = og.Params(); ensure p1.lg is defined; sim1 = og.init_states(p1); ...\n", "pass" ] }, { "cell_type": "markdown", "id": "52e4eced", "metadata": {}, "source": [ "### Exercise 2 — Compare higher depreciation `delta`\n", "- Increase `delta` by +5 percentage points (e.g., from 0.06 to 0.11).\n", "- Re‑simulate and compare the time paths of \\(s_t\\), \\(K_t\\), and \\(c_t\\).\n", "- Provide a short economic interpretation.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "de96325e", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:47.190082Z", "iopub.status.busy": "2026-09-22T16:35:47.189958Z", "iopub.status.idle": "2026-09-22T16:35:47.192114Z", "shell.execute_reply": "2026-09-22T16:35:47.191436Z" } }, "outputs": [], "source": [ "# TODO: Your code here.\n", "# Example skeleton:\n", "# p_hi = og.Params(); p_hi.delta = 0.11\n", "# sim_hi = og.run_optimal_policy(og.init_states(p_hi), np.arange(1, p_hi.nT, dtype=int), p_hi, (0,1), [p_hi.i_s])\n", "# Plot comparisons vs. baseline.\n", "pass" ] }, { "cell_type": "markdown", "id": "9b6fce79", "metadata": {}, "source": [ "### Exercise 3 — Compare higher capital share `alpha`\n", "- Increase `alpha` from 0.30 to 0.45.\n", "- Discuss how it changes the optimal savings and capital accumulation.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "cb38f261", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:47.193109Z", "iopub.status.busy": "2026-09-22T16:35:47.192988Z", "iopub.status.idle": "2026-09-22T16:35:47.195212Z", "shell.execute_reply": "2026-09-22T16:35:47.194431Z" } }, "outputs": [], "source": [ "# TODO: Your code here.\n", "pass" ] }, { "cell_type": "markdown", "id": "a5f413a1", "metadata": {}, "source": [ "### Exercise 4 — Add a DICE-style population growth law\n", "\n", "So far, we have treated population as an exogenous flat path ($L_t = 1$). \n", "In integrated assessment models, population follows a **law of motion** that captures the idea of demographic saturation:\n", "\n", "$$\n", "L_{t+1} = L_t^{1-\\zeta_L} \\cdot L_{\\infty}^{\\zeta_L} ,\n", "$$\n", "\n", "- $L_{\\infty}$ is the **long-run population level** (carrying capacity), \n", "- $\\zeta_L$ controls the **speed of convergence** toward $L_{\\infty}$, \n", "\n", "Questions\n", "- The calibration of $\\zeta_L$ is right now 0, calibrate it to 0.02.\n", "- Compare results with stable population.\n", "Use the supplied target Linf=10500 in the same abstract units as L0=1. This is a demographic stress test, not a realistic normalized population forecast.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "41ed0580", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:47.196155Z", "iopub.status.busy": "2026-09-22T16:35:47.196032Z", "iopub.status.idle": "2026-09-22T16:35:47.198168Z", "shell.execute_reply": "2026-09-22T16:35:47.197547Z" } }, "outputs": [], "source": [ "# TODO: Your code here.\n", "# You can copy and adapt og.init_states into a new function, then use it in place of the default.\n", "#p.L0 = 1.0 # initial population (scale arbitrary)\n", "#p.Linf = 10500 # Asymptotic population (millions)\n", "#p.lg = 0 # Population growth rate parameter\n", "pass" ] }, { "cell_type": "markdown", "id": "ad9ba963-8ac2-481e-a47f-cf5bda0f36b7", "metadata": {}, "source": [ "#### Interpret your results: does a growing population increase saving? Why?\n" ] }, { "cell_type": "markdown", "id": "eb72a8b5-431a-4505-81cc-2ef700ee92ce", "metadata": {}, "source": [ "Write your answer here.\n" ] }, { "cell_type": "markdown", "id": "efc264ff-3a97-4591-8e96-37b5bacc1066", "metadata": {}, "source": [ "### Exercise 5 — Add DICE-style **TFP** dynamics with declining growth\n", "\n", "So far, we kept productivity flat (effectively $A_t \\equiv 1$ because `gA = 0`). \n", "We now introduce **time-varying TFP growth** that declines over time—akin to what DICE uses.\n", "\n", "**Law of motion (implemented in `OptimalGrowth.py`):**\n", "$$\n", "A_t \\;=\\; A_{t-1}\\; G_t, \n", "\\qquad\\text{with}\\qquad \n", "G_t \\;=\\; \\frac{1}{\\,1 - g_A \\exp\\!\\big(-\\delta_A\\,\\Delta\\,t\\big)\\,}\\,.\n", "$$\n", "\n", "Equivalently, the **per-step proportional growth rate** is approximately\n", "$$\n", "g_{A,t} \\;\\approx\\; g_A \\exp\\!\\big(-\\delta_A\\,\\Delta\\,t\\big).\n", "$$\n", "Hence, **initial growth** is about $g_A$, then it **decays at rate** $\\delta_A$ toward zero.\n", "\n", "**Parameters in `Params`:**\n", "- `A0` — initial TFP level (default $A_0 = 1$; normalization).\n", "- `gA` — **initial** TFP growth *per year* (currently set to `0.015*0`, i.e., **zero** in the notebook).\n", "- `deltaA` — **decay rate** of the TFP growth (per year).\n", "- `Delta` — time step (years per period).\n", "\n", "---\n", "\n", "#### Suggested calibration for this exercise\n", "\n", "Use a positive but realistic initial growth, and a slow decay:\n", "\n", "- $A_0 = 1$\n", "- $g_A = 0.015$ (≈ **1.5%/year** initial TFP growth)\n", "- $\\delta_A = 0.005$ (growth decays at **0.5%/year** toward zero)\n", "\n", "**Questions / Tasks**\n", "\n", "1. Implement the law of motion above (already in the module) by setting the parameters accordingly.\n", "2. Re-simulate the planner’s problem and **plot $A_t$** and **compare**:\n", " - saving rate $s_t$,\n", " - output $Y_t$,\n", " - consumption per capita $c_t = C_t/L_t$,\n", " against the baseline with constant TFP.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "f1fb6c18-390f-45c5-a6c4-e408a165ba71", "metadata": { "execution": { "iopub.execute_input": "2026-09-22T16:35:47.199446Z", "iopub.status.busy": "2026-09-22T16:35:47.199328Z", "iopub.status.idle": "2026-09-22T16:35:47.201716Z", "shell.execute_reply": "2026-09-22T16:35:47.201035Z" } }, "outputs": [], "source": [ "# 1) Set parameters for TFP growth that declines over time\n", "p_tfp = og.Params()\n", "p_tfp.A0 = 1.0\n", "p_tfp.gA = 0.015 # ≈ 1.5%/year initial TFP growth\n", "p_tfp.deltaA = 0.005 # growth decay rate per year (toward 0)\n", "\n", "#....\n", "pass" ] }, { "cell_type": "markdown", "id": "ac1d61a0-5ab6-41e8-8c2e-c2a4a63d0123", "metadata": {}, "source": [ "3. Discuss: how does transitional TFP growth alter the optimal saving path and welfare relative to flat TFP?\n" ] }, { "cell_type": "markdown", "id": "1c0a4966-c509-410a-a268-45d7a0d0745f", "metadata": {}, "source": [ "Write your answer here.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" }, "x3a_merged_sources": [ "Session0/Notebook_OptimalGrowth.ipynb" ], "x3a_provenance": { "allowed_changes": [ "file rename", "session title", "local-path normalization", "optional installation cell", "stored-output normalization" ], "session": 1, "source_files": [ "Session0/Notebook_OptimalGrowth.ipynb" ] } }, "nbformat": 4, "nbformat_minor": 5 }