odu#
Series: lecture-dp
File:
lectures/odu.mdAudit date: 2026-08-26
Corpus snapshot:
c30490a2f4Categories audited: writing, math, code, figures, references, links, admonitions (JAX out of scope)
Overall score: 7.4 / 10
Priority: HIGH
Score breakdown#
Category |
Score |
One-line note |
|---|---|---|
Writing |
3/10 |
|
Math |
9/10 |
|
Code |
7.5/10 |
|
JAX |
out of scope |
JAX rules target |
Figures |
5/10 |
|
References |
9/10 |
|
Links |
8/10 |
|
Admonitions |
10/10 |
no mechanical violations detected. |
Issues#
Critical#
None found.
High severity#
[qe-code-001] (reviewer) — Follow PEP8 unless closer to mathematical notation. Count: 6. Lines: 371, 970, 799, 833, 311, 1009. Example: 371 opens the docstring of
get_greedywith four quotation marks,"""", so the docstring begins with a stray"character. 970-972 assign lambdas to names (l = lambda w: f(w) / g(w),obj = lambda w: l(w) - 1.) - E731, andlis the identifier PEP8 singles out as ambiguous (E741); the same pattern appears at 230-231, 302-303 and 793. 799 testsif e == False:whereif not e:is the PEP8 form (E712). 833 bindsp = np.random.uniform(0, 1)inside a jitted function whilepis the beta density defined at module scope (224) and called as a function at 913 - and 642 does the same again withdef ω_func(p, ω), sopis a density, a float and a probability grid point in one lecture. 311-312 draw the Monte Carlo integration nodes withnp.random.betaand no seed, as do 800, 912 and 833, so every figure in the lecture - and the convergence counts printed at 737 and 743 - change on each build. 1009 assigns the quiver artist toq, the symbol the model uses for the unknown density (94, 123, 184), and never uses it (F841). Also 441-442 are two blank lines before areturninside a function (E303), and 1002-1007 allocatesΔWwithnp.zerosandΔΠwithnp.emptybut fills onlyΔΠ, which is deliberate (the arrows are horizontal) and worth one comment.[qe-fig-001] — Do not set figure size unless necessary. Count: 7. Lines: 233, 451, 466, 754, 846, 981, 1041. Example: figsize=.
[qe-fig-005] — Descriptive figure names for cross-referencing. Count: 5. Lines: 222, 448, 462, 750, 782. Example: code-cell figure without mystnb figure metadata.
[qe-fig-008] — Use lw=2 for line charts. Count: 9. Lines: 756, 847, 993, 1022, 1023, 1043, 1044, 1050, 1051. Example: plot() without lw=.
[qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 5. Lines: 94, 265, 129, 27, 1159. Example: 94 and 133 write the wage sequence as
${W_t}$with the braces unescaped, so the set notation vanishes and the source renders as \(W_t\) - the same object is written correctly as$\{W_t\}$nowhere in the file. 265 is worse:$\mathbb 1{w\geq \bar w(\pi) }$has both an unbraced\mathbb 1and unescaped set braces, in the sentence that states the lecture’s central conjecture (‘Summary: We conjecture that the optimal policy is of the form …’), and line 113 writes the same indicator correctly as$\mathbf{1}\{w \geq \bar w\}$- so the file has two spellings of one symbol and the broken one is in the summary. Then five typos: ‘repectively’ (129), ‘the the distribution’ (149), ‘accpet_t’ (892), ‘currently employment’ (832), ‘unemployment compensation if low’ (1159), plus ‘priori’ for ‘prior’ twice (906, 1088). And 27 says ‘this lecture deploys the libraries:’ - plural, above a cell that installs one.[qe-writing-006] — Capitalize lecture titles properly. Count: 7. Lines: 70, 82, 116, 242, 484, 499, 1064. Example: H3 Title Case: ‘Model Features’ (Features).
[qe-writing-008] — Remove excessive whitespace between words. Count: 23. Lines: 42, 102, 129, 131, 139, 149, 260, 863, 944, 945, …. Example: 2 spaces.
Medium severity#
[qe-fig-003] — No matplotlib embedded titles. Count: 3. Lines: 850, 1047, 1054. Example: .set_title.
[qe-link-002] — Use doc links for cross-series references. Count: 4. Lines: 38, 41, 861, 1071. Example: raw link to python.quantecon.org.
[qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 3. Lines: 590, 561, 1066. Example: 590 introduces two symbols that are already taken, inside the contraction proof: ‘for real numbers \(a, b, c\) we always have’, where \(c\) is unemployment compensation throughout the lecture (218, 542, 567) and appears in
{eq}`odu_dq`twelve lines above, and \(b\) is the space of bounded functions declared at 558 (‘\(b[0,1]\) be the bounded real-valued functions on \([0,1]\)’). Renaming the three dummies in{eq}`odu_nt2`costs nothing and removes both collisions. 561 names the operator \(Q\) in a lecture whose unknown object is the density \(q\) (94, 123) and whose mixture density is \(q_\pi\) (184) - three closely related meanings on one letter, and the code then bindsqto a matplotlib artist at 1009. 1066, 1123, 1140, 1152 and 1178 write the distributional statements with a literal tilde outside math, ‘\(F\) ~ Beta(1, 1), \(G\) ~ Beta(3, 1.2)’, where$F \sim \operatorname{Beta}(1, 1)$is both correct and what 216-217 already uses for\operatorname{Beta}.[qe-math-015 (proposed)] (reviewer) — Lowercase for densities/PMFs, uppercase for CDFs. Count: 4. Lines: 1022, 216, 256, 1096. Example: the lecture states the convention correctly and then breaks it in both directions. 127-129: ‘The worker knows there are two possible distributions \(F\) and \(G\). / These two distributions have densities \(f\) and \(g\), repectively.’ - uppercase for the distributions, lowercase for the densities, exactly as the rule asks. But 1022-1023 plot
1 - beta.cdf(w_bar, F_a, F_b)and1 - beta.cdf(w_bar, G_a, G_b)under the legend labels'$f$'and'$g$', i.e. a quantity computed from the CDF carries the density letter - and the panel’s own y-axis label at 1027,$\mathbb{P}\{w > \overline{w}(\pi)\}$, says what the quantity is, so only the legend is wrong. In the other direction, the lowercase letters are used for the distributions throughout the prose: ‘\(f\) is \(\operatorname{Beta}(1, 1)\)’ and ‘\(g\) is \(\operatorname{Beta}(3, 1.2)\)’ (216-217, where \(f\) and \(g\) are densities and Beta(1,1) is a distribution), ‘\(f\) is a less attractive offer distribution than \(g\)’ (256), ‘if \(f\) generates successive wage offers’ and ‘if \(g\) generates wage offers’ (1096, 1099), ‘the worker believes the true distribution is \(g\)’ (1111) - while the Examples section switches back to the uppercase for the same objects (‘\(F\) ~ Beta(1, 1), \(G\) ~ Beta(3, 1.2)’ at 1066, 1123, 1140, 1152, 1178; ‘\(G\) has the same mean as \(F\)’ at 1125; ‘\(G\) is now an “inferior” distribution to \(F\)’ at 1128). After 129 the uppercase letters never appear again until line 872, so a reader has no way to know whether \(f\) and \(F\) are being distinguished on purpose.[qe-writing-003] (reviewer) — Maintain logical flow. Count: 4. Lines: 38, 478, 820, 21. Example: 38 links the previous lecture in this series by raw URL - ‘previously studied’ - while line 84 links the same lecture correctly, ‘
{doc}`in the baseline model <mccall_model>`’;mccall_model.mdis inlecture-dp, so the raw URL both breaks the convention and points out of the book. Second, the two internal cross-references are written as markdown links to MyST targets rather than as references: ‘looking forward’ (477-478) and ‘shown above’ (693). Without a leading#these resolve as relative document paths, not as the(looking-forward)=and(take-1-solution-by-vfi)=labels defined at 241 and 268, so both are broken - and neither link rule reports an intra-document reference of this shape. Third, the Appendix A simulation initialises everyone as employed -e = np.ones((N, T+1))at 820 - in a lecture whose entire subject is an unemployed worker deciding when to accept, with the only explanation being the garbled comment at 832 (‘# If agent is currently employment’); the reader has to infer that the simulation is of a steady-state labour market with separation rates, which 771-780 never says. Fourth, the H1 at 21 numbers this lecture ‘Job Search VIII’ while the otherwise near-identical copy inlecture-python.mystnumbers it ‘Job Search IX’ - one of the two series has it wrong.[qe-writing-004] — Avoid unnecessary capitalization in narrative text. Count: 1. Lines: 705. Example: mid-sentence ‘Distribution’.
[qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 2. Lines: 1128, 283. Example: the three bold spans in the file are all correct - reservation wage (114) and reservation wage functional equation (548) are definitions, and Summary: (264) is a label - and italic is never misused, because italic is never used at all in 1185 lines. The gap shows where a term is introduced in a special sense and gets typographic quotes instead: ‘\(G\) is now an “inferior” distribution to \(F\)’ (1128) and ‘\(G\) is even more “inferior”’ (1143), and the model’s own name in scare quotes at 283 (‘a given parameterization of the “offer distribution unknown” model’) and 705 (‘This code solves the “Offer Distribution Unknown” model’), the second of which is also the qe-writing-004 hit. Italic is the convention for the first pair; the model name should either be bolded once where it is defined at 116 or set in plain text.
[qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 981, 471, 1064. Example:
job_search_example(951-1057) produces the lecture’s analytical centrepiece - a 2x2 panel of the likelihood ratio, the accept/reject regions with belief-update arrows, the two densities, and the acceptance probability under each distribution - and then a second 2-panel figure of empirical CDFs. It is called five times (1118, 1135, 1147, 1173, 1184), producing ten figures, none of which carries a:name:or a caption, so all the prose can do is point positionally: ‘the red arrows in the upper right figure’ (1068), ‘the lower right figure sheds light on which effect dominates’ (1103), ‘That graph shows that …’ (1109). Worse, 1158 asks for a cross-example comparison - ‘Comparing outcomes to the baseline case (example 1)’ - across roughly a hundred lines and eight intervening figures. Second, the ‘reject’ and ‘accept’ annotations are placed at hard-coded coordinates (471-472, 759-760, 996-997:ax.text(0.5, 0.6, 'reject'),ax.text(0.7, 0.9, 'accept')) while the shape and level of \(\bar w(\pi)\) change from example to example - 1131-1132 says that in Example 2 the curve is increasing rather than decreasing - so nothing guarantees the two labels still fall inside the regions they name in Examples 2 through 5. Third, the sequence of five examples varies two parameters (\(G\)’s Beta parameters and \(c\)) and the payoff is a comparison of \(\bar w(\pi)\) curves; one overlay figure of the five reservation-wage functions on shared axes would make the comparative statics visible in a way that ten separate 2x2 panels cannot.
Low severity#
[qe-fig-006] — Lowercase axis labels. Count: 1. Lines: 849. Example: axis label
Time.[qe-ref-001] — Use correct citation style. Count: 1. Lines: 79. Example:
{cite}in narrative flow: ‘{cite}’.
Strengths#
The contraction argument is given in full and at the right level of detail: the function space and norm are declared (556-559), the operator \(Q\) is defined as a display (563-569), the equivalence between its fixed points and the RWFE is stated both ways (571-573), the bound is built in three steps with the elementary inequality \(|\max\{a,b\} - \max\{a,c\}| \leq |b-c|\) isolated as its own numbered equation (592-596), and the two consequences - existence of a unique \(\bar w\) and uniform convergence of \(Q^k\omega\) from any starting point - are drawn out separately (622-625).
The reduction from a two-dimensional value function to a one-dimensional reservation-wage function is motivated before it is derived: 486-497 says what will be gained (‘one dimensional rather than two dimensional’, ‘no maximization step’, ‘orders of magnitude faster than VFI’) and why it is worth the algebra (‘when it comes to programming, a bit of mathematical analysis goes a long way’), and then 501-544 derives
{eq}`odu_mvf4`in four displays from the indifference condition at \(w = \bar w(\pi)\).The lecture predicts the shape of the answer before computing it. 242-266 argues that the policy must be a threshold, that the threshold must depend on \(\pi\), and that it must be decreasing in \(\pi\) - with the two-step reason given (\(f\) is the less attractive distribution, larger \(\pi\) puts more weight on it) - and then 477-482 goes back and checks the computed policy against that prediction (‘The black line in the figure above corresponds to the function \(\bar w(\pi)\) introduced there. It is decreasing as expected.’).
Bayes’ rule is not asserted but shown:
{eq}`odu_pi_rec_2`at 157-162 is followed immediately by the two displays it comes from (166-176), and the two objects the recursive formulation needs - the mixture density \(q_\pi\) and the update map \(\kappa\) - are defined as displays (183-191) and then appear under the same names in the code (334-341, 646-653).The two solution methods are implemented against the same
SearchProbleminstance with the same Monte Carlo draws (sp.w_f,sp.w_gat 311-312), so the exercise at 686-697 - ‘Your result should coincide closely with the figure for the optimal policy shown above’ - is a genuine cross-check of two independent algorithms rather than a restatement.Appendix B builds the one figure that answers the question the model is for: 1090-1112 poses the two countervailing effects (under \(f\) offers are lower but \(\pi\) rises and the reservation wage falls; under \(g\) offers are higher but \(\pi\) falls and the reservation wage rises), says that the question is which dominates, and then the lower-right panel measures exactly that, with the empirical CDFs of unemployment duration (1043-1048) as the confirmation.
The five examples move one thing at a time and each states its prediction first: same mean and smaller variance makes \(G\) inferior so \(\bar w\) turns increasing (1123-1132), smaller variance still makes the slope steeper (1140-1144), higher \(c\) buys a longer learning period so acceptance comes later and \(\pi\) is closer to 0 or 1 at acceptance (1152-1170), lower \(c\) the reverse (1178-1181).
Recommended actions#
Fix the two broken LaTeX escapes, since one of them is in the lecture’s summary statement:
${W_t}$at 94 and 133 should be$\{W_t\}$, and$\mathbb 1{w\geq \bar w(\pi) }$at 265 should be$\mathbf{1}\{w \geq \bar w(\pi)\}$to match line 113.Repair the two internal cross-references at 478 and 693.
[looking forward](looking-forward)and[shown above](take-1-solution-by-vfi)are markdown links to MyST targets and resolve as document paths; use{ref}looking forwardand `{ref}`shown above <take-1-solution-by-vfi>, or prefix the targets with#.Convert the four raw links (38, 41, 861, 1071) to references. Line 38 points at
python.quantecon.org/mccall_model.html, which is in this series and which line 84 already links as{doc}`mccall_model`; 41, 861 and 1071 all point at the sameexchangeable.html, for whichintermediate:is a mapped intersphinx key in this repo’s_config.yml. This is the qe-link-002 finding, and the first is the one that matters.Settle the \(F,G\) / \(f,g\) convention that 127-129 declares. The immediate fix is the legend at 1022-1023, where a curve computed from
beta.cdfis labelled$f$and$g$- it should be$F$/$G$, or the words used at 1043-1044 (‘f generates’ / ‘g generates’). Then either use the uppercase letters for the distributions throughout the prose (216-217, 256, 1096, 1099, 1111) or drop \(F\) and \(G\) entirely, since after line 129 they do not reappear until 872.Seed the randomness.
SearchProblem.__init__draws its Monte Carlo integration nodes unseeded (311-312), and 800, 833 and 912 draw unseeded too, so the value function, the reservation-wage curve, the unemployment-rate spike in Appendix A, the ten figures in the Examples section and the iteration counts printed at 737 and 743 all move on every build. Aseedargument onSearchProblemwith a default would fix all of it in one place.Name the figures and make the panels citable. Add
mystnbfigure metadata with:name:to the seven figure cells (222, 448, 462, 750, 782, plus the two produced insidejob_search_example), move the three embedded titles into captions (850, 1047, 1054 - note 1047 and 1054 usetitle.set_text, which is the same thing written the long way), and replace the positional references at 1068, 1103, 1105 and 1158 with{numref}citations. Then add one overlay figure of the five reservation-wage curves so the comparative statics of Examples 1-5 can be seen at once.Check the hard-coded ‘reject’/‘accept’ annotations. They sit at fixed coordinates (471-472, 759-760, 996-997) while \(\bar w(\pi)\) changes shape across the five examples - 1131-1132 says it is increasing in Example 2 - so they should be placed relative to
w_bar(for instance at the mid-grid point, just below and just above the curve) rather than at \((0.5, 0.6)\) and \((0.7, 0.9)\).Fix the code defects: the four-quote docstring at 371,
if e == False:at 799, thepshadowing at 642 and 833, the unusedqat 1009, the double blank line at 441-442, and the lambda assignments at 230-231, 302-303, 793 and 970-972 (withlat 970 renamed - E741). Add a one-line comment at 1002 saying thatΔWstays zero because the belief moves and the wage does not.Do the math and figure sweep: write the five distributional statements with
\simand\operatorname{Beta}rather than a literal tilde (1066, 1123, 1140, 1152, 1178); rename the three dummy variables in{eq}`odu_nt2`at 590-596, which currently reuse \(b\) and \(c\); lowercase the axis label at 849 and write the twoProb(...)labels at 1048 and 1055 as$\mathbb{P}$expressions (the checker cannot see either, since both are set throughax.set); addlw=2to the nine line plots (756, 847, 993, 1022, 1023, 1043, 1044, 1050, 1051 - note 234, 235, 986, 1015 and 1016 already set it); and drop the sevenfigsizeoverrides (233, 451, 466, 754, 846, 981, 1041).Sentence-case the seven Title Case headings (70, 82, 116, 242, 484, 499, 1064) - note that 269 (‘Take 1: Solution by VFI’) and 551 (‘Solving the RWFE’) are already correct and their acronyms should be left capitalised. Reconcile the H1 with the sibling copy, which numbers this lecture ‘Job Search IX’; the two series disagree and only one can be right.
Rewrite the
{cite}-as-subject at 79 (‘Let’s first review the basic McCall model{cite}`McCall1970`and then add the variation’) - this is the qe-ref-001 hit - fix the seven typos (129, 149, 832, 892, 906, 1088, 1159), correct ‘deploys the libraries’ at 27 to the house wording with the single library named, and clear the 23 double spaces. Note that the two copies of this file differ only in the H1 number and in two em dashes (248, 253), so every other fix has to be mirrored inlecture-python.myst.