dyn_stack#
Series: lecture-python-advanced.myst
File:
lectures/dyn_stack.mdAudit date: 2026-08-26
Corpus snapshot:
b83d6da399Categories audited: writing, math, code, figures, references, links, admonitions (JAX out of scope)
Overall score: 6.8 / 10
Priority: HIGH
Score breakdown#
Category |
Score |
One-line note |
|---|---|---|
Writing |
3.5/10 |
|
Math |
4/10 |
|
Code |
7.5/10 |
|
JAX |
out of scope |
JAX rules target |
Figures |
5/10 |
|
References |
10/10 |
no mechanical violations detected. |
Links |
7.5/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: 7. Lines: 1005, 1058, 1213, 1236, 1255, 1257, 1356. Example: 1005 writes
π_matrix = (R + F. T @ Q @ F)- a space between the attribute dot andT, which parses but reads as a typo and is what PEP8’s whitespace-before-member rule exists to prevent. Four of the file’s “manual checks” are one-sided inequalities where a magnitude is meant:(P - P_next < tol0).all()at 1058,(v_leader_direct - v_expanded < tol0)[0, 0]at 1066,yt[:, 0][-1] - (...)[-1] < tol0at 1172 and(P - (...) < tol0).all()at 1205 (also carrying a double space) all pass whenever the difference is negative, so a comment reading “Manually checks whether P is approximately a fixed point” is checking something weaker than it claims; 1403 does it correctly withnp.abs, so the pattern is available in the same file. The two iteration cells usefor i in range(1000)andfor i in range(100)withiunused and a fixed count in place of a convergence test (1213, 1234, 1237), and 1236 resetsP_iter = np.zeros((5, 5))inside the outer loop so each policy evaluation restarts from zero. 1255 is a bareF_iterexpression nested inside anifblock, which Jupyter does not echo, so the success branch of the policy-iteration cell prints nothing at all. 1257-1258 and 1260-1261 put a backslash continuation inside a string literal, so the failure messages carry a run of twelve spaces mid-sentence. And 1356-1357 name the MPE quantities against the state vector: 1292 defines \(z_t = [1, q_{2t}, q_{1t}]'\), soq1 = z[1, :]is firm 2’s output andq2 = z[2, :]is firm 1’s - the reverse of the conventionq_leader = yt[1, :-1],q_follower = yt[2, :-1]used at 1022-1023 - and 1380-1381 then pairπ_1 = p * q1 - γ * u1**2withu1 = -F1 @ z, mixing one firm’s control with the other’s quantity; the error is invisible only because the MPE is symmetric, which the cell at 1372 goes on to verify.[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-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 6. Lines: 516, 750, 887, 900, 906, 591. Example: 516 writes the maximum as
{\rm max}_{u, y^*}where\maxis the standard operator and is used correctly at 473, 479 and 502 in the same section. The multiplier matrices flip index order between their definition and their use: 750 writes \(\check x_t = \sum_{j=1}^t H_j^t \check z_{t-j}\) and 756-760 define \(H^t_1, H^t_2, \ldots, H^t_t\), so the same object appears as both \(H_j^t\) and \(H^t_j\) in adjacent displays. The accent on \(X\) is applied inconsistently in the follower’s problem: 887 writes \(\tilde X_t' \tilde R x_t\) where the display’s own right side is a quadratic form in the five-vector (so the second factor should be \(\tilde X_t\)), and puts \(- x_t^2 \tilde Q\) on the left against \(- \gamma x_t^2\) on the right, giving two names for one scalar; 900 has \(x_t = -\tilde F X_t\) and 920 \(x_0 = -\tilde F \tilde X_0\); 906 has \(\tilde X_{t+1} = (\tilde A - \tilde B \tilde F) X_t\), accented on the left and not on the right. And 591 introduces a third sequence notation, \(\vec q_{2,1} = \{q_{2t+1}\}_{t=0}^\infty\), used once and never again beside \(\vec q_2\) and \(\{q_{2t+1}\}_{t=0}^\infty\).[qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 12. Lines: 155, 226, 239, 251, 703, 795, 813, 881, 1076, 1128, …. Example: the same device is formatted four different ways:
**Big K, little k**(703, 718, 809), curly-quoted with math delimiters as“Big $K$, little $k$”(813), split into**Big K**and**little k**(881-882), and mangled at 1128 as**Big** $K$ **, little** $k$, which renders as a bold “Big”, a plain \(K\), a bold “, little” and a plain \(k\). Bold is then used for plain emphasis throughout - time and within time (155-158), two boundary conditions: (226), factored (239), unstable, backwards, stable, forwards in one sentence (250-252), not (795), resets (1076), state (1289) and reborn at \(t\) three times (1117-1119) - alongside its correct definitional uses (Stackelberg leader/follower 34-36, natural state variables 327, feedback-feedforward 269, continuation Stackelberg leader 454). Italic, meanwhile, is reserved for two words only (working backward 175, natural 802).[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-fig-001] — Do not set figure size unless necessary. Count: 3. Lines: 1027, 1095, 1361. Example: figsize=.
[qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 4. Lines: 160, 610, 685, 1041. Example: 610-635 restates 178-204 word for word (see qe-writing-003), 26 lines of algebra a reader has already worked through. 160-165 spends two sentences saying that the abstract formulation is abstract (“While our abstract formulation reveals the timing protocol and equilibrium concept well, it obscures details that must be addressed when we want to compute and interpret a Stackelberg plan” / “To gain insights about these things, we study them in more detail”). 685-697 is four paragraphs of announcement with no content - “We’ll report results of implementing this code soon”, “But first, we want to represent the Stackelberg leader’s optimal choices recursively”, a two-bullet list of reasons, then “First, let’s get a recursive representation” - immediately before the section that does it. And 1041-1043 says the same thing twice: “We’ll compute it two ways and get the same answer” then “In addition to being a useful check on the accuracy of our coding, computing things in these two ways helps us think about the structure of the problem”.
[qe-writing-003] (reviewer) — Maintain logical flow. Count: 4. Lines: 274, 610, 775, 1117. Example:
{eq}`sstack3`and its own restatement disagree about a sign: 274 writes the forward sum as\sum_{j=0}^\infty \delta_2^j q_{2t+j+1}while 296, quoting the same equation inside the leader’s Lagrangian{eq}`sstack4`, writes\sum_{j=0}^\infty \delta_2^{-j} q_{2t+j+1}- and since 248 establishes \(\delta_2 > 1/\sqrt\beta > 1\), only the second converges, so 274 is wrong at the point where the follower’s decision rule is first stated. Lines 610-635 repeat 178-204 verbatim: the same Lagrangian, the same two first-order conditions and the same rearrangement, differing only by the word “again”, a dropped citation, and a new typo (“These first-order order conditions”).{eq}`finalrule`at 775 drops the check accent -u_t = -F_z \check z_t - F_x \sum_{j=1}^t H^t_j z_{t-j}- in the one equation whose entire purpose, stated at 791-796, is that the representation must be “cast in terms of the history \(\check z^t\) and not a corresponding representation cast in terms of \(z^t\)”; 750 has the accent. And the three-bullet reading of the time-inconsistency figure at 1117-1119 is self-contradictory and mislabels the firms: the third bullet says the reborn leader “wants to increase the output of the follower firm 2 below that prescribed” - increase below, and firm 2 is the leader per 108-109 - while the first bullet cites \(w(\hat x_0)\) where the panel plots \(w(\hat x_t)\).[qe-writing-004] — Avoid unnecessary capitalization in narrative text. Count: 1. Lines: 323. Example: mid-sentence ‘Programming’.
[qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 102, 1094, 1161. Example: the timing protocol is the concept the whole lecture rests on - both firms choose entire sequences once and for all at \(t=0\), the leader first within time 0 and the follower second, knowing the leader’s whole sequence (102-121, restated at 153-158 and again at 459-468) - and it is carried entirely by prose that has to italicise “within time” to make the point; a single timeline would do it. Second, the file has twelve code cells whose entire output is an unlabelled bare value:
True/Falseat 1058, 1066, 1172, 1205 and 1403, a raw float at 1167, 1284, 1372 and 1445, and raw matrices at 1190, 1195 and 1200 - a reader cannot tell what any of them is asserting without reading the code, where a printed sentence or a{note}would say it. The one place the lecture does want to flag something to the reader, 1161-1162 (“Note: Variables with_tildeare obtained from solving the follower’s problem – those without are from the Stackelberg problem”), is written as bare prose immediately after a figure rather than as a{note}admonition, so the convention that governs the next fifteen cells is easy to miss. Third, the time-inconsistency figure (1094-1113) plots 301 points as'bo'/'ro'markers atms=2in three stacked panels with embedded titles and no y-axis labels, and the second panel has no legend at all.
Low severity#
None found.
Strengths#
The follower’s decision rule is derived rather than quoted: the Euler equations are reduced to the second-order difference equation
{eq}`sstack1`(217-221), its two boundary conditions are stated separately (226-230), the lag polynomial is factored with the root ordering \(0 < \delta_1 < 1 < 1/\sqrt\beta < \delta_2\) made explicit (242-248), the reason for inverting the unstable root forward rather than backward is given in one sentence (250-252), and the result is named for its shape - the feedback-feedforward form{eq}`sstack3`- so the forward sum over the leader’s future quantities arrives as a consequence rather than an assumption.The two-subproblem split is justified before it is made: 459-468 lists the three things the decomposition has to respect (the follower moves after seeing \(\vec q_2\), the leader optimises knowing the best response, the leader’s problem must be recursive in artfully chosen states), and only then does 470-485 state subproblem 1 as taking \(x_0\) as given and subproblem 2 as choosing it - which is exactly what makes 540-551 a one-line gradient condition, \(x_0 = -P_{22}^{-1}P_{21}z_0\).
Time inconsistency is defined operationally and then computed: 1073-1076 specifies the reborn leader as one who inherits \(z_t\) but discards the continuation \(x_t\) and resets it to \(-P_{22}^{-1}P_{21}z_t\), the reset is a single line of code (1087), both value paths are evaluated with the same \(P\) (1089-1091), and the three panels show the reset’s effect on \(u_t\), on \(x_t\) and on the value simultaneously.
Almost every quantity is computed two independent ways and compared: the leader’s time-0 value by forward simulation of \(\beta^t\pi_t\) against the quadratic form \(-y_0'Py_0\) (1046-1052), the recursive follower’s output path against the Stackelberg path (1151-1167), the \(\tilde F^*\) hand-simulation against both (1264-1284), and the MPE value against a one-sided LQ that takes the other firm’s rule as given (1395-1403).
The Big K / little k device is used twice for two different jobs and each is explained where it is used: 700-719 separates \(\check z_t\) from \(z_t\) so that the leader’s plan can be presented to the follower as a history-dependent sequence, and 811-882 carries \(\tilde q_{1t}, \tilde x_t\) alongside \(q_{1t}, x_t\) so that \(q_{2t}\) becomes exogenous to firm 1 - with 786-796 raising and answering the obvious objection that \(z_t = \check z_t\) holds anyway.
The economics is closed at the end rather than left implicit: 1406-1414 sets up the comparison, 1417-1432 plots the MPE value against the Stackelberg leader’s and the follower’s on one axis, and 1443-1445 prints the exact welfare difference \(v_{\text{leader}}(y_0) + v_{\text{follower}}(y_0) - 2 v_{\text{MPE}}(y_0)\) so the reader gets a number and not an impression.
The
{hint}at 1175-1186 asks a real question - which coefficients of \(\tilde F\) force the follower to set \(x_t = \tilde x_t\) - points the reader at the components of \(X_t\), and then printsF_tilde.round(4)in the very next cell so the answer is checkable on the spot.
Recommended actions#
Fix the four verification cells that compare a signed difference against a tolerance instead of its magnitude - 1058, 1066, 1172 and 1205 - each of which reports success whenever the difference happens to be negative; 1403 already uses
np.absand is the pattern to copy.Correct the exponent in
{eq}`sstack3`at 274: it writes \(\sum_{j=0}^\infty \delta_2^{j} q_{2t+j+1}\) where 296 writes \(\delta_2^{-j}\), and only the negative exponent converges given \(\delta_2 > 1\) from 248 - then check the inverse operator at 262-264, whose right side \(-\delta_2 (1-\delta_2^{-1})^{-1}L^{-1}\) does not match the left side \([-\delta_2 L(1-\delta_2^{-1}L^{-1})]^{-1}\) in either the power of \(\delta_2\) or the \(L^{-1}\) inside the parenthesis.Swap the names in the MPE cell at 1356-1357 so they match the state vector defined at 1292 (\(z_t = [1, q_{2t}, q_{1t}]'\)) and the leader/follower convention at 1022-1023, and re-pair the profits at 1380-1381 with the matching controls.
Delete the duplicated derivation at 610-635, which repeats 178-204 apart from the word “again”, a dropped citation and the typo “first-order order”, and keep the citation.
Rewrite the three bullets at 1117-1119: the third says the reborn leader wants to “increase the output of the follower firm 2 below that prescribed” (self-contradictory, and firm 2 is the leader), the first cites \(w(\hat x_0)\) where the panel shows \(w(\hat x_t)\), and 1117 and 1121 carry “leader’s’s” and “leaders’s”.
Settle one formatting for “Big K, little k” - it appears as
**Big K, little k**(703, 718, 809),“Big $K$, little $k$”with curly quotes (813), split bold (881-882) and mangled at 1128 - and move the emphatic bolds at 155-158, 226, 239, 250-252, 795, 1076 and 1289 to italic.Clean up the code cells:
F. Tat 1005, the fixedrange(1000)/range(100)loops with unusediand the inner reset at 1213-1244, the bareF_iterat 1255 that produces no output, and the two backslash continuations inside string literals at 1257 and 1260 that put twelve spaces into the failure message; also settle one kernel name, since 25 and 50 use{code-cell} ipythonand everything from 946 on uses{code-cell} python3.Give the twelve bare-output cells a printed statement of what they assert (1058, 1066, 1167, 1172, 1190, 1195, 1200, 1205, 1284, 1372, 1403, 1445), and turn the
_tildeconvention at 1161-1162 into a{note}since fifteen later cells depend on it.Finish the mechanical items: the 26 apostrophe transposes to
\top(all genuine - \(y'Ry\), \(u'Qu\), \(A'PA\), \(\check y_t'\), \(\tilde X_t'\)), the six embedded titles (1031, 1101, 1106, 1110, 1364, 1429),mystnb: figure: caption/nameon the six figure cells (1021, 1094, 1151, 1264, 1355, 1417), the threefigsize=, the five raw links to{doc}(42, 324, 519, 814, 1411), the 30 double spaces, thex_tsubscript typo in the title at 1101 (\(q_t\) for \(q_{2t}\)), and the typos “described [this lecture]” (42), “q_{t+1}” for \(q_{1t+1}\) (203, 634), “\(q_{1t}\)” for \(q_{1T}\) in the terminal condition (230), “according an optimal rule” (583), “program describe above” (942) and “equilbrium” (1408, 1412).