dyn_stack#

  • Series: lecture-dp

  • File: lectures/dyn_stack.md

  • Audit date: 2026-08-26

  • Corpus snapshot: c30490a2f4

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

  • Overall score: 7.1 / 10

  • Priority: HIGH

Score breakdown#

Category

Score

One-line note

Writing

4/10

qe-writing-005 ×7; qe-writing-002 ×5; qe-writing-003 ×2, +2 more.

Math

5/10

qe-math-002 ×25.

Code

8.5/10

qe-code-001 ×3.

JAX

out of scope

JAX rules target lecture-jax.

Figures

5/10

qe-fig-003 ×6; qe-fig-005 ×6; qe-fig-008 ×7, +1 more.

References

10/10

no mechanical violations detected.

Links

7.5/10

qe-link-002 ×5.

Admonitions

10/10

no mechanical violations detected.

Issues#

Critical#

None found.

High severity#

  • [qe-fig-003] — No matplotlib embedded titles. Count: 6. Lines: 1031, 1101, 1106, 1110, 1364, 1429. Example: .set_title.

  • [qe-fig-005] — Descriptive figure names for cross-referencing. Count: 6. Lines: 1021, 1094, 1151, 1264, 1355, 1417. Example: code-cell figure without mystnb figure metadata.

  • [qe-fig-008] — Use lw=2 for line charts. Count: 7. Lines: 1155, 1156, 1276, 1277, 1426, 1427, 1428. Example: plot() without lw=.

  • [qe-link-002] — Use doc links for cross-series references. Count: 5. Lines: 42, 324, 519, 814, 1411. Example: raw link to python.quantecon.org.

  • [qe-math-002] — Use \top for transpose notation. Count: 25. Lines: 355, 513, 516, 523, 529, 658, 669, 714, 887, 1073. Example: apostrophe transpose y'.

  • [qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 5. Lines: 42, 690, 718, 1100, 1406. Example: line 42 drops a preposition - ‘our calculations are closely related to ones described [this lecture]’; lines 690-694 announce ‘several reasons’ and give two; lines 717-719 restate 703-706 without adding anything (‘That we distinguish \(\check z_t\) from \(z_t\) is part and parcel of the Big K, little k device in this instance’); line 1100 has a double space inside a figure legend string, label='reborn  at t Stackelberg leader', so the extra space is rendered into the published figure rather than just sitting in the source; and ‘equilbrium’ is misspelled twice, at 1406 and 1410, in the heading paragraph of the lecture’s concluding comparison.

  • [qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 7. Lines: 252, 470, 476, 494, 538, 718, 813. Example: line 252 uses bold for emphasis three times in one sentence - a component that is unstable if solved backwards and stable if solved forwards. Lines 470, 476, 494 and 538 use ‘Subproblem 1’ and ‘Subproblem 2’ as bold pseudo-headings, each label appearing twice (once to announce the decomposition, once to head the solution), where real subheadings would let the reader navigate between the statement and the solution. The Big K, little k device is bolded at 703, bolded again at 718 in a sentence that adds nothing to 703-706, bolded a third time at 809, and then written a fourth way at 813 as “Big \(K\), little \(k\)” inside curly quotes - one device, four presentations.

  • [qe-writing-008] — Remove excessive whitespace between words. Count: 30. Lines: 311, 314, 315, 339, 438, 441, 445, 557, 570, 583, …. Example: 2 spaces.

Medium severity#

  • [qe-code-001] (reviewer) — Follow PEP8 unless closer to mathematical notation. Count: 3. Lines: 1005, 1097, 1098. Example: line 1005 writes π_matrix = (R + F. T @ Q @ F) - a space between F. and T. It is legal Python and it works, but it is plainly a typo for F.T, and every other transpose in the same block is written tight (yt[:, t].T at 1008, z[:, t].T at 1421, yt_tilde[:, t].T at 1422). Line 1097 and 1099 write (- F @ yt).flatten() with a space after the unary minus. Lines 1098 and 1100 indent the continuation of axes[0].plot(...) by four spaces instead of aligning it with the opening delimiter (E128), where the parallel calls at 1105-1111 keep their arguments on one line.

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

  • [qe-writing-003] (reviewer) — Maintain logical flow. Count: 2. Lines: 685, 1447. Example: line 685 says ‘We’ll report results of implementing this code soon’ - no code has been shown at that point. The duopoly section has been pure algebra since line 58, and the first cell that builds \(A\), \(B\), \(R\), \(Q\) and calls the LQ solver is roughly 300 lines further on (around 980-1013), so ‘this code’ has no referent for a reader who has just been handed the matrices at 640-683. Second, the lecture ends on an uninterpreted number: the final cell (1445-1447) evaluates vt_leader[0] + vt_follower[0] - 2 * vt_MPE[0] with a comment saying it is ‘the difference in total value between the Stackelberg and the MPE’, and then the file stops. Nothing states what sign to expect or what the comparison establishes, although 1406-1411 promised it would be ‘enlightening to compare equilibrium values for firms 1 and 2 under two alternative settings’.

  • [qe-writing-004] — Avoid unnecessary capitalization in narrative text. Count: 1. Lines: 323. Example: mid-sentence ‘Programming’.

Low severity#

None found.

Strengths#

  • The two-subproblem decomposition is set up and then closed: Subproblem 1 is solved as an ordinary LQ problem yielding the Riccati equation and \(F\) (500-536), Subproblem 2 is solved in closed form for the free initial jump, \(x_0 = -P_{22}^{-1}P_{21}z_0\) (538-551) - and that formula reappears verbatim as the first line of the recursive representation at 725 and as H_0_0 = -P22inv @ P21 in the code at 994, so the algebra and the implementation are visibly the same object.

  • The Big K, little k device is introduced with a reason rather than as a trick: 701-711 explains that a representation of the leader’s history-dependent plan requires distinguishing \(\check z_t\), which the follower takes as given, from \(z_t\), which depends partly on the follower’s own decisions - and 795-796 states the payoff precisely, that the representation is cast in terms of \(\check z^t\) and not \(z^t\).

  • The lecture states and then demonstrates the asymmetry that motivates it: the follower’s problem is recursive in the natural state variables and therefore time consistent (800-805), while the leader’s plan is not - and the ‘reborn at \(t\)’ experiment at 1088-1112 plots the two paths against each other so the reader sees the leader’s incentive to restart.

  • Every equality that could be taken on trust is checked in code instead: (np.abs(v2_direct - v2_direct_alt) < tol2).all() at 1402 verifies two different computations of the follower’s value, and the concluding cells print the three continuation values side by side (1435-1442) before differencing them.

  • The mapping from the duopoly model into the general Stackelberg setup is done explicitly and completely (553-683): the state vector, the \(4 \times 4\) transition system written out entry by entry, firm 2’s revenues as \(z_t'R_1z_t\) with \(R_1\) displayed, and then \(R = \begin{bmatrix} R_1 & 0 \\ 0 & 0\end{bmatrix}\) - so a reader can check every matrix in the code against a display.

  • The final comparison is a genuine three-way one - Markov perfect, Stackelberg leader, Stackelberg follower - plotted on one axis with a legend placed outside the frame so the three curves stay readable (1424-1432), and the sign convention (values are negatives of quadratic forms) is applied consistently at 1421-1422.