mccall_persist_trans#

  • Series: lecture-python.myst

  • File: lectures/mccall_persist_trans.md

  • Audit date: 2026-08-26

  • Corpus snapshot: e25fdf2345

  • Categories 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

qe-writing-003 ×2; qe-writing-002 ×2; qe-writing-007 ×2.

Math

6/10

qe-math-010 (proposed) ×5; qe-math-009 ×3.

Code

7/10

qe-code-002 ×3; qe-code-001 ×4.

JAX

out of scope

JAX rules target lecture-jax.

Figures

8/10

qe-fig-005 ×4; qe-fig-008 ×4.

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_duration computes the unemployment duration twice and returns the wrong one. 360 binds τ = jnp.where(accept, t, t_max) and never reads it; the function instead returns jnp.where(unemployed_final, t_max, t_final) at 376, where t_final is the post-increment t + 1 produced by the accepting iteration (t_new = t + 1 at 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_point unpacks three values from jax.lax.while_loop at 271-273 and uses one: iterations and final_error are dropped, so the qe.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 the ax.plot( it continues (E127) and 195 falls one space short of aligning with the open parenthesis on 194; i is 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 the Model definition: 185’s comment calls s the “transient shock log variance” when 80 makes it the log standard deviation - the log variance is \(s^2\) - and the default s=1.0 makes 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”. The qe.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_model already generates the shock draws at 207, and draw_duration already 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_duration returns t_max = 10_000 whenever 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), and compute_expectation / evaluate_shock (229-240) reproduce the nested structure of that display with inline comments tying go_val, y_next and w_next back 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 - and create_job_search_model builds the \(z\) grid from the AR(1)’s own stationary moments (200-204) rather than from hard-coded bounds.

  • Timing uses the quantecon Timer context manager at 283 rather than a %%time cell magic, which is what qe-code-004 asks for.