supply_demand_multiple_goods#

  • Series: lecture-python-intro

  • File: lectures/supply_demand_multiple_goods.md

  • Audit date: 2026-08-26

  • Corpus snapshot: a12d17c0ef

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

  • Overall score: 8.0 / 10

  • Priority: LOW

Score breakdown#

Category

Score

One-line note

Writing

4.5/10

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

Math

9.5/10

qe-math-009 ×3.

Code

6.5/10

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

JAX

out of scope

JAX rules target lecture-jax.

Figures

7.5/10

qe-fig-006 ×4; qe-fig-008 ×5.

References

N/A

no citations in this lecture.

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: 228, 266, 740, 753, 1031. Example: twenty lines carry trailing whitespace, and most of them are indentation-only blank lines inside class bodies - 228, 251, 260 in ExchangeEconomy, 656, 675 in ProductionEconomy, 973, 984 in Monopoly - plus the per-parameter signature lines at 229-231, 657-661 and 974-978. Five lambdas are assigned to names where PEP8 asks for a def: supply_inv and demand_inv (740-741), marg_cost, marg_rev and demand_inv (1030-1032). Ten lines exceed 79 characters, four of them past 110: 1047 at 116, 1046 at 113, 753 and 754 at 112. 1031 multiplies by a redundant literal, -2 * 1 / μ * Π * Π * x. And 266, 690 and 999 call the built-in any() on a NumPy array (if any(c < 0), if any(Π @ c - b >= 0)) rather than np.any(), which works only because the arrays are one-dimensional and raises on anything else - in a lecture whose whole subject is the \(n\)-good generalisation. One stray item: 1027 is the comment # compute with nothing after it.

  • [qe-fig-008] — Use lw=2 for line charts. Count: 5. Lines: 750, 751, 1042, 1043, 1044. Example: plot() without lw=.

  • [qe-writing-008] — Remove excessive whitespace between words. Count: 10. Lines: 52, 54, 144, 219, 355, 448, 934, 1117. Example: 2 spaces.

Medium severity#

  • [qe-code-002] — Use Unicode symbols for Greek letters in code. Count: 3. Lines: 368, 371, 373. Example: spelled-out beta.

  • [qe-fig-006] — Lowercase axis labels. Count: 4. Lines: 763, 764, 1060, 1061. Example: axis label Quantity.

  • [qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 3. Lines: 91, 360, 1126. Example: 1126 and 1132 write the area under the inverse supply curve as h c + \frac{1}{2} c^\top J c, but \(h\) and \(c\) are both \(n \times 1\) (566, 99), so \(hc\) is not defined - the same quantity is written correctly as \(h^\top q\) at 551 and \(h^\top q\) at 912, and the first-order condition at 1141 differentiates as though the transpose were there. The transpose has simply been dropped from the welfare criterion, twice, in the section that states the first welfare theorem. 91-93 introduces \(\Pi\) as \(m \times n\) and \(b\) as \(m \times 1\) and then never uses \(m\) again: every formula from 106 onward, and every instance in the code, has \(\Pi\) square, and ExchangeEconomy inverts it outright at 263 - so the extra dimension is notation the lecture carries and never needs. And 360 defines two symbols in one chained assignment, (1+r) := R := \frac{p_1}{p_2}, of which neither \(R\) nor \(r\) is used anywhere else in the lecture.

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

  • [qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 3. Lines: 35, 292, 843. Example: 35-47 spends thirteen lines on two bulleted lists of eight economists, each a bare Wikipedia link with no accompanying claim about what any of them contributed - the two welfare theorems at 52-54 do the work those names were introduced for. The Marshallian/Hicksian distinction is set up four separate times: named at 144, explained in a {note} at 177-183, explained again as a two-bullet contrast at 287-290, and then explained a third time at 292-302 under “contemplate different mental experiments”, after which 304 says it will be discussed “more below” and it is not. And 843-847 is a section heading whose entire body is two bullets in the future tense (“we’ll do some experiments like those above”, plus the stale TODO), followed by four code cells with no narration - contrast the single-good example at 768-840, which states what it will do in four bullets and then says what each experiment showed (“this raises both the equilibrium price and quantity”, 840). Smaller edits: 390 is ungrammatical (“our consumer confronts risk means in particular that”), 442 switches to “his/her” where 134 and 275 use “he” and “his”, and 646 refers to something “described above in the main text” while itself being the main text.

  • [qe-writing-003] (reviewer) — Maintain logical flow. Count: 4. Lines: 426, 618, 847, 967. Example: 426-430 labels the wrong vector: “A consumer’s endowment vector is \(c = \begin{bmatrix} c_1 \cr c_2 \end{bmatrix}\)” - that is the consumption vector, and the endowment \(e\) was already displayed at 417 four lines earlier. 618 says “this equation is the counterpart of equilibrium quantity {eq}`eq:old1` for the scalar \(n=1\) model with which we began”, but this lecture does not begin with a scalar model - {doc}`intro_supply_demand` does, and that is where eq:old1 is defined (its line 653). The reference resolves, because {eq} labels are project-wide, but it silently lands the reader in a different lecture while the sentence claims the model is this one’s. 847 ships a literal (TODO) in published prose - “we can do experiments with a diagonal \(\Pi\) and also with a non-diagonal \(\Pi\) matrix to study how cross-slopes affect responses of \(p\) and \(c\) to various shifts in \(b\) (TODO)” - and it is stale, since the four cells immediately below at 849-895 do precisely that. 967 puts the conclusion 145 lines before its evidence: “the plot indicates that the monopolist sets output lower than the competitive equilibrium quantity”, where plot_monopoly is defined at 1010 and first called at 1112. Two smaller ones in the same family: the {note} at 177-183 cites {eq}`eq:old2p` a hundred lines before it is written at 281, and 304 promises “we’ll discuss these distinct demand curves more below”, which never happens.

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

  • [qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 4. Lines: 275, 289, 578, 904. Example: bold carries two jobs at once throughout, so it stops distinguishing either. Definitions are bold, correctly and often: marginal utility of wealth (152), numeraire (283), compensated (302), expected utility (398), Arrow securities (440), insurance (444), marginal costs (566), inverse supply curve (585), consumer surplus and producer surplus (649-650), price-taker (899). But so is plain emphasis: only (275), static (386), marginal revenue to marginal costs (578), price equals marginal revenue (583) - a whole clause - demand curve and price in the contrast at 904, diagonal and non-diagonal (847), substitution and income (294, 298), lower output (923). Meanwhile italic appears exactly once in 1154 lines, at 313, and there it marks part of a definition (known probability distribution), so the lecture has no working emphasis form at all. Three terms are also re-bolded rather than bolded once: Marshallian at 144, 182 and 289, Hicksian at 182 and 290, risk at 313, 388 and 390.

  • [qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 849, 1046, 1112. Example: a lecture titled “Supply and Demand with Many Goods” draws two figures and both are of the single-good case (plot_competitive_equilibrium called at 796, 816 and 837; plot_monopoly called at 1112). Every multi-good result reaches the reader as printed vectors: the endowment economy (377-381), the dynamic interpretation (367-382), the risk economy (454-469), the three comparative statics of sdm_ex3 (496-532), the two-good production economy (849-895) and the multi-good monopoly comparison (1069-1090). The worst case is 843-895, whose stated purpose at 847 is “to study how cross-slopes affect responses of \(p\) and \(c\) to various shifts in \(b\)”: it runs four experiments - diagonal \(\Pi\), shifted \(b\), non-diagonal \(\Pi\), shifted \(b\) again - and delivers eight numbers with no table, no figure and not one sentence of interpretation, so the cross-slope effect the section exists to demonstrate is left for the reader to compute by eye from four separate cell outputs. Second, the monopoly figure (1010-1062) shades consumer and producer surplus at the monopoly quantity but not the deadweight-loss wedge between \(q\) and \(c\), which is exactly what 923-927 and 967-969 claim - that the monopolist sets output below both the planner’s and the competitive quantity. Third, both figures hardcode five hex colours (750-758, 1042-1055) instead of the matplotlib cycle, and the two surplus fills are #EED1CF and #E6E6F5, a pale-pink/pale-lavender pair with very little contrast between them and against white - for the figure’s principal content.

Low severity#

None found.

Strengths#

  • The three linear-algebra identities the lecture will actually use are stated up front, once, with their dimensions declared (65-85), and then every derivative in the file is a direct application of one of them - \(\partial L/\partial c\) at 159-160, \(\partial C(q)/\partial q\) at 569, \(\partial p^\top q/\partial q\) at 580, and the first-order condition of the planning problem at 1141. A reader who takes the three formulas on faith can follow the whole lecture.

  • The quadratic-utility device is used to make three apparently different economies the same object, and each mapping is written out rather than asserted: dynamics by setting \(\Pi = \mathrm{diag}(1, \sqrt\beta)\) and \(b = (b_1, \sqrt\beta b_2)\) (330-343), risk by setting \(\Pi = \mathrm{diag}(\sqrt\lambda, \sqrt{1-\lambda})\) (412-423), and each is then instantiated in the same ExchangeEconomy class (377, 464) so the reader sees one implementation serve both.

  • The dynamic reinterpretation is carried all the way to its economic content: 345-363 shows the budget constraint becoming a present-value identity, names both sides, gives the units of the relative price (“time \(2\) goods per unit of time \(1\) goods”), and only then identifies \(p_1/p_2\) as the gross interest rate - which is the step that makes the trick more than an algebraic coincidence.

  • The Marshallian/Hicksian distinction is drawn in terms of what is held fixed and what is solved for, with the mental experiment spelled out for each (287-302), including the observation that a Hicksian curve leaves \(p^\top e + w\) unaltered “because we freeze \(\mu\) and solve for \(w\)” - a genuinely clarifying sentence that most treatments at this level omit.

  • ExchangeEconomy and ProductionEconomy both check their own maintained assumption and fail loudly rather than returning nonsense: the non-satiation threshold at 245-246 (“set bliss points further away”), the negative-allocation check at 266-268 that prints the offending allocation before raising, and the same non-satiation check at 690-691 and 999-1000 - which matters here because {eq}`eq:bversusc` at 131 is exactly the condition that makes equilibrium prices positive.

  • Monopoly subclasses ProductionEconomy (972-983) so that the monopoly and competitive outcomes are computed from one set of parameters by one object, which is why the comparison at 1082-1089 and the two-equilibrium figure at 1049-1055 are guaranteed to be like-for-like.

  • The lecture closes the loop it opened: 52-54 states the two welfare theorems, and 1115-1148 poses the planning problem, differentiates it, and shows the first-order condition implies {eq}`eq:old5p` - the competitive equilibrium quantity derived 500 lines earlier - concluding “this is another version of the first welfare theorem”.