robust_permanent_income#

  • Series: lecture-python.myst

  • File: lectures/robust_permanent_income.md

  • Audit date: 2026-08-26

  • Corpus snapshot: e25fdf2345

  • Categories audited: writing, math, code, figures, references, links, admonitions (JAX out of scope)

  • Overall score: 7.9 / 10

  • Priority: HIGH

Score breakdown#

Category

Score

One-line note

Writing

3.5/10

qe-writing-001 ×5; qe-writing-002 ×5; qe-writing-005 ×4, +2 more.

Math

9/10

qe-math-009 ×6.

Code

6.5/10

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

JAX

out of scope

JAX rules target lecture-jax.

Figures

6/10

qe-fig-003 ×5; qe-fig-005 ×1; qe-fig-001 ×3.

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-code-002] — Use Unicode symbols for Greek letters in code. Count: 5. Lines: 674, 675, 676. Example: spelled-out mu.

  • [qe-fig-003] — No matplotlib embedded titles. Count: 5. Lines: 179, 193, 516, 832, 838. Example: .set_title.

  • [qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 6. Lines: 273, 300, 282, 598, 601, 696. Example: the calligraphic alphabet is stretched past the point where it helps, and four plain letters carry two meanings each. Decoration: \(\mathcal{D}(\Omega)\) (696, used 699, 704, 708, 719) is an ordinary matrix-valued function that the code writes as plain D (741); \(\mathcal{M}^{c}_t\) (601, 605, 616) is a scalar marginal utility, not an operator; and \(\mathcal{S}_{t,\tau}\) is defined at 598 and never used again anywhere in the file. (\(\mathcal{R}_t\) and \(\mathcal{T}_t\) are the paper’s own operator names and are not free choices.) Collisions: (1) \(R\) is the state cost matrix in \(u = -i^\top Q i - x^\top R x\) (91, 94, 704, 708) and the gross physical return \(R \equiv \delta_k + \gamma\) (273, 276, 289, 468) - the code has to invent Rf (367) alongside R = np.eye(2) (766) to keep them apart, so the implementation disambiguates what the algebra does not. (2) \(v\) is the loading vector of the marginal-utility martingale, \(\theta^2 \equiv v^\top v\) (300, 303, 312), and the malevolent player’s distortion \(\{v_t\}\), \(\hat v_t^\top \hat v_t\) (209, 214, 222, 637, 650, 713) - both appear as squared norms, 20 lines apart in the same argument at 644-650. (3) \(U\) is continuation utility \(U_t\), \(U^e_t\) (99, 552) and the selector matrices \(U_d\), \(U_b\) in \(d_t = U_d z_t\) (282). (4) \(\mu\) is the conditional mean of continuation utility (126-141), the marginal utilities \(\mu_{c,t}\), \(\mu_{s,t}\) (294, 300) and the shock means \(\mu_b = 32\), \(\mu_d\) (426, 438); \(N\) is the LQ cross-product matrix (704, 708, 722) and the normal distribution \(N(\mu, s^2)\) (126, 853); and \(s\) is both the conditional standard deviation of \(U_{t+1}\) (126) and the service stream \(s_t\) (239, 250, 727).

  • [qe-writing-001] — Use one sentence per paragraph. Count: 5. Lines: 126, 332, 339, 598, 693. Example: 2 sentences in one paragraph.

  • [qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 5. Lines: 141, 409, 868, 42, 994. Example: the prose is disciplined - one sentence per paragraph almost everywhere, and only five two-sentence blocks in 1000 lines - so the cost here is repetition, not verbosity. The \(\sigma/2\)-versus-\(\sigma/4\) point is made three times in seventy lines: 139-141 (“lies halfway between the original mean \(\mu\) and this worst-case mean”), the {note} at 143-148 (“The worst-case mean … shifts by \(\frac{\sigma}{2}s^2\), while the operator value … shifts by \(\frac{\sigma}{4}s^2\)”) and again at 199-201 (“sits halfway between it and the reference mean \(\mu\)”). Worse, the exercise-1 solution (868-891) reproduces line for line the derivation the main text already gives in full at 126-133 - same moment generating function, same \(a = \sigma/2\), same three-step chain - so the note at 150 promising “A self-contained derivation … is requested in {ref}`hst_ex1`” asks the reader to redo what they have just read. §“Related lectures” (994-1002) restates 62 and 64. On sentence length, 409 is 51 words with a role reference and a display-math reference inside it, and 42 is a 40-word question.

Medium severity#

  • [qe-code-001] (reviewer) — Follow PEP8 unless closer to mathematical notation. Count: 4. Lines: 384, 745, 675, 929. Example: flake8 at PEP8’s own 79-character limit finds nothing but two items in the whole file, which is a genuinely good result: 384 assigns a lambda (f = lambda b: b * Rf * ζ_hat(b, σ, θ2) - 1, E731 - a def f(b): is the PEP8 form, and here it would also let the root-finder’s bracket be documented) and 745 over-indents a continuation line (E127: + β * Acl.T @ D @ Acl) sits four columns past the visual indent opened at 744). The E226 hits (183, 491, 501, 502) are tight multiplication inside lower-priority expressions, which PEP8 endorses and which keeps Rf**(-np.arange(H)) close to the algebra, so they are not findings. Two naming items a linter cannot see: 674-676 call the worst-case likelihood ratio \(m^u\) log_mu / mu in a file that uses Unicode μ for the conditional mean twelve lines earlier (162-168), so one file has μ meaning \(\mu\) and mu meaning \(m^u\) - rename to m_u (this is also what makes the three qe-code-002 hits false positives, see the actions); and 929’s for θ2 in [0.01, 0.02] rebinds the module-level θ2 = 0.01 set at 368, so any later cell that relies on the global sees 0.02.

  • [qe-fig-001] — Do not set figure size unless necessary. Count: 3. Lines: 173, 509, 821. Example: figsize=.

  • [qe-writing-003] (reviewer) — Maintain logical flow. Count: 4. Lines: 426, 588, 552, 755. Example: (1) 426 says “recall from the discussion of {eq}`eq:hst_budget` that the level of \(b_t\) does not affect the decision rules, only prices” - but that discussion (269-276) is three lines about \(R \equiv \delta_k + \gamma\) and says nothing whatever about \(b_t\); the fact being recalled was never stated. (2) 588 closes the distorted transition law with “with \(\hat A\) given by {eq}`eq:hst_D`-style risk corrections”, which is both a forward reference (eq:hst_D is introduced at 697, a section later) and a non-definition: \(\hat A\) is used in {eq}`eq:hst_pricing_lom` and never given. (3) The asset-pricing section (536-687) leans on \(\Omega\) at 552, 585 and 590 - “\(\Omega\) is negative semidefinite”, \((I - \sigma C^\top \Omega C)^{-1}\) - when all the reader has been told about \(\Omega\) is the half-sentence at 233; the Riccati iteration that produces it arrives 150 lines later at 693-711, so the pricing argument runs on an object whose construction has not been given. (4) The lecture estimates the HST economy (405-459: a parameter table, \(\phi_1 = 0.998\), \(\lambda = 2.443\)) and then never solves it: the regulator section switches at 755-767 to a made-up \(2\times2\) system (A = [[0.9, 0], [0, 0.8]], Q = [[1.0]], R = np.eye(2), β = 0.95) with no bridge back, so the worst-case distortions plotted at 821-841 belong to a different economy from the one whose \(\sigma\)-\(\hat\beta\) locus was computed at 359-396.

  • [qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 4. Lines: 417, 418, 428, 1000. Example: bold-for-definitions is followed unusually well here - robust (44), observationally equivalent (51), market price of risk (52), Knightian uncertainty (58), risk-sensitivity operator (102), exponential tilting (139), two-player zero-sum game (207), habit persistence (259), risk-free rate (276), martingale (291), annuity value (465), shadow prices (544), twisting operator (562), distorted transition law (580), stochastic discount factor (605), likelihood ratio (631), risk adjustment (693) - and italic is reserved for emphasis throughout. The exceptions are four terms defined in italic: the data definitions at 417-418 (“* Consumption is measured as nondurables plus services.” / “* Investment is measured as durables plus gross private investment.”) are definitions, not emphasis; 428 introduces the persistent and transitory components in italic and the file then uses those two words as the component names eleven times (440, 459, 473, 497-498, 511-512, 526); and 1000 introduces the price of model uncertainty in italic. Also worth a look: 590 sets three consecutive spans in italic including the connective, *pessimistically shifted conditional mean* *and* an *inflated conditional variance*, which reads as italicising a whole clause.

  • [qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 536, 663, 594. Example: the title is “Robust Permanent Income and Pricing” and the pricing half has no figure at all. §“Asset pricing and the market price of risk” (536-687) is 150 lines of algebra whose only output is a hand-formatted print table (678-682, f"{'|v_hat|':>10}{'E[m^u]':>12}...") - and the picture that belongs there, the exact market price of risk \([\exp(\hat v^\top \hat v) - 1]^{1/2}\) against its linear approximation \(|\hat v|\), exists in the file but is hidden in the exercise-3 dropdown at 962-982. Second, the punchline at 657-659 (“a conditional-mean misspecification of \(x\%\) … raises the market price of risk by roughly \(x/100\)”) is exactly a one-line plot and is instead left as three printed rows for scale in [0.05, 0.10, 0.20] (679). Third, the multi-period pricing operator \(\mathcal{S}_{t,\tau} = \mathcal{T}_t \cdots \mathcal{T}_{t+\tau-1}\) (594-627) has neither figure nor code, where a term structure of prices under \(\sigma = 0\) against \(\sigma < 0\) would show the two distortions of 590 (“a pessimistically shifted conditional mean and an inflated conditional variance”) accumulating with horizon.

Low severity#

  • [qe-fig-005] — Descriptive figure names for cross-referencing. Count: 1. Lines: 962. Example: code-cell figure without mystnb figure metadata.

Strengths#

  • The risk-sensitive solver is validated against an independent implementation rather than trusted: 770-771 calls qe.LQ(Q, R, A, B, C=C, beta=β).stationary_values(), 774 solves the same problem with solve_rslq at \(\sigma = 0\), and 776-778 prints both feedback rules and max |difference| - the \(\sigma = 0\) limit predicted at 719 (\(\mathcal{D}(\Omega) = \Omega\), \(G = 0\)) is therefore checked, not asserted.

  • The paper’s punchline identity is verified by Monte Carlo: 663-683 draws two million standard normals, forms the likelihood ratio \(m^u\) from its log (log_mu = w @ v_hat - 0.5 * v_hat @ v_hat, 674) and prints \(\mathbb{E}[m^u]\), \(\mathrm{std}(m^u)\) and \(|\hat v|\) side by side for three distortion sizes, so 685-687 (“A 10% distortion delivers a market price of risk near 0.10”) is a measured statement.

  • The Gaussian certainty equivalent is decomposed rather than just stated: 137-141 separates the worst-case mean shift \(\frac{\sigma}{2}s^2\) from the operator value \(\mu + \frac{\sigma}{4}s^2\) and identifies the difference as the relative-entropy penalty, and the right panel of the first figure (182-194) draws both - the tilted density dashed and the operator value as a dotted vertical line - so the halfway property is visible.

  • The {note} at 143-151 pre-empts the exact confusion the two coefficients invite (“Both are correct; the smaller shift of \(\mathcal{R}_t\) reflects the entropy cost the malevolent player pays for the distortion”), which is the right admonition in the right place.

  • The economics of observational equivalence is given in one sentence that names both offsetting forces (337): “Increasing the preference for robustness stimulates a precautionary motive for saving; lowering \(\beta\) makes saving less attractive; along a particular locus the two effects exactly cancel” - and the locus is then computed by root-finding \(\hat\beta R \hat\zeta(\hat\beta) = 1\) (380-388) rather than asserted.

  • The impulse-response cell does the permanent-income arithmetic in the open: ar2_irf (484-492) builds the AR(2) responses from the estimated roots, dc_p = (1 - 1/Rf) * np.sum(disc * ψ_p) (501) is {eq}`eq:hst_pi_mpc` transcribed, and 504-507 prints consumption’s response as a percentage of impact for the persistent and transitory shocks - which is the contrast 524-526 claims.

  • The equations that carry the argument are labelled and genuinely reused: {eq}`eq:hst_R` at 130, 150, 693 and 859; {eq}`eq:hst_mpr` at 661, 685, 846 and 946; {eq}`eq:hst_Omega_scalar` at 355, 903 and 936; {eq}`eq:hst_R_gauss` at 150 and 853; {eq}`eq:hst_mu_rw` at 315 and 934.

  • All five parameter estimates that the code later uses are traceable to the table at 448-457: the AR roots 0.998, 0.704, 0.813, 0.189 and the shock scales 0.108, 0.155 appear as literals at 497-498 with # persistent endowment d* / # transitory endowment d_hat comments naming which row each came from.

  • The three exercises each attach to a specific labelled equation and add something the text does not: hst_ex1 to {eq}`eq:hst_R_gauss` (853), hst_ex2 to the discriminant of {eq}`eq:hst_Omega_scalar` and the endpoint \(\underline\sigma\) (899-903), and hst_ex3 to {eq}`eq:hst_mpr`, where 985-989 draws the correct conclusion about where the linear approximation fails.

  • All four main-text figure cells carry mystnb caption and name metadata (156-160, 360-364, 478-482, 803-807), which is why qe-fig-005 reports only the exercise-3 cell at 962.