From e8d6b4a2bc50012d96dd05912de65f9db4bd50fe Mon Sep 17 00:00:00 2001 From: mmcky Date: Tue, 29 Jul 2025 12:54:18 +1000 Subject: [PATCH 01/12] MAINT: upgrade anaconda=2025.06 and python=3.13 --- .github/workflows/cache.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/execution.yml | 6 +++--- .github/workflows/linkcheck.yml | 4 ++-- .github/workflows/publish.yml | 2 +- environment.yml | 19 ++++++++----------- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml index c83e76b7..49c02404 100644 --- a/.github/workflows/cache.yml +++ b/.github/workflows/cache.yml @@ -15,7 +15,7 @@ jobs: auto-update-conda: true auto-activate-base: true miniconda-version: 'latest' - python-version: "3.12" + python-version: "3.13" environment-file: environment.yml activate-environment: quantecon - name: Install latex dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a38ace90..ed2d6594 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: auto-update-conda: true auto-activate-base: true miniconda-version: 'latest' - python-version: "3.12" + python-version: "3.13" environment-file: environment.yml activate-environment: quantecon - name: Install latex dependencies diff --git a/.github/workflows/execution.yml b/.github/workflows/execution.yml index 16b55aec..ec9a5e9e 100644 --- a/.github/workflows/execution.yml +++ b/.github/workflows/execution.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest"] - python-version: ["3.12"] + python-version: ["3.13"] steps: - name: Checkout uses: actions/checkout@v4 @@ -53,7 +53,7 @@ jobs: fail-fast: false matrix: os: ["macos-latest"] - python-version: ["3.12"] + python-version: ["3.13"] steps: - name: Checkout uses: actions/checkout@v4 @@ -85,7 +85,7 @@ jobs: # fail-fast: false # matrix: # os: ["windows-latest"] - # python-version: ["3.11"] + # python-version: ["3.13"] # steps: # - name: Checkout # uses: actions/checkout@v2 diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml index fd0c1848..b1301358 100644 --- a/.github/workflows/linkcheck.yml +++ b/.github/workflows/linkcheck.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest"] - python-version: ["3.12"] + python-version: ["3.13"] steps: - name: Checkout uses: actions/checkout@v4 @@ -20,7 +20,7 @@ jobs: auto-update-conda: true auto-activate-base: true miniconda-version: 'latest' - python-version: "3.12" + python-version: "3.13" environment-file: environment.yml activate-environment: quantecon - name: Download "build" folder (cache) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fce75dcf..4af023d0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,7 +16,7 @@ jobs: auto-update-conda: true auto-activate-base: true miniconda-version: 'latest' - python-version: "3.12" + python-version: "3.13" environment-file: environment.yml activate-environment: quantecon - name: Install latex dependencies diff --git a/environment.yml b/environment.yml index f69c4ec3..228241ec 100644 --- a/environment.yml +++ b/environment.yml @@ -2,20 +2,17 @@ name: quantecon channels: - default dependencies: - - python=3.12 - - anaconda=2024.10 + - python=3.13 + - anaconda=2025.06 - pip - pip: - - jupyter-book==1.0.3 - - quantecon-book-theme==0.7.6 - - sphinx-tojupyter==0.3.0 + - jupyter-book==1.0.4post1 + - quantecon-book-theme==0.8.3 + - sphinx-tojupyter==0.3.1 - sphinxext-rediraffe==0.2.7 - - sphinx-reredirects==0.1.4 - sphinx-exercise==1.0.1 - - sphinx-proof==0.2.0 - - ghp-import==1.1.0 - - sphinxcontrib-youtube==1.3.0 #Version 1.3.0 is required as quantecon-book-theme is only compatible with sphinx<=5 + - sphinxcontrib-youtube==1.4.1 - sphinx-togglebutton==0.3.2 - # Docker Requirements - - pytz + - sphinx-reredirects==0.1.4 + From ad8d4edda3568ff36343a8b6a100a9885b17674f Mon Sep 17 00:00:00 2001 From: mmcky Date: Tue, 29 Jul 2025 13:02:41 +1000 Subject: [PATCH 02/12] add back in sphinx-proof --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 228241ec..80b64d6a 100644 --- a/environment.yml +++ b/environment.yml @@ -11,6 +11,7 @@ dependencies: - sphinx-tojupyter==0.3.1 - sphinxext-rediraffe==0.2.7 - sphinx-exercise==1.0.1 + - sphinx-proof==0.2.1 - sphinxcontrib-youtube==1.4.1 - sphinx-togglebutton==0.3.2 - sphinx-reredirects==0.1.4 From 33087366b47461dce4caa98cd8053374da96c63d Mon Sep 17 00:00:00 2001 From: mmcky Date: Wed, 30 Jul 2025 09:06:26 +1000 Subject: [PATCH 03/12] migrate to lychee for linkchecking --- .github/workflows/linkcheck.yml | 54 ++++++++++++++------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml index b1301358..cada059f 100644 --- a/.github/workflows/linkcheck.yml +++ b/.github/workflows/linkcheck.yml @@ -1,41 +1,31 @@ name: Link Checker [Anaconda, Linux] on: - pull_request: - types: [opened, reopened] + schedule: + # UTC 23:00 is early morning in Australia (9am) + - cron: '0 23 * * *' + workflow_dispatch: jobs: - link-check-linux: - name: Link Checking (${{ matrix.python-version }}, ${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest"] - python-version: ["3.13"] + link-checking: + name: Link Checking + runs-on: "ubuntu-latest" + permissions: + issues: write # required for peter-evans/create-issue-from-file steps: + # Checkout the live site (html) - name: Checkout uses: actions/checkout@v4 - - name: Setup Anaconda - uses: conda-incubator/setup-miniconda@v3 with: - auto-update-conda: true - auto-activate-base: true - miniconda-version: 'latest' - python-version: "3.13" - environment-file: environment.yml - activate-environment: quantecon - - name: Download "build" folder (cache) - uses: dawidd6/action-download-artifact@v11 - with: - workflow: cache.yml - branch: main - name: build-cache - path: _build + ref: gh-pages - name: Link Checker - shell: bash -l {0} - run: jb build lectures --path-output=./ --builder=custom --custom-builder=linkcheck - - name: Upload Link Checker Reports - uses: actions/upload-artifact@v4 - if: failure() + id: lychee + uses: lycheeverse/lychee-action@v2 + with: + fail: false + args: --accept 403,503 *.html + - name: Create Issue From File + if: steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@v5 with: - name: linkcheck-reports - path: _build/linkcheck \ No newline at end of file + title: Link Checker Report + content-filepath: ./lychee/out.md + labels: report, automated issue, linkchecker \ No newline at end of file From 4506af4a0d3c1e4af117c1569e538a107cc017df Mon Sep 17 00:00:00 2001 From: mmcky Date: Wed, 30 Jul 2025 09:39:15 +1000 Subject: [PATCH 04/12] fix figure to image in exercise --- lectures/stationary_densities.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lectures/stationary_densities.md b/lectures/stationary_densities.md index ab148ee8..dfca439a 100644 --- a/lectures/stationary_densities.md +++ b/lectures/stationary_densities.md @@ -784,8 +784,8 @@ In doing so, set $\theta = 0.8$ and $n = 500$. The next figure shows the result of such a computation -```{figure} /_static/lecture_specific/stationary_densities/solution_statd_ex1.png - +```{image} /_static/lecture_specific/stationary_densities/solution_statd_ex1.png +:align: centre ``` The additional density (black line) is a [nonparametric kernel density estimate](https://en.wikipedia.org/wiki/Kernel_density_estimation), added to the solution for illustration. From c5b94d010507472262ec06ba9143ec34018fd713 Mon Sep 17 00:00:00 2001 From: mmcky Date: Wed, 30 Jul 2025 09:49:43 +1000 Subject: [PATCH 05/12] fix align option --- lectures/stationary_densities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lectures/stationary_densities.md b/lectures/stationary_densities.md index dfca439a..827ed4d5 100644 --- a/lectures/stationary_densities.md +++ b/lectures/stationary_densities.md @@ -785,7 +785,7 @@ In doing so, set $\theta = 0.8$ and $n = 500$. The next figure shows the result of such a computation ```{image} /_static/lecture_specific/stationary_densities/solution_statd_ex1.png -:align: centre +:align: center ``` The additional density (black line) is a [nonparametric kernel density estimate](https://en.wikipedia.org/wiki/Kernel_density_estimation), added to the solution for illustration. From d41cca98798c714840c87a3de6a916d5e949d3b6 Mon Sep 17 00:00:00 2001 From: mmcky Date: Wed, 30 Jul 2025 10:34:30 +1000 Subject: [PATCH 06/12] tmp: disable build cahce --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed2d6594..940cea87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,13 +34,13 @@ jobs: - name: Display Pip Versions shell: bash -l {0} run: pip list - - name: Download "build" folder (cache) - uses: dawidd6/action-download-artifact@v11 - with: - workflow: cache.yml - branch: main - name: build-cache - path: _build + # - name: Download "build" folder (cache) + # uses: dawidd6/action-download-artifact@v11 + # with: + # workflow: cache.yml + # branch: main + # name: build-cache + # path: _build # Build Assets (Download Notebooks and PDF via LaTeX) - name: Build PDF from LaTeX shell: bash -l {0} From 7a2f747cee86f01ff492aa9d57317849fbf9282f Mon Sep 17 00:00:00 2001 From: mmcky Date: Wed, 6 Aug 2025 10:50:42 +1000 Subject: [PATCH 07/12] update match_transport for numpy2 --- .gitignore | 4 ++- lectures/match_transport.md | 50 +++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index e6b45300..e31c3bc0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ _build/ -.DS_Store \ No newline at end of file +.DS_Store +.ipynb_checkpoints/ +.virtual_documents/ \ No newline at end of file diff --git a/lectures/match_transport.md b/lectures/match_transport.md index f1abce03..9fc12032 100644 --- a/lectures/match_transport.md +++ b/lectures/match_transport.md @@ -4,7 +4,7 @@ jupytext: extension: .md format_name: myst format_version: 0.13 - jupytext_version: 1.16.4 + jupytext_version: 1.17.2 kernelspec: display_name: Python 3 (ipykernel) language: python @@ -13,13 +13,11 @@ kernelspec: # Composite Sorting -+++ - ## Overview Optimal transport theory studies how a marginal probabilty measure can be related to another marginal probability measure in an ideal way. - * here ideal means to minimize some cost criterion. +* here ideal means to minimize some cost criterion. The output of such a theory is a **coupling** of the two probability measures, i.e., a joint probabilty measure having those two marginal probability measures. @@ -575,9 +573,9 @@ def plot_H_z(self, figsize=(15, 8), range_x_axis=None, scatter=True): plt.axhline(0, color='black', linewidth=1) # determine the step points for horizontal lines - step = np.concatenate(([self.support_z.min() - .05 * self.support_z.ptp()], + step = np.concatenate(([self.support_z.min() - .05 * np.ptp(self.support_z)], self.support_z, - [self.support_z.max() + .05 * self.support_z.ptp()])) + [self.support_z.max() + .05 * np.ptp(self.support_z)])) height = np.concatenate(([0], H_z, [0])) # plot the horizontal lines of the step function @@ -699,9 +697,9 @@ def plot_layers(self, figsize=(15, 8)): plt.figure(figsize=figsize) # Plot H(z) - step = np.concatenate(([self.support_z.min() - .05 * self.support_z.ptp()], + step = np.concatenate(([self.support_z.min() - .05 * np.ptp(self.support_z)], self.support_z, - [self.support_z.max() + .05 * self.support_z.ptp()])) + [self.support_z.max() + .05 * np.ptp(self.support_z)])) height = np.concatenate((H_z, [0])) plt.step(step, height, where='post', color='black', label='CDF', zorder=1) @@ -984,7 +982,7 @@ def plot_layer_matching(self, layer, matching_layer): ax.spines['top'].set_color('none') ax.spines['right'].set_color('none') ax.yaxis.set_ticks([]) - ax.set_ylim(bottom= -self.support_z.ptp() / 100) + ax.set_ylim(bottom= -np.ptp(self.support_z) / 100) plt.show() @@ -1319,20 +1317,20 @@ def plot_matching(self, matching_off_diag, title, figsize=(15, 15), ax.spines['top'].set_color('none') ax.spines['right'].set_color('none') ax.yaxis.set_ticks([]) - ax.set_ylim(- self.X_types.ptp() / 10, - (max_height / 2) + self.X_types.ptp()*.01) + ax.set_ylim(- np.ptp(self.X_types) / 10, + (max_height / 2) + np.ptp(self.X_types)*.01) # Plot H_z on the main axis if enabled if plot_H_z: H_z = np.cumsum(self.q_z) step = np.concatenate(([self.support_z.min() - - .02 * self.support_z.ptp()], + - .02 * np.ptp(self.support_z)], self.support_z, [self.support_z.max() - + .02 * self.support_z.ptp()])) + + .02 * np.ptp(self.support_z)])) - H_z = H_z/H_z.ptp() * self.support_z.ptp() /2 + H_z = H_z/np.ptp(H_z) * np.ptp(self.support_z) /2 height = np.concatenate(([0], H_z, [0])) # Plot the compressed H_z on the same main x-axis @@ -1340,8 +1338,8 @@ def plot_matching(self, matching_off_diag, title, figsize=(15, 15), label='$H_z$', where='post') # Set the y-limit to keep H_z and maximum circle size in the plot - ax.set_ylim(np.min(H_z) - H_z.ptp() *.01, - np.maximum(np.max(H_z), max_height / 2) + H_z.ptp() *.01) + ax.set_ylim(np.min(H_z) - np.ptp(H_z) *.01, + np.maximum(np.max(H_z), max_height / 2) + np.ptp(H_z) *.01) # Add label and legend for H_z ax.legend(loc="upper right") @@ -1614,7 +1612,7 @@ example_2.plot_matching(matching_NAM, title = 'NAM', +++ {"user_expressions": []} -### Example 3 +### Example 3 +++ {"user_expressions": []} @@ -1907,7 +1905,7 @@ def plot_hierarchies(self, subpairs, scatter=True, range_x_axis=None): if range_x_axis is not None: ax.set_xlim(range_x_axis) - ax.set_ylim(- self.X_types.ptp() / 10, + ax.set_ylim(- np.ptp(self.X_types) / 10, (range_x_axis[1] - range_x_axis[0]) / 2 ) # Title and layout settings for the main plot @@ -2230,12 +2228,11 @@ We plot the wage standard deviation for the sorted occupations. --- mystnb: figure: - caption: "Average wage for each Standard Occupational Classification (SOC) code. - The codes are sorted by average wage on the horizontal axis. In red, - a polynomial of degree 5 is fitted to the data. The size of the marker is - proportional to the number of individuals in the occupation." + caption: Average wage for each Standard Occupational Classification (SOC) code. + The codes are sorted by average wage on the horizontal axis. In red, a polynomial + of degree 5 is fitted to the data. The size of the marker is proportional to + the number of individuals in the occupation. --- - # Scatter plot wage dispersion for each occupation plt.figure(figsize=(10, 6)) @@ -2273,11 +2270,10 @@ We also plot the average wages for each occupation (SOC code). Again, occupation --- mystnb: figure: - caption: "Average wage for each Standard Occupational Classification (SOC) code. - The codes are sorted by average wage on the horizontal axis. In red, - a polynomial of degree 5 is fitted to the data." + caption: Average wage for each Standard Occupational Classification (SOC) code. + The codes are sorted by average wage on the horizontal axis. In red, a polynomial + of degree 5 is fitted to the data. --- - # Scatter plot average wage for each occupation plt.figure(figsize=(10, 6)) From fbc81218d046eba992495be7564d5b0d86ca2d30 Mon Sep 17 00:00:00 2001 From: mmcky Date: Wed, 6 Aug 2025 10:58:10 +1000 Subject: [PATCH 08/12] convert np.asfarray -> np.assarray( , dtype=float) --- lectures/_static/lecture_specific/amss2/recursive_allocation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lectures/_static/lecture_specific/amss2/recursive_allocation.py b/lectures/_static/lecture_specific/amss2/recursive_allocation.py index 82f2f818..3dd83501 100644 --- a/lectures/_static/lecture_specific/amss2/recursive_allocation.py +++ b/lectures/_static/lecture_specific/amss2/recursive_allocation.py @@ -230,7 +230,7 @@ def objf(z): def objf_prime(x): epsilon = 1e-7 - x0 = np.asfarray(x) + x0 = np.asarray(x, dtype=float) f0 = np.atleast_1d(objf(x0)) jac = np.zeros([len(x0), len(f0)]) dx = np.zeros(len(x0)) From 288e4aefd87b4bc67562d6c20a607a62145a2bcf Mon Sep 17 00:00:00 2001 From: Humphrey Yang Date: Sun, 21 Sep 2025 12:24:41 +1000 Subject: [PATCH 09/12] update amss to remove dependency on interpolation --- .../amss/recursive_allocation.py | 83 +++++++++++++++++-- lectures/amss.md | 81 ++++++++++++++++-- 2 files changed, 150 insertions(+), 14 deletions(-) diff --git a/lectures/_static/lecture_specific/amss/recursive_allocation.py b/lectures/_static/lecture_specific/amss/recursive_allocation.py index f5495f1f..91b8bd32 100644 --- a/lectures/_static/lecture_specific/amss/recursive_allocation.py +++ b/lectures/_static/lecture_specific/amss/recursive_allocation.py @@ -1,3 +1,64 @@ +import numpy as np +from numba import njit, prange +from quantecon import optimize + +@njit +def get_grid_nodes(grid): + """ + Get the actual grid points from a grid tuple. + """ + x_min, x_max, x_num = grid + return np.linspace(x_min, x_max, x_num) + +@njit +def linear_interp_1d_scalar(x_min, x_max, x_num, y_values, x_val): + """Helper function for scalar interpolation""" + x_nodes = np.linspace(x_min, x_max, x_num) + + # Extrapolation with linear extension + if x_val <= x_nodes[0]: + # Linear extrapolation using first two points + if x_num >= 2: + slope = (y_values[1] - y_values[0]) / (x_nodes[1] - x_nodes[0]) + return y_values[0] + slope * (x_val - x_nodes[0]) + else: + return y_values[0] + + if x_val >= x_nodes[-1]: + # Linear extrapolation using last two points + if x_num >= 2: + slope = (y_values[-1] - y_values[-2]) / (x_nodes[-1] - x_nodes[-2]) + return y_values[-1] + slope * (x_val - x_nodes[-1]) + else: + return y_values[-1] + + # Binary search for the right interval + left = 0 + right = x_num - 1 + while right - left > 1: + mid = (left + right) // 2 + if x_nodes[mid] <= x_val: + left = mid + else: + right = mid + + # Linear interpolation + x_left = x_nodes[left] + x_right = x_nodes[right] + y_left = y_values[left] + y_right = y_values[right] + + weight = (x_val - x_left) / (x_right - x_left) + return y_left * (1 - weight) + y_right * weight + +@njit +def linear_interp_1d(x_grid, y_values, x_query): + """ + Perform 1D linear interpolation. + """ + x_min, x_max, x_num = x_grid + return linear_interp_1d_scalar(x_min, x_max, x_num, y_values, x_query[0]) + class AMSS: # WARNING: THE CODE IS EXTREMELY SENSITIVE TO CHOCIES OF PARAMETERS. # DO NOT CHANGE THE PARAMETERS AND EXPECT IT TO WORK @@ -78,6 +139,10 @@ def simulate(self, s_hist, b_0): pref = self.pref x_grid, g, β, S = self.x_grid, self.g, self.β, self.S σ_v_star, σ_w_star = self.σ_v_star, self.σ_w_star + Π = self.Π + + # Extract the grid tuple from the list + grid_tuple = x_grid[0] if isinstance(x_grid, list) else x_grid T = len(s_hist) s_0 = s_hist[0] @@ -111,8 +176,8 @@ def simulate(self, s_hist, b_0): T = np.zeros(S) for s in range(S): x_arr = np.array([x_]) - l[s] = eval_linear(x_grid, σ_v_star[s_, :, s], x_arr) - T[s] = eval_linear(x_grid, σ_v_star[s_, :, S+s], x_arr) + l[s] = linear_interp_1d(grid_tuple, σ_v_star[s_, :, s], x_arr) + T[s] = linear_interp_1d(grid_tuple, σ_v_star[s_, :, S+s], x_arr) c = (1 - l) - g u_c = pref.Uc(c, l) @@ -135,6 +200,8 @@ def simulate(self, s_hist, b_0): def obj_factory(Π, β, x_grid, g): S = len(Π) + # Extract the grid tuple from the list + grid_tuple = x_grid[0] if isinstance(x_grid, list) else x_grid @njit def obj_V(σ, state, V, pref): @@ -152,7 +219,7 @@ def obj_V(σ, state, V, pref): V_next = np.zeros(S) for s in range(S): - V_next[s] = eval_linear(x_grid, V[s], np.array([x[s]])) + V_next[s] = linear_interp_1d(grid_tuple, V[s], np.array([x[s]])) out = Π[s_] @ (pref.U(c, l) + β * V_next) @@ -167,7 +234,7 @@ def obj_W(σ, state, V, pref): c = (1 - l) - g[s_] x = -pref.Uc(c, l) * (c - T - b_0) + pref.Ul(c, l) * (1 - l) - V_next = eval_linear(x_grid, V[s_], np.array([x])) + V_next = linear_interp_1d(grid_tuple, V[s_], np.array([x])) out = pref.U(c, l) + β * V_next @@ -178,9 +245,11 @@ def obj_W(σ, state, V, pref): def bellman_operator_factory(Π, β, x_grid, g, bounds_v): obj_V, obj_W = obj_factory(Π, β, x_grid, g) - n = x_grid[0][2] + # Extract the grid tuple from the list + grid_tuple = x_grid[0] if isinstance(x_grid, list) else x_grid + n = grid_tuple[2] S = len(Π) - x_nodes = nodes(x_grid) + x_nodes = get_grid_nodes(grid_tuple) @njit(parallel=True) def T_v(V, V_new, σ_star, pref): @@ -209,4 +278,4 @@ def T_w(W, σ_star, V, b_0, pref): W[s_] = res.fun σ_star[s_] = res.x - return T_v, T_w + return T_v, T_w \ No newline at end of file diff --git a/lectures/amss.md b/lectures/amss.md index 0bb65d74..061f823a 100644 --- a/lectures/amss.md +++ b/lectures/amss.md @@ -27,7 +27,6 @@ In addition to what's in Anaconda, this lecture will need the following librarie tags: [hide-output] --- !pip install --upgrade quantecon -!pip install interpolation ``` ## Overview @@ -38,12 +37,80 @@ Let's start with following imports: import numpy as np import matplotlib.pyplot as plt from scipy.optimize import root -from interpolation.splines import eval_linear, UCGrid, nodes from quantecon import optimize, MarkovChain from numba import njit, prange, float64 from numba.experimental import jitclass ``` +Now let's define numba-compatible interpolation functions for this lecture. + +We will soon use the following interpolation functions to interpolate the value function and the policy functions + +```{code-cell} ipython +:tags: [collapse-40] + +@njit +def get_grid_nodes(grid): + """ + Get the actual grid points from a grid tuple. + """ + x_min, x_max, x_num = grid + return np.linspace(x_min, x_max, x_num) + +@njit +def linear_interp_1d_scalar(x_min, x_max, x_num, y_values, x_val): + """Helper function for scalar interpolation""" + x_nodes = np.linspace(x_min, x_max, x_num) + + # Extrapolation with linear extension + if x_val <= x_nodes[0]: + # Linear extrapolation using first two points + if x_num >= 2: + slope = (y_values[1] - y_values[0]) \ + / (x_nodes[1] - x_nodes[0]) + return y_values[0] + slope * (x_val - x_nodes[0]) + else: + return y_values[0] + + if x_val >= x_nodes[-1]: + # Linear extrapolation using last two points + if x_num >= 2: + slope = (y_values[-1] - y_values[-2]) \ + / (x_nodes[-1] - x_nodes[-2]) + return y_values[-1] + slope * (x_val - x_nodes[-1]) + else: + return y_values[-1] + + # Binary search for the right interval + left = 0 + right = x_num - 1 + while right - left > 1: + mid = (left + right) // 2 + if x_nodes[mid] <= x_val: + left = mid + else: + right = mid + + # Linear interpolation + x_left = x_nodes[left] + x_right = x_nodes[right] + y_left = y_values[left] + y_right = y_values[right] + + weight = (x_val - x_left) / (x_right - x_left) + return y_left * (1 - weight) + y_right * weight + +@njit +def linear_interp_1d(x_grid, y_values, x_query): + """ + Perform 1D linear interpolation. + """ + x_min, x_max, x_num = x_grid + return linear_interp_1d_scalar(x_min, x_max, x_num, y_values, x_query[0]) +``` + +Let's start with following imports: + In {doc}`an earlier lecture `, we described a model of optimal taxation with state-contingent debt due to Robert E. Lucas, Jr., and Nancy Stokey {cite}`LucasStokey1983`. @@ -774,7 +841,7 @@ x_min = -1.5555 x_max = 17.339 x_num = 300 -x_grid = UCGrid((x_min, x_max, x_num)) +x_grid = [(x_min, x_max, x_num)] crra_pref = CRRAutility(β=β, σ=σ, γ=γ) @@ -788,7 +855,7 @@ amss_model = AMSS(crra_pref, β, Π, g, x_grid, bounds_v) ```{code-cell} python3 # WARNING: DO NOT EXPECT THE CODE TO WORK IF YOU CHANGE PARAMETERS V = np.zeros((len(Π), x_num)) -V[:] = -nodes(x_grid).T ** 2 +V[:] = -get_grid_nodes(x_grid[0]) ** 2 σ_v_star = np.ones((S, x_num, S * 2)) σ_v_star[:, :, :S] = 0.0 @@ -914,14 +981,14 @@ x_min = -3.4107 x_max = 3.709 x_num = 300 -x_grid = UCGrid((x_min, x_max, x_num)) +x_grid = [(x_min, x_max, x_num)] log_pref = LogUtility(β=β, ψ=ψ) S = len(Π) bounds_v = np.vstack([np.zeros(2 * S), np.hstack([1 - g, np.ones(S)]) ]).T V = np.zeros((len(Π), x_num)) -V[:] = -(nodes(x_grid).T + x_max) ** 2 / 14 +V[:] = -(get_grid_nodes(x_grid[0]) + x_max) ** 2 / 14 σ_v_star = 1 - np.full((S, x_num, S * 2), 0.55) @@ -1026,4 +1093,4 @@ lump-sum transfers to the private sector. problem, there exists another realization $\tilde s^t$ with the same history up until the previous period, i.e., $\tilde s^{t-1}= s^{t-1}$, but where the multiplier on constraint {eq}`AMSS_46` takes a positive value, so -$\gamma_t(\tilde s^t)>0$. +$\gamma_t(\tilde s^t)>0$. \ No newline at end of file From a53a6462ad2d35314d5ba504f50c8f1b1596d6ad Mon Sep 17 00:00:00 2001 From: mmcky Date: Mon, 22 Sep 2025 13:15:45 +1000 Subject: [PATCH 10/12] TST: fix on quantecon-book-theme --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 80b64d6a..1b90380d 100644 --- a/environment.yml +++ b/environment.yml @@ -7,7 +7,7 @@ dependencies: - pip - pip: - jupyter-book==1.0.4post1 - - quantecon-book-theme==0.8.3 + - git+https://github.com/QuantEcon/quantecon-book-theme@kp992-patch-1 - sphinx-tojupyter==0.3.1 - sphinxext-rediraffe==0.2.7 - sphinx-exercise==1.0.1 From ec50e4cb064bde36f70c24796061629f9ff8dd11 Mon Sep 17 00:00:00 2001 From: mmcky Date: Mon, 22 Sep 2025 17:45:41 +1000 Subject: [PATCH 11/12] update quantecon-book-theme=0.9.2 --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 1b90380d..76faa295 100644 --- a/environment.yml +++ b/environment.yml @@ -7,7 +7,7 @@ dependencies: - pip - pip: - jupyter-book==1.0.4post1 - - git+https://github.com/QuantEcon/quantecon-book-theme@kp992-patch-1 + - quantecon-book-theme==0.9.2 - sphinx-tojupyter==0.3.1 - sphinxext-rediraffe==0.2.7 - sphinx-exercise==1.0.1 From 3af6f2872daa22a61b557a7e0acd7bbc8d9a971f Mon Sep 17 00:00:00 2001 From: mmcky Date: Mon, 22 Sep 2025 17:56:22 +1000 Subject: [PATCH 12/12] restore build cache: --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4431941f..f9c8d1ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,13 +36,13 @@ jobs: - name: Display Pip Versions shell: bash -l {0} run: pip list - # - name: Download "build" folder (cache) - # uses: dawidd6/action-download-artifact@v11 - # with: - # workflow: cache.yml - # branch: main - # name: build-cache - # path: _build + - name: Download "build" folder (cache) + uses: dawidd6/action-download-artifact@v11 + with: + workflow: cache.yml + branch: main + name: build-cache + path: _build # Build Assets (Download Notebooks and PDF via LaTeX) - name: Build PDF from LaTeX shell: bash -l {0}