spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Figure ch03 — Modèle des quatre quadrants (DiPasquale–Wheaton) tracé à partir des paramètres pédagogiques du marché type3(analysis/python/cases/ch03_cycles.py, SYNTHÉTIQUE). Équilibre de référence (trait plein) et nouvel équilibre après une baisse4du taux de capitalisation de 5 % à 4 % (tirets), avec la réaction de court terme (stock fixe) en pointillé.5Les quatre demi-axes ont des échelles propres (S en milliers de logements, P en k$/logement, R en k$/logement/an,6C en milliers de logements/an) ; les graduations sont dessinées explicitement.7Sortie : assets/figures/ch03/ch03-fig-quatre-quadrants.pdf"""8import sys, pathlib9ROOT = pathlib.Path(__file__).resolve().parents[3]10sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases"))11import numpy as np, matplotlib.pyplot as plt12from matplotlib.lines import Line2D13from traite.style import setup, palette, savefig, LARGEUR14import ch03_cycles as cas1516# facteurs d'affichage : unités de données → unités du tracé17FS = 1.0 / 1000.0 # S : logements → milliers (axe x positif, 0..150)18FP = 1.0 / 3000.0 # P : $ → k$/3 (axe x négatif ; 420 k$ → 140 unités)19FR = 1.0 / 1000.0 # R : $/an → k$ (axe y positif, 0..24)20FC = 4.0 / 1000.0 # C : logements/an → milliers × 4 (axe y négatif ; 3 000/an → 12 unités)212223def main():24 setup()25 p = cas.Parametres4Q()26 base = cas.equilibre_4q(p); i2 = 0.0427 nouv = cas.equilibre_4q(p, i=i2); ct = cas.court_terme_4q(p, i=i2)28 c0, c1 = palette.cat[0], palette.cat[1]29 fig, ax = plt.subplots(figsize=(LARGEUR, LARGEUR * 0.9))30 XP, XS, YR, YC = -150, 150, 24, -13.531 ax.set_xlim(XP - 2, XS + 2); ax.set_ylim(YC - 1, YR + 1)32 ax.axhline(0, color=palette.ink, lw=0.9); ax.axvline(0, color=palette.ink, lw=0.9)33 ax.grid(False); ax.set_xticks([]); ax.set_yticks([])34 for s in ax.spines.values():35 s.set_visible(False)36 # graduations manuelles37 for v in (50, 100, 150):38 ax.plot([v, v], [0, -0.35], color=palette.ink, lw=0.7); ax.text(v, -0.6, f"{v}", ha="center", va="top", fontsize=7, color=palette.ink2)39 for v in (150, 300, 450):40 ax.plot([-v / 3, -v / 3], [0, -0.35], color=palette.ink, lw=0.7); ax.text(-v / 3, -0.6, f"{v}", ha="center", va="top", fontsize=7, color=palette.ink2)41 for v in (6, 12, 18, 24):42 ax.plot([0, -1.5], [v, v], color=palette.ink, lw=0.7); ax.text(-2.5, v, f"{v}", ha="right", va="center", fontsize=7, color=palette.ink2)43 for v in (1, 2, 3):44 ax.plot([0, -1.5], [-4 * v, -4 * v], color=palette.ink, lw=0.7); ax.text(-2.5, -4 * v, f"{v}", ha="right", va="center", fontsize=7, color=palette.ink2)45 # --- NE : marché de l'espace R = (a − S)/b46 S = np.linspace(50_000, 150_000, 50)47 ax.plot(S * FS, (p.a - S) / p.b * FR, color=c0, lw=1.8)48 # --- NW : marché des actifs P = R/i49 R = np.linspace(0, YR / FR, 50)50 ax.plot(-R / p.i * FP, R * FR, color=c0, lw=1.8)51 ax.plot(-R / i2 * FP, R * FR, color=c1, lw=1.8, ls="--")52 # --- SW : construction C = (P − c0)/c153 P = np.linspace(p.c0, 450_000, 50)54 ax.plot(-P * FP, -(P - p.c0) / p.c1 * FC, color=c0, lw=1.8)55 # --- SE : stock stationnaire S = C/δ56 C = np.linspace(0, 1_500, 50)57 ax.plot(C / p.delta * FS, -C * FC, color=c0, lw=1.8)5859 def rectangle(eq, color, ls, lw=1.0):60 Sx, Rx, Px, Cx = eq["S"] * FS, eq["R"] * FR, eq["P"] * FP, eq["C"] * FC61 ax.plot([Sx, Sx], [-Cx, Rx], color=color, lw=lw, ls=ls)62 ax.plot([-Px, Sx], [Rx, Rx], color=color, lw=lw, ls=ls)63 ax.plot([-Px, -Px], [Rx, -Cx], color=color, lw=lw, ls=ls)64 ax.plot([-Px, Sx], [-Cx, -Cx], color=color, lw=lw, ls=ls)65 for x, y in ((Sx, Rx), (-Px, Rx), (-Px, -Cx), (Sx, -Cx)):66 ax.plot(x, y, "o", color=color, ms=3.8)6768 rectangle(base, c0, "-")69 rectangle(nouv, c1, "--")70 # court terme : stock fixe, P bondit à R0/i2, C bondit, mais S n'a pas encore bougé71 Pct, Cct = ct["P"] * FP, ct["C"] * FC72 ax.plot([-Pct, base["S"] * FS], [base["R"] * FR, base["R"] * FR], color=c1, lw=1.0, ls=":")73 ax.plot([-Pct, -Pct], [base["R"] * FR, -Cct], color=c1, lw=1.0, ls=":")74 ax.plot([-Pct, base["S"] * FS], [-Cct, -Cct], color=c1, lw=1.0, ls=":")75 ax.plot(-Pct, base["R"] * FR, "o", color=c1, ms=3.8, mfc="white"); ax.plot(-Pct, -Cct, "o", color=c1, ms=3.8, mfc="white")76 # titres des quadrants (coins) et noms d'axes77 ax.text(XS, YR, "Marché de l'espace\n$D(R) = S$", ha="right", va="top", fontsize=8, color=palette.ink2)78 ax.text(XP, 9.0, "Marché des actifs\n$P = R/i$", ha="left", va="center", fontsize=8, color=palette.ink2)79 ax.text(XP, YC + 4.0, "Construction\n$C = (P - c_0)/c_1$", ha="left", va="bottom", fontsize=8, color=palette.ink2)80 ax.text(XS, YC, "Ajustement du stock\n$C = \\delta S$", ha="right", va="bottom", fontsize=8, color=palette.ink2)81 ax.text(XS, 1.0, "Stock $S$\n(milliers de logements)", ha="right", va="bottom", fontsize=7.5)82 ax.text(XP, 1.0, "Prix $P$\n(k\\$ par logement)", ha="left", va="bottom", fontsize=7.5)83 ax.text(-2.5, YR, "Loyer $R$\n(k\\$/logement/an)", ha="right", va="top", fontsize=7.5)84 ax.text(-2.5, YC, "Construction $C$\n(milliers de logements/an)", ha="right", va="bottom", fontsize=7.5)85 # annotations numériques86 ax.text(base["S"] * FS + 1.5, base["R"] * FR + 0.4, f"$S^*$ = {base['S']/1000:.0f} ; $R^*$ = {base['R']/1000:.1f}", fontsize=7.5, color=c0, ha="left", va="bottom")87 ax.text(nouv["S"] * FS + 1.5, nouv["R"] * FR - 0.4, f"{nouv['S']/1000:.1f} ; {nouv['R']/1000:.1f}", fontsize=7.5, color=c1, ha="left", va="top")88 ax.text(-base["P"] * FP + 1.5, base["R"] * FR + 0.4, f"$P^*$ = {base['P']/1000:.0f}", fontsize=7.5, color=c0, ha="left", va="bottom")89 ax.text(-nouv["P"] * FP - 1.5, nouv["R"] * FR - 0.4, f"{nouv['P']/1000:.1f}", fontsize=7.5, color=c1, ha="right", va="top")90 ax.text(-Pct, base["R"] * FR + 1.6, f"court terme :\n$P$ = {ct['P']/1000:.0f}", fontsize=7.5, color=c1, ha="center", va="bottom")91 ax.text(-Pct + 1.5, -Cct - 0.3, f"$C$ = {ct['C']/1000:.1f}", fontsize=7.5, color=c1, ha="left", va="top")92 ax.text(base["S"] * FS + 1.5, -base["C"] * FC - 0.3, f"$C^*$ = {base['C']/1000:.1f}", fontsize=7.5, color=c0, ha="left", va="top")93 h = [Line2D([], [], color=c0, lw=1.8, label="équilibre de référence ($i$ = 5 %)"),94 Line2D([], [], color=c1, lw=1.8, ls="--", label="long terme après la baisse à $i$ = 4 %"),95 Line2D([], [], color=c1, lw=1.0, ls=":", label="court terme (stock fixe)")]96 ax.legend(handles=h, loc="upper center", bbox_to_anchor=(0.5, -0.01), ncol=3, fontsize=7.5, frameon=False)97 out = savefig(fig, "ch03/ch03-fig-quatre-quadrants.pdf"); print("OK", out)9899100if __name__ == "__main__":101 main()102