#!/usr/bin/env python3 """Figure ch03 — Modèle des quatre quadrants (DiPasquale–Wheaton) tracé à partir des paramètres pédagogiques du marché type (analysis/python/cases/ch03_cycles.py, SYNTHÉTIQUE). Équilibre de référence (trait plein) et nouvel équilibre après une baisse du taux de capitalisation de 5 % à 4 % (tirets), avec la réaction de court terme (stock fixe) en pointillé. Les quatre demi-axes ont des échelles propres (S en milliers de logements, P en k$/logement, R en k$/logement/an, C en milliers de logements/an) ; les graduations sont dessinées explicitement. Sortie : assets/figures/ch03/ch03-fig-quatre-quadrants.pdf""" import sys, pathlib ROOT = pathlib.Path(__file__).resolve().parents[3] sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) import numpy as np, matplotlib.pyplot as plt from matplotlib.lines import Line2D from traite.style import setup, palette, savefig, LARGEUR import ch03_cycles as cas # facteurs d'affichage : unités de données → unités du tracé FS = 1.0 / 1000.0 # S : logements → milliers (axe x positif, 0..150) FP = 1.0 / 3000.0 # P : $ → k$/3 (axe x négatif ; 420 k$ → 140 unités) FR = 1.0 / 1000.0 # R : $/an → k$ (axe y positif, 0..24) FC = 4.0 / 1000.0 # C : logements/an → milliers × 4 (axe y négatif ; 3 000/an → 12 unités) def main(): setup() p = cas.Parametres4Q() base = cas.equilibre_4q(p); i2 = 0.04 nouv = cas.equilibre_4q(p, i=i2); ct = cas.court_terme_4q(p, i=i2) c0, c1 = palette.cat[0], palette.cat[1] fig, ax = plt.subplots(figsize=(LARGEUR, LARGEUR * 0.9)) XP, XS, YR, YC = -150, 150, 24, -13.5 ax.set_xlim(XP - 2, XS + 2); ax.set_ylim(YC - 1, YR + 1) ax.axhline(0, color=palette.ink, lw=0.9); ax.axvline(0, color=palette.ink, lw=0.9) ax.grid(False); ax.set_xticks([]); ax.set_yticks([]) for s in ax.spines.values(): s.set_visible(False) # graduations manuelles for v in (50, 100, 150): 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) for v in (150, 300, 450): 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) for v in (6, 12, 18, 24): 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) for v in (1, 2, 3): 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) # --- NE : marché de l'espace R = (a − S)/b S = np.linspace(50_000, 150_000, 50) ax.plot(S * FS, (p.a - S) / p.b * FR, color=c0, lw=1.8) # --- NW : marché des actifs P = R/i R = np.linspace(0, YR / FR, 50) ax.plot(-R / p.i * FP, R * FR, color=c0, lw=1.8) ax.plot(-R / i2 * FP, R * FR, color=c1, lw=1.8, ls="--") # --- SW : construction C = (P − c0)/c1 P = np.linspace(p.c0, 450_000, 50) ax.plot(-P * FP, -(P - p.c0) / p.c1 * FC, color=c0, lw=1.8) # --- SE : stock stationnaire S = C/δ C = np.linspace(0, 1_500, 50) ax.plot(C / p.delta * FS, -C * FC, color=c0, lw=1.8) def rectangle(eq, color, ls, lw=1.0): Sx, Rx, Px, Cx = eq["S"] * FS, eq["R"] * FR, eq["P"] * FP, eq["C"] * FC ax.plot([Sx, Sx], [-Cx, Rx], color=color, lw=lw, ls=ls) ax.plot([-Px, Sx], [Rx, Rx], color=color, lw=lw, ls=ls) ax.plot([-Px, -Px], [Rx, -Cx], color=color, lw=lw, ls=ls) ax.plot([-Px, Sx], [-Cx, -Cx], color=color, lw=lw, ls=ls) for x, y in ((Sx, Rx), (-Px, Rx), (-Px, -Cx), (Sx, -Cx)): ax.plot(x, y, "o", color=color, ms=3.8) rectangle(base, c0, "-") rectangle(nouv, c1, "--") # court terme : stock fixe, P bondit à R0/i2, C bondit, mais S n'a pas encore bougé Pct, Cct = ct["P"] * FP, ct["C"] * FC ax.plot([-Pct, base["S"] * FS], [base["R"] * FR, base["R"] * FR], color=c1, lw=1.0, ls=":") ax.plot([-Pct, -Pct], [base["R"] * FR, -Cct], color=c1, lw=1.0, ls=":") ax.plot([-Pct, base["S"] * FS], [-Cct, -Cct], color=c1, lw=1.0, ls=":") 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") # titres des quadrants (coins) et noms d'axes ax.text(XS, YR, "Marché de l'espace\n$D(R) = S$", ha="right", va="top", fontsize=8, color=palette.ink2) ax.text(XP, 9.0, "Marché des actifs\n$P = R/i$", ha="left", va="center", fontsize=8, color=palette.ink2) ax.text(XP, YC + 4.0, "Construction\n$C = (P - c_0)/c_1$", ha="left", va="bottom", fontsize=8, color=palette.ink2) ax.text(XS, YC, "Ajustement du stock\n$C = \\delta S$", ha="right", va="bottom", fontsize=8, color=palette.ink2) ax.text(XS, 1.0, "Stock $S$\n(milliers de logements)", ha="right", va="bottom", fontsize=7.5) ax.text(XP, 1.0, "Prix $P$\n(k\\$ par logement)", ha="left", va="bottom", fontsize=7.5) ax.text(-2.5, YR, "Loyer $R$\n(k\\$/logement/an)", ha="right", va="top", fontsize=7.5) ax.text(-2.5, YC, "Construction $C$\n(milliers de logements/an)", ha="right", va="bottom", fontsize=7.5) # annotations numériques 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") 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") 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") 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") 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") ax.text(-Pct + 1.5, -Cct - 0.3, f"$C$ = {ct['C']/1000:.1f}", fontsize=7.5, color=c1, ha="left", va="top") 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") h = [Line2D([], [], color=c0, lw=1.8, label="équilibre de référence ($i$ = 5 %)"), Line2D([], [], color=c1, lw=1.8, ls="--", label="long terme après la baisse à $i$ = 4 %"), Line2D([], [], color=c1, lw=1.0, ls=":", label="court terme (stock fixe)")] ax.legend(handles=h, loc="upper center", bbox_to_anchor=(0.5, -0.01), ncol=3, fontsize=7.5, frameon=False) out = savefig(fig, "ch03/ch03-fig-quatre-quadrants.pdf"); print("OK", out) if __name__ == "__main__": main()