lqcontrol#

  • Series: lecture-python.myst

  • File: lectures/lqcontrol.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.5 / 10

  • Priority: LOW

Score breakdown#

Category

Score

One-line note

Writing

4.5/10

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

Math

5/10

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

Code

7.5/10

qe-code-001 ×5.

JAX

out of scope

JAX rules target lecture-jax.

Figures

5.5/10

qe-fig-005 ×8; qe-fig-006 ×3; qe-fig-002 ×5, +2 more.

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-001] (reviewer) — Follow PEP8 unless closer to mathematical notation. Count: 5. Lines: 698, 1272, 1293, 1298, 1556. Example: the lecture builds an LQModel NamedTuple with create_lq_model and simulate_and_plot (635-716), and 746 makes a point of the pattern - “Since LQModel is a NamedTuple, we can obtain the new model by copying the old one with a single field changed” - and then not one of the three exercise solutions uses it: 1255-1319, 1372-1469 and 1525-1579 all rebuild raw parameters and re-inline the plotting. The consequence is that the twenty-line plotting block at 688-712 is copied nearly verbatim at 1297-1319 and again at 1447-1469 (same n_rows, subplots_adjust(hspace=0.5), bbox, legend_args, p_args, same loop over axes), three copies to keep in step. The matrices are built two different ways for the same purpose: np.array at 661-669 in the body, bare nested lists at 1272-1283, 1391-1424 and 1542-1554 in the solutions. time is used as a variable name at 1293, 1442 and 1572, shadowing the standard-library module. LQ is called with C positional at 679, 1286, 1405 and 1427 but C=C at 1556. And the continuation lines at 699, 1308 and 1458 are indented to column 12 under a call whose opening delimiter is at column 20, so they neither align with the delimiter nor use a plain hanging indent.

  • [qe-fig-002] — Prefer code-generated figures. Count: 5. Lines: 1018, 1088, 1164, 1168, 1172. Example: static image .png.

  • [qe-fig-005] — Descriptive figure names for cross-referencing. Count: 8. Lines: 1018, 1088, 1164, 1168, 1172, 1255, 1372, 1525. Example: {figure} without :name:.

  • [qe-math-010 (proposed)] — Blackboard \mathbb{P}, \mathbb{E}, \mathbb{V} with braces. Count: 15. Lines: 101, 301, 373, 387, 410, 421, 434, 458, 587, 786, …. Example: missing braces: \mathbb E.

  • [qe-writing-003] (reviewer) — Maintain logical flow. Count: 5. Lines: 109, 731, 766, 838, 1481. Example: five breaks. (a) Four headings, two names: #### Example 1 appears at 109 and again at 252, #### Example 2 at 131 and again at 274 - one pair under “The law of motion”, one under “Preferences” - so the TOC carries two entries called “Example 1” and the labelled references have to route around them (lq_hhp at 130 and {ref}studied above <lq_hhp>`` at 276 do exactly that). (b) 731 defines cumulative unanticipated income as \(z_t := \sum_{j=0}^t \sigma w_t\) - the summation index \(j\) never appears in the summand, so as written the sum is \((t+1)\sigma w_t\); the code at 702 computes np.cumsum(income - \u03bc), i.e. \(\sum_j \sigma w_j\). © 766-768 says “we’ve chosen not to treat this extension in our implementation given below”, but the implementation is above it: the LQ interface is described at 544-563 and the model built and simulated at 634-716. (d) 838-842 joins two statements with “In other words” that are not restatements of each other - “optimal policies can depend on time only if time itself is a component of the state vector \(x_t\)” followed by “In other words, there exists a fixed matrix \(F\) such that \(u_t = -Fx_t\) for all \(t\)”; the second follows from time not being in the state, which the first sentence does not say. (e) lqc_ex3 at 1480-1482 asks the reader to reproduce three figures, “while \(\gamma\) varies between 1 and 50 (see figures)”, and the solution at 1525-1579 sets \u03b3 = 1 once and draws one figure - two of the three static images at 1164-1174 are never reproduced.

  • [qe-writing-008] — Remove excessive whitespace between words. Count: 12. Lines: 247, 272, 332, 345, 353, 530, 551, 724, 839, 1022, …. Example: 2 spaces.

Medium severity#

  • [qe-fig-001] — Do not set figure size unless necessary. Count: 4. Lines: 690, 1299, 1449, 1565. Example: figsize=.

  • [qe-fig-006] — Lowercase axis labels. Count: 3. Lines: 709, 1316, 1466. Example: axis label Time.

  • [qe-fig-008] — Use lw=2 for line charts. Count: 3. Lines: 705, 1312, 1462. Example: plot() without lw=.

  • [qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 3. Lines: 492, 925, 1496. Example: \(m_0, m_1, m_2\) are the coefficients of the income polynomial at 925-931 and 1055, and then at 1496 the same letters become the monopoly constants, “Letting \(m_0 := (a_0 - c) / 2a_1\) and \(m_1 := 1 / 2 a_1\)” - and the code carries the collision, with m1/m2 as polynomial coefficients at 1264-1265 and 1383-1384 and m0/m1 as monopoly constants at 1537-1538. Worse, both definitions at 1496 are missing their braces: (a_0 - c) / 2a_1 renders as \(\tfrac{a_0-c}{2}a_1\) and 1 / 2 a_1 as \(\tfrac12 a_1\), where the code at 1537-1538 computes (a0-c)/(2 * a1) and 1/(2 * a1) - the display and the code compute different things. And the trace operator is written \mathop{\mathrm{trace}} at 492, 513 and 881, where \mathrm{tr} is both the conventional spelling and shorter, and the \mathop{} wrapper adds nothing \mathrm{} does not already give.

  • [qe-writing-001] — Use one sentence per paragraph. Count: 1. Lines: 247. Example: 3 sentences in one paragraph.

  • [qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 3. Lines: 335, 462, 1238. Example: 335-341 spends three paragraphs and about ninety words establishing a measurability condition and then tells the reader to ignore it: “The fancy measure-theoretic way of saying this is that \(u_t\) must be measurable with respect to the \(\sigma\)-algebra generated by \(x_0, w_1, w_2, \ldots, w_t\)”, then the equivalent function form, then “(Just about every function that’s useful for applications is Borel measurable, so, for the purposes of intuition, you can read that last phrase as “for some function \(g_t\)”)” - which is the whole content, and could be the whole passage. The lecture also apologises twice for its own plotting code rather than simplifying it: 1238-1239 “We use some fancy plot commands to get a certain style – feel free to use simpler ones” and the comment at 1567 “# Some fancy plotting stuff – simplify if you prefer”; what is being apologised for is the same six-line bbox_to_anchor legend block copied three times. And 462 abbreviates “the r.h.s.” where 101 writes “the right-hand side” in full for the same thing.

  • [qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 3. Lines: 861, 863, 891. Example: there is no bold anywhere in the 1583 lines, and three of the four italic spans are definitions, which is the case the rule reserves for bold: 861 “Equation {eq}`lq_pr_ih` is also called the LQ Bellman equation”, 863 “the map that sends a given \(P\) into the right-hand side … is called the LQ Bellman operator”, and 891 “have the property of certainty equivalence” - each one an explicit naming of a term the lecture then reuses. Only 160 (affine) is emphasis and correctly italic. The rest of the lecture’s defined terms get no emphasis at all and are marked with quotation marks instead - “control” vector (100), “linear” and “quadratic” (81), “ideal” quantity (141), “loss-to-go” (391), “cost” (402) - so the convention is not applied in either direction.

  • [qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 355, 889, 1164. Example: the three static images at 1164, 1168 and 1172 are presented as a controlled comparison - 1162 says “The only difference in parameters across the figures is the size of \(\gamma\)” - and nothing on the page says which figure is which \(\gamma\): all three {figure} directives have an empty body (1165, 1169, 1173), so there is no caption, and the value lives only in the file names ..._g1.png, ..._g10.png, ..._g50.png. The reader is asked to compare three unlabelled images. The same emptiness applies to the two life-cycle figures at 1018 and 1088. Second, the backward-induction derivation at 355-542 is the analytical core and has no figure at all, although the object it constructs - the sequence \(\{P_t\}\) running backwards from \(R_f\) to \(P_0\), and the policy sequence \(\{F_t\}\) with it - is directly plottable, and LQ.update_values (561) already exposes exactly that iteration. Third, certainty equivalence (889-897) is a striking property and its only verification is “This can be confirmed by inspecting {eq}`lq_oc_ih` or {eq}`lq_oc_cp`” (895) - solving the same model at two values of \(C\) and printing the identical \(F\) is three lines and would make the claim checkable.

Low severity#

None found.

Strengths#

  • The backward induction is derived one step at a time rather than stated: 360 introduces \(J_T(x) = x^\top R_f x\) and says why (“For reasons that will soon become clear”), 362-391 solves the \(T-1\) problem in full, 393-423 steps back to \(T-2\) and explicitly says what plays the role of what (“the value \(J_{T-1}(x)\) plays a role analogous to that played by the terminal loss”), 425-438 generalises, and only then does 440 name the result the Bellman equation - so the reader arrives at the recursion having watched it being built twice.

  • Every stage of the derivation is labelled and the labels are used: lq_lom (92) is cited at 128, 307, 530, 986 and 1215, lq_lomwc (150) at 194 and 599, lq_lowmc2 (200) at 602, lq_pref_flow (234) at 263, lq_object (299) at 542 and 605, lq_lsm (382) at 450, lq_fswb (453) at 472, lq_oc0 (467) at 518, lq_pr (500) at 561, 624 and 794, lq_dd (511) at 561, 816 and 875, lq_oc (521) at 528, 530, 561, 624 and 806, lq_xud (535) at 627, lq_pio (585) at 604 and 1058, lq_lowmc3 (951) at 1342, lq_4sp (993) at 1348, lq_cases (1044) at 1064, lq_object_ih (830) at 1213, lq_object_mp (1122) at 1184 and 1186, lq_object_mp2 (1202) at 1212.

  • The “add a state to buy linearity” trick is taught three times at rising difficulty and each time the reason is stated before the construction: the constant term problem at 157-163 solved by appending a 1 (167-225, “In effect, we’ve bought ourselves linearity by adding another state”), the age-dependent income profile solved by appending \(t\) and \(t^2\) (941-984, with 986-988 telling the reader to expand \(Ax_t + Bu_t + Cw_{t+1}\) and check), and the retirement kink solved by splicing two LQ problems (1064-1083, with the three-step recipe spelled out).

  • The information constraint is treated as a substantive assumption rather than a technicality: 320-353 states that \(u_t\) may not depend on future shocks, gives the measure-theoretic version and its plain-language equivalent, then explains why the state suffices (343-347) and closes by naming what is special about the LQ case (“the optimal \(u_t\) turns out to be a linear function of \(x_t\)”, 353).

  • The extensions section is honest about what has and has not been implemented: 764-776 says time-varying parameters are not treated, says why the loss is smaller than it looks, points at where the trick is used instead ({ref}below <lq_nsi>``) and gives a reference for the systematic treatment; 792-818 works the cross-product term into the Riccati recursion and the policy, states that \(\{d_t\}\) is unchanged, and leaves the algebra as an explicit exercise.

  • The {note} at 246-250 records two things a careful reader would otherwise get wrong - that the definiteness conditions on \(R\) and \(Q\) can be relaxed, and that “this notation is not universal: some authors let \(Q\) be the matrix associated with the state and \(R\) the matrix associated with the control” - which is exactly the confusion that trips someone reading a second source.

  • The comparative-statics claim about the monopolist is stated as a prediction before it is computed: 1141-1156 reasons out what \(\gamma = 0\) implies, derives the frictionless target \(\bar q_t\), and predicts the two limiting behaviours, and only then does 1158 say “This intuition turns out to be correct” and show the simulations.

  • All three exercises supply the complete parameter set needed to reproduce the figure (1228, 1331, 1480-1482), the second carries a five-point {hint} in a dropdown covering exactly the two things that make the splice work (1335-1356), and all five admonition rules plus both link rules measure zero.