[career] Convert to JAX and content checks#617
Conversation
|
📖 Netlify Preview Ready! Preview URL: https://pr-617--sunny-cactus-210e3e.netlify.app (6232e18) 📚 Changed Lecture Pages: career |
|
📖 Netlify Preview Ready! Preview URL: https://pr-617--sunny-cactus-210e3e.netlify.app (1ededf4) 📚 Changed Lecture Pages: career |
|
📖 Netlify Preview Ready! Preview URL: https://pr-617--sunny-cactus-210e3e.netlify.app (5842d1e) 📚 Changed Lecture Pages: career |
|
📖 Netlify Preview Ready! Preview URL: https://pr-617--sunny-cactus-210e3e.netlify.app (8707aab) 📚 Changed Lecture Pages: career |
|
🤖 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-617--sunny-cactus-210e3e.netlify.app/ currently returns 404. Why previews are down (repo-wide findings)1. This branch is stale — 178 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: |
|
Thanks @HumphreyYang — this is a nice piece of work, and a good example of a lecture where JAX genuinely earns its place. Apologies it has sat for a while. I checked the numerics locally against the original Numba implementation, and the core of the conversion is solid:
A few things I'd like to sort out before merging, then some smaller points that are entirely for your consideration. Requests1. -To generate the draws from the distributions $F$ and $G$, use `quantecon.random.draw()`.
+To generate the draws from the distributions $F$ and $G$, use `quantecon.jr.draw()`.This looks like a 2. def gen_probs(n, a, b):
probs = jnp.zeros(n+1) # unused
k_vals = jnp.arange(n+1) # unused
probs = jnp.array([binom(n, k) * beta(k + a, n - k + b) / beta(a, b) for k in range(n+1)])
return probsThe first two lines are dead, and the comprehension is still the original Python loop calling SciPy — Two ways out, and I'd lean towards the first: Keep it in NumPy. This is a plotting cell with no performance requirement, and def gen_probs(n, a, b):
k = np.arange(n + 1)
return binom(n, k) * beta(k + a, n - k + b) / beta(a, b)Or do it properly in JAX, if you'd rather the lecture be uniform. Two catches there, both worth knowing:
from jax.scipy.special import betaln, gammaln
def gen_probs(n, a, b):
k = jnp.arange(n + 1)
log_p = (gammaln(n + 1.) - gammaln(k + 1.) - gammaln(n - k + 1.)
+ betaln(k + a, n - k + b) - betaln(a, b))
return jnp.exp(log_p)I checked this against SciPy across 3. Could you add a line explaining the CPU pin? jax.config.update('jax_platform_name', 'cpu') # Set JAX to use CPUA reader will reasonably wonder why a JAX lecture disables the accelerator. If the reason is that the 50×50 grid is small enough that dispatch overhead dominates, that's a genuinely instructive point and worth a sentence. It's also worth a sanity check that the pin is still needed — the old code was For consideration onlyNone of these are blockers.
ContextWe've just consolidated our JAX guidance into a single page — QuantEcon/QuantEcon.manual#113 — covering when a lecture should convert, an idiomatic style guide, and a Numba migration section. Several points above (the Thanks again — the Bellman rewrite in particular is a clear improvement on what was there. |
|
many thanks @HumphreyYang , above comment written by claude under my guidance. if you can attend to the above i'll try to get this merged quickly |
|
Thanks again @HumphreyYang — closing this in favour of #985, but as with #618 I want to be clear that this PR did the substantive work. Reviewing it opened up a broader question about when a lecture should use JAX at all, and working through that ended up reshaping our guidance rather than just this file. By the end there were enough changes that a fresh branch off Your work is the starting point for #985 and much of it survives:
You're credited as co-author on the commit. The main structural change is writing the three options as a scalar function of one state, in the order they appear in the text, then applying def _B(v, cw, i, j):
stay_put = cw.θ[i] + cw.ϵ[j] + cw.β * v[i, j] # I
new_job = cw.θ[i] + cw.G_mean + cw.β * v[i, :] @ cw.G_probs # II
new_life = cw.G_mean + cw.F_mean + cw.β * cw.F_probs @ v @ cw.G_probs # III
return jnp.array([stay_put, new_job, new_life])
Three corrections to my review above, since they shouldn't stand.
The CPU pin — I asked the wrong question. I asked you to justify Tie-breaking. I flagged that For what it's worth, #985 validates cleanly against the Numba implementation on Please do review #985, and push back if you think the nested- |
Rewrites the career choice lecture to use JAX on the GPU, replacing the CareerWorkerProblem class, operator_factory, and the nested prange loops. The three options in the Bellman equation are written as a scalar function _B of one state, returning them in the order they appear in the text. Two applications of jax.vmap then evaluate it at every state, and T and get_greedy become the max and the argmax of that same array -- which removes the duplicated grid-search code the two functions previously carried, and makes the 1/2/3 action encoding explicit rather than buried in a branch. Other changes: - Model primitives move to a NamedTuple with a factory function. - solve_model uses a bounded jax.lax.while_loop and returns the iteration count and final error so callers can check convergence. - Deletes gen_probs. It computed the beta-binomial pmf by hand, and is bit-for-bit identical to BetaBinomial(n, a, b).pdf() from quantecon, which the lecture already imports and uses for F_probs and G_probs. This also drops the scipy.special import. - Exercise 1 simulates paths with jax.lax.scan. - Exercise 2 writes one first-passage simulation as a bounded jax.lax.while_loop and runs 25,000 of them at once under jax.vmap. This is the part of the lecture that genuinely benefits from a GPU. - Adds the shared GPU admonition. Validated against the Numba implementation on main: value functions agree to 1.8e-04 (relative 9.0e-07), and the greedy policy is identical at all 2500 grid cells. The median first passage time is 7 at the default parameters and 14 at β=0.99, matching the values stated in the text. All fourteen code cells execute on an RTX 4080 and the five figures reproduce the reference images. Supersedes #617. Co-authored-by: Humphrey Yang <u6474961@anu.edu.au> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This PR updates
careerby converting the code to JAX.Some changes are done to vectorize the
forloops in addition to some old change rules in the previous lectures.For the exercises, draws from$F$ and $G$ are generated with inverse transform sampling (
jax.random.uniform+jnp.searchsorted) rather thanqe.random.draw.