[inventory_dynamics] Update code to JAX and latest style guide#623
Conversation
|
📖 Netlify Preview Ready! Preview URL: https://pr-623--sunny-cactus-210e3e.netlify.app (2b7c4fe) 📚 Changed Lecture Pages: inventory_dynamics |
There was a problem hiding this comment.
Pull Request Overview
This PR updates the inventory dynamics lecture to use JAX instead of numba and numpy, modernizing the codebase and improving performance. The changes align with the latest style guide and fix minor formatting issues.
Key changes:
- Replaces numba-based
Firmclass with JAX-based functions usingNamedTuple - Converts simulation functions to use JAX's functional programming paradigm with
jax.lax.scanandjax.vmap - Updates all numpy operations to JAX numpy equivalents
|
📖 Netlify Preview Ready! Preview URL: https://pr-623--sunny-cactus-210e3e.netlify.app (54d1cac) 📚 Changed Lecture Pages: inventory_dynamics |
HumphreyYang
left a comment
There was a problem hiding this comment.
Many thanks @kp992! These are really nice! Just two very minor observations.
I think we should remove the grid in the figures to maintain consistency in the style:
for ax in axes:
ax.grid(alpha=0.4)
| # Generate independent random keys for each firm | ||
| firm_keys = random.split(key, (num_firms, sim_length)) | ||
| # Run simulation for all firms | ||
| restock_indicators = vectorized_simulate(firm_keys) | ||
| # Compute frequency (fraction of firms that restocked > 1 times) | ||
| frequency = jnp.mean(restock_indicators) | ||
| return frequency |
There was a problem hiding this comment.
I think it might look better if we add some spaces in between:
| # Generate independent random keys for each firm | |
| firm_keys = random.split(key, (num_firms, sim_length)) | |
| # Run simulation for all firms | |
| restock_indicators = vectorized_simulate(firm_keys) | |
| # Compute frequency (fraction of firms that restocked > 1 times) | |
| frequency = jnp.mean(restock_indicators) | |
| return frequency | |
| # Generate independent random keys for each firm | |
| firm_keys = random.split(key, (num_firms, sim_length)) | |
| # Run simulation for all firms | |
| restock_indicators = vectorized_simulate(firm_keys) | |
| # Compute frequency (fraction of firms that restocked > 1 times) | |
| frequency = jnp.mean(restock_indicators) | |
| return frequency |
|
🤖 Status note for a future session — from a maintainer investigation on 2026-07-08 into why open-PR previews 404. Context only, not instructions. Netlify preview: https://pr-623--sunny-cactus-210e3e.netlify.app/ currently returns 404. Why previews are down (repo-wide findings)1. This branch is stale — 174 commits behind 2. The arviz failure was a red herring — do NOT pin arviz or rewrite plotting. A 2026-07-07 rebuild also failed in Recommended first step for this PRUpdate this branch to This PR touches: |
📖 Netlify Preview Ready!Preview URL: https://pr-623--sunny-cactus-210e3e.netlify.app Commit: 📚 Changed LecturesBuild Info
|
|
Thanks @jstac for updating this. |
Convert the lecture from Numba (jitclass / @jit(parallel=True)) to JAX: - Replace the `Firm` jitclass with a `NamedTuple` and pure update functions. - Drive the time loop with `jax.lax.fori_loop`, which reads like the Python for-loops it replaces, and generate a fresh per-period shock with `random.fold_in(key, t)`. Each path therefore takes a single key, so the calling code (and every `jax.vmap`) needs only a one-dimensional array of keys instead of a 2-D key grid. - Use the modern typed-key API (`random.key`) rather than `random.PRNGKey`. - Vectorize the Monte Carlo draw loops with `jax.vmap` (including the 50,000-draw marginal-distribution cell, which as a Python loop over a jitted call ran ~28s on GPU; vmapped it is ~0.3s). - Use `sample.min()/.max()` in `plot_kde` instead of Python `min()/max()`, which iterate a JAX array element-by-element (~4s per call). - Add a note that `X.at[t].set(...)` is compiled to an in-place update under `jax.jit`, so the functional style costs no per-period allocation. Fix a correctness bug in the restock-frequency exercise: the previous `simulate_firm_restocks` captured the global `x_init` (= 50 by that point in the lecture) rather than taking it as an argument, so `compute_freq`'s `x_init=70` was silently ignored. The exercise asks for firms starting at X_0 = 70; the code answered for X_0 = 50, reporting ~0.96 instead of the correct ~0.45. Passing `x_init` explicitly through the vmapped call fixes it. Also: give the two exercise-solution helpers distinct names so they no longer shadow each other, and minor typo, terminology, and sentence-case title fixes. Whole-lecture execution drops from ~57s to ~5s on an RTX 4080. Co-authored-by: Humphrey Yang <39026988+HumphreyYang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
Many thanks @kp992 , merging. |
|
\translate-resync Triggering the first sync now that the zh-cn workflow is live (#979, merged today). This PR merged before the workflow existed, so its change to |
✅ Translation sync completed (zh-cn)Target repo: QuantEcon/lecture-python.zh-cn
|
This PR: