mccall_persist_trans#
Series: lecture-python.myst
File:
lectures/mccall_persist_trans.mdAudit date: 2026-08-26
Corpus snapshot:
e25fdf2345Categories audited: writing, math, code, figures, references, links, admonitions (JAX out of scope)
Overall score: 8.2 / 10
Priority: LOW
Score breakdown#
Category |
Score |
One-line note |
|---|---|---|
Writing |
6.5/10 |
|
Math |
6/10 |
|
Code |
7/10 |
|
JAX |
out of scope |
JAX rules target |
Figures |
8/10 |
|
References |
10/10 |
no mechanical violations detected. |
Links |
10/10 |
no mechanical violations detected. |
Admonitions |
10/10 |
no mechanical violations detected. |
Issues#
Critical#
None found.
High severity#
[qe-math-010 (proposed)] — Blackboard \mathbb{P}, \mathbb{E}, \mathbb{V} with braces. Count: 5. Lines: 100, 104, 116, 129, 136. Example: missing braces:
\mathbb E.
Medium severity#
[qe-code-001] (reviewer) — Follow PEP8 unless closer to mathematical notation. Count: 4. Lines: 360, 271, 185, 318. Example:
draw_durationcomputes the unemployment duration twice and returns the wrong one. 360 bindsτ = jnp.where(accept, t, t_max)and never reads it; the function instead returnsjnp.where(unemployed_final, t_max, t_final)at 376, wheret_finalis the post-incrementt + 1produced by the accepting iteration (t_new = t + 1at 365, returned at 368). So a worker who accepts the very first offer is recorded as unemployed for 1 period rather than 0, and every number behind the two duration figures (418, 457) carries that offset. The discardedτis precisely the expression that would have given 0, so the dead line documents the intended semantics and the live one contradicts it - one of the two has to go. Second,compute_fixed_pointunpacks three values fromjax.lax.while_loopat 271-273 and uses one:iterationsandfinal_errorare dropped, so theqe.Timer()block at 283 reports how long the solve took while the iteration count it computed is thrown away. Third, eleven lines carry trailing whitespace (185, 191, 194, 238, 250, 255, 259, 265, 269, 274, 318). Fourth, the continuation at 319 is indented twelve spaces past theax.plot(it continues (E127) and 195 falls one space short of aligning with the open parenthesis on 194;iis bound and never used in both loops (407, 448); and 448 rebinds the module-levelβas a loop variable.[qe-code-002] — Use Unicode symbols for Greek letters in code. Count: 3. Lines: 446, 448, 457. Example: spelled-out
beta.[qe-fig-005] — Descriptive figure names for cross-referencing. Count: 4. Lines: 289, 309, 416, 455. Example: code-cell figure without mystnb figure metadata.
[qe-fig-008] — Use lw=2 for line charts. Count: 4. Lines: 293, 318, 418, 457. Example: plot() without lw=.
[qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 3. Lines: 80, 185, 97. Example: 80 uses a distributional symbol for a deterministic identity: \(Y_t \sim \exp(\mu + s \zeta_t)\), where the right-hand side is a function of the standard normal \(\zeta_t\) rather than the name of a distribution. The code writes the same relation as an equality,
y_next = jnp.exp(μ + s * e2)(234), and the neighbouring half of the same display uses=for the AR(1). The two correct forms are \(Y_t = \exp(\mu + s\zeta_t)\) or \(\log Y_t \sim N(\mu, s^2)\); as written it reads as though \(\exp\) were a distribution family. Second, the same quantity is mislabelled in theModeldefinition: 185’s comment callssthe “transient shock log variance” when 80 makes it the log standard deviation - the log variance is \(s^2\) - and the defaults=1.0makes the two numerically identical, which is why the error survives; 184-185 also say “transient” where the prose says “transitory” throughout (41, 47, 49, 54, 87). Third, the model is stated in \((W_t, Y_t, Z_t)\) at 74-87 and solved in \((w, z, w', z')\) from 97 onward with no sentence bridging the two, so \(w\) makes its first appearance inside \(v^*(w, z)\) having never been introduced.[qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 2. Lines: 47, 41. Example: 47 is a 43-word sentence carrying the lecture’s entire modelling choice in one breath, with the contrast to the previous lecture parenthesised at the end: “Here we take a different approach: we model wage dynamics through an AR(1) process for the persistent component plus a transitory shock, while returning to the assumption that jobs are permanent (as in the
{doc}`baseline model <mccall_model>`).” The Overview also links to the same document three times in seven lines under two different labels - “McCall job search model” at 41, “baseline model” at 43 and again at 47 - so the reader meets three links that all go to one place. And the third item of the bulleted list at 49-52, “Simple enough to analyze while capturing key features of wage dynamics”, asserts something the reader has no way to check and that the lecture never returns to.[qe-writing-003] (reviewer) — Maintain logical flow. Count: 2. Lines: 49, 110. Example: the lecture is named after a decomposition it never examines. The title (22), the opening sentence (41, “decomposing wage offers into persistent and transitory components”) and the closing promise of the Overview (54, “we can focus on understanding how persistent and transitory wage shocks affect search behavior and reservation wages”) all put the two components at the centre. There are four figures - reservation wage against \(z\) (289-298), the same for three values of \(c\) (309-323), mean duration against \(c\) (416-421), mean duration against \(\beta\) (455-460) - and every one of them varies \(c\) or \(\beta\). Neither is new here: both are studied in
{doc}`mccall_model`and{doc}`mccall_fitted_vfi`. The parameters that are the subject - \(s\), the transitory scale, and \(\rho\) and \(\sigma\), the persistence and its volatility - appear only as defaults at 194 and are never changed, so a reader finishes the lecture having seen nothing that the previous two lectures in the series did not already show. Second, 110 motivates the lecture’s main technical device on an unmeasured claim: the reduction to a one-dimensional state “greatly accelerates computation”. Theqe.Timer()at 283 times the reduced problem and there is no two-dimensional baseline anywhere, so the speed-up is asserted and the reader has nothing to compare.[qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 2. Lines: 74, 416. Example: the wage process is never drawn. \(W_t = \exp(Z_t) + Y_t\) (74) is the object the lecture is named after, and one panel showing a simulated \(\{W_t\}\) path with \(\exp(Z_t)\) and \(Y_t\) plotted separately beneath it would make the persistent-transitory split visible in about five lines -
create_job_search_modelalready generates the shock draws at 207, anddraw_durationalready simulates the state at 354-364. As it stands the reader is told which component is which at 87 and never shown. Second, the two duration figures (416-421, 455-460) report Monte Carlo averages of 10,000 simulated durations (409, 450) as single unadorned lines, with no measure of simulation error and no check on the censoring:draw_durationreturnst_max = 10_000whenever the loop exhausts its budget (337, 376), which would silently inflate a mean, and nothing anywhere reports how often that happens. A shaded band across replications, or a printed count of censored draws, would tell the reader how much of the curvature in those two curves to believe.
Low severity#
None found.
Strengths#
Emphasis is used exactly once and used correctly: the lecture contains two emphasis spans in 466 lines - persistent and transitory at 41 - both bold, both marking the definition of a central term, with no bolded emphasis and no italicised definition anywhere in the file.
The dimensionality reduction is built in four one-line steps that a reader can follow without re-reading: define the continuation value \(f^*\) (116), rewrite the Bellman equation in terms of it (122), substitute to get a functional equation in \(z\) alone (129), then name the operator \(Q\) (136) and state the contraction argument that makes iterating it legitimate (139-144).
The reservation wage is derived rather than produced: 146-149 gives the stopping inequality, 152 fixes \(u = \ln\), 154-161 solves the equality for \(\bar w(z) = \exp(f^*(z)(1-\beta))\) under the label
corr_mcm_barw, and the code cell at 287-290 cites that label and implements exactly that line.Both comparative statics are explained by mechanism before they are plotted, not merely by direction: 301-304 gives the option-value argument for why the reservation wage rises in \(z\) (“a higher state leads the agent to predict higher future wages, increasing the option value of waiting”), and 424-426 does the same for duration in \(c\).
The lecture places itself precisely in its series and says what it drops as well as what it adds: 43 names the baseline’s IID assumption and calls it unrealistic, 45 says Job Search III added correlation and job separation, 47 says this lecture keeps the correlation and returns to permanent jobs, and 56 names the algorithm and the lecture it comes from.
The code mirrors the algebra closely enough to audit:
Q(215) is named after the operator of{eq}136, its docstring lists its arguments in the lecture’s own terms (216-223), andcompute_expectation/evaluate_shock(229-240) reproduce the nested structure of that display with inline comments tyinggo_val,y_nextandw_nextback to \(f(z')\), \(y'\) and \(w'\).The Monte Carlo draws are generated once and stored in the model (207,
e_draws) rather than redrawn inside the operator, which is what makes successive applications of \(Q\) contract to a fixed point instead of wandering - andcreate_job_search_modelbuilds the \(z\) grid from the AR(1)’s own stationary moments (200-204) rather than from hard-coded bounds.Timing uses the
quanteconTimercontext manager at 283 rather than a%%timecell magic, which is whatqe-code-004asks for.
Recommended actions#
Fix the duration off-by-one in
draw_duration: the returnedt_final(376) is one larger than theτcomputed and discarded at 360, so accepting the first offer is recorded as one period of unemployment. Decide which convention is meant, delete the other line, and re-generate the two duration figures (416, 455).Add a figure that varies the parameters the lecture is about. Nothing in the file changes \(s\), \(\rho\) or \(\sigma\); a panel of \(\bar w(z)\) for two or three values of \(s\), and another for two values of \(\rho\), would show what the transitory component and the persistence actually do - and would distinguish this lecture from
{doc}`mccall_fitted_vfi`, which already covers \(c\) and \(\beta\).Change \(\sim\) to \(=\) at 80 (or restate as \(\log Y_t \sim N(\mu, s^2)\)): as written \(\exp(\mu + s\zeta_t)\) is presented as a distribution, while the code at 234 treats it as an equality. Then fix the comment at 185, which calls
sthe “log variance” when it is the log standard deviation, and settle on “transitory” in the comments at 184-185 to match the prose.Plot one simulated wage path with \(\exp(Z_t)\) and \(Y_t\) drawn separately, so the decomposition named in the title is visible somewhere in the lecture.
Report the Monte Carlo uncertainty in the duration figures and check the censoring:
draw_durationreturnst_max = 10_000on exhaustion (376), which would inflate a mean silently, and neither figure indicates how much of its shape is simulation noise across the 10,000 replications.Either measure the speed-up claimed at 110 or soften the claim - as written the lecture’s main technical device is motivated by an acceleration nothing in the file demonstrates.
Sweep the mechanical items: add
mystnb: figure: caption/namemetadata to all four figure cells (289, 309, 416, 455) andlw=2to the four line plots (293, 318, 418, 457); add braces to the five\mathbb Eoperators (100, 104, 116, 129, 136) forqe-math-010(proposed); strip the trailing whitespace on eleven lines (185, 191, 194, 238, 250, 255, 259, 265, 269, 274, 318); fix the continuation indents at 195 and 319; drop the unused loop indexiat 407 and 448; and use the three valuesiterations,final_errorunpacked at 271 or stop unpacking them.