07. 非線形方程式の数値解法
テーマ: 需要・供給の均衡価格と、従価税の帰着(tax incidence)
学習点: 無名関数(lambda), 高階関数, 二分法, 弾力性の数値微分
依存: 標準ライブラリのみ / 難易度: 基礎
実行方法
uv run 07_market_equilibrium.py
スクリプト冒頭の PEP 723 メタデータ(# /// script)により、必要なライブラリは
uv が自動的に仮想環境へ導入します。事前の pip install は不要です。
解説
何をするプログラムか
ミクロ経済学の需要・供給モデルを数値的に解くスクリプトです。需要関数 Qd(p) = 1000·p^(−1.2)、供給関数 Qs(p) = 12·p^(0.8) という定弾力性型(べき乗型)の関数を使うため、均衡価格は手計算では求めにくく、二分法という数値解法で Qd(p) = Qs(p) を満たす価格を探します。
後半では従価税(価格に比例する税、消費税がその代表例です)を導入し、税率を 0〜30% まで変えたときの消費者価格・生産者価格・取引数量・税収を計算します。「税は法律上誰が納めるかではなく、需要と供給の弾力性の比で実質的な負担が決まる」という税の帰着(tax incidence)の理論を、数値で検証するのが狙いです。
コードの読みどころ
- 需要・供給関数は
demand: Demand = lambda p: 1000 * p ** -1.2のように無名関数(lambda)で 1 行定義されています。冒頭のDemand = Callable[[float], float]は「float を受け取り float を返す関数」という型の別名で、型ヒントの読みやすさを高めています。 solve(f, lo, hi)は「関数 f を引数に取る関数」つまり高階関数です。均衡計算ではsolve(lambda p: demand(p) - supply(p), 0.01, 1000)と、超過需要をその場で lambda にして渡しています。- 二分法の本体は、区間の中点
midで符号を調べ、flo * fm < 0なら解は左半分にあるとしてhi = mid、そうでなければloを進める、の繰り返しです。区間幅が許容誤差tol=1e-10を下回るまで確実に解へ挟み込みます。 elasticity()は導関数を解析的に求める代わりに、中心差分(q(p+h) - q(p-h)) / (2h)で数値微分し、点弾力性 (dQ/dp)·(p/Q) を計算します。どんな関数を渡しても動く汎用的な書き方です。- 課税後の均衡は
solve(lambda p: demand(p) - supply(p / (1 + t)), ...)の 1 行で解いています。消費者が払う価格 p と生産者が受け取る価格 p/(1+t) の乖離が、lambda の中にそのまま表現されています。 - 税率 0% のとき負担率は定義できないため
float("nan")を入れており、出力にnan%と表示されます。
理論的背景
| 均衡は Qd(p) = Qs(p) を満たす価格で決まります。従価税 t を課すと、消費者の支払う価格 p と生産者の受け取る価格 p/(1+t) が分離し、取引数量は減少します。このとき消費者価格の上昇分が税全体に占める割合(消費者負担率)は、線形近似のもとで Es / (Es + | Ed | ) となることが知られています。本例では Ed = −1.2、Es = +0.8 なので理論値は 0.8/(0.8+1.2) = 40% です。需要が弾力的(買い手が逃げやすい)ほど消費者負担は小さく、供給が弾力的なほど生産者負担が小さくなります。 |
実行結果の見方
まず均衡価格 9.1287、均衡数量 70.3896 が求まり、数値微分による弾力性が設定どおり −1.200 と +0.800 になっていることを確認してください。従価税の表では、税率 5% のとき消費者負担率は 40.6% で理論値 40.0% にほぼ一致し、税率が 30% に上がると 43.2% へと乖離が広がります。弾力性比の公式はあくまで小さな税に対する線形近似であることが、最終行の注記とともに読み取れます。また税率が上がるほど数量が 70.39 から 62.06 へ減っており、課税が取引そのものを縮小させる(死荷重を生む)様子も観察できます。
ソースコード
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""07: 非線形方程式の数値解法 ---------------------------------------------
テーマ: 需要・供給の均衡価格と、従価税の帰着(tax incidence)
学習点: 無名関数(lambda), 高階関数, 二分法, 弾力性の数値微分
根拠: 均衡は Qd(p)=Qs(p)。従価税 t を課すと消費者価格 p と生産者価格 p/(1+t)
が乖離する。税の負担配分は需要・供給の価格弾力性の比で決まり、
消費者負担割合 = Es / (Es + |Ed|) が成り立つ(線形近似)。
"""
from typing import Callable
Demand = Callable[[float], float]
Supply = Callable[[float], float]
def solve(f: Callable[[float], float], lo: float, hi: float, tol=1e-10) -> float:
"""f(lo) と f(hi) の符号が異なる区間で f(x)=0 を二分法で解く。"""
flo = f(lo)
for _ in range(300):
mid = (lo + hi) / 2
fm = f(mid)
if abs(fm) < tol or hi - lo < tol:
return mid
if flo * fm < 0:
hi = mid
else:
lo, flo = mid, fm
return (lo + hi) / 2
def elasticity(q: Callable[[float], float], p: float, h=1e-5) -> float:
"""点弾力性 (dQ/dp)*(p/Q) を中心差分で数値的に求める。"""
dq = (q(p + h) - q(p - h)) / (2 * h)
return dq * p / q(p)
def main() -> None:
# 非線形(定弾力性型)の需要・供給関数
demand: Demand = lambda p: 1000 * p ** -1.2 # 需要の価格弾力性 -1.2
supply: Supply = lambda p: 12 * p ** 0.8 # 供給の価格弾力性 +0.8
p_star = solve(lambda p: demand(p) - supply(p), 0.01, 1000)
q_star = demand(p_star)
print(f"均衡価格 p* = {p_star:.4f} / 均衡数量 q* = {q_star:.4f}")
print(f"需要の弾力性 Ed = {elasticity(demand, p_star):+.3f}")
print(f"供給の弾力性 Es = {elasticity(supply, p_star):+.3f}\n")
print("[従価税の帰着] 税率を変えたときの価格・数量・税収")
print(f"{'税率':>6}{'消費者価格':>12}{'生産者価格':>12}{'数量':>10}"
f"{'税収':>11}{'消費者負担率':>13}")
ed, es = abs(elasticity(demand, p_star)), elasticity(supply, p_star)
theory = es / (es + ed)
for t in [0.00, 0.05, 0.10, 0.20, 0.30]:
# 消費者が払う価格 p、生産者受取 p/(1+t)
p = solve(lambda p: demand(p) - supply(p / (1 + t)), 0.01, 1000)
pp = p / (1 + t)
q = demand(p)
rev = (p - pp) * q
share = (p - p_star) / (p - pp) if t > 0 else float("nan")
print(f"{t:>6.0%}{p:>12.4f}{pp:>12.4f}{q:>10.4f}{rev:>11.4f}"
f"{share:>13.1%}")
print(f"\n理論値(弾力性比による消費者負担率)= Es/(Es+|Ed|) = {theory:.1%}")
print("※ 税率が小さいほど数値結果は線形近似の理論値に一致する。")
if __name__ == "__main__":
main()
実行結果
均衡価格 p* = 9.1287 / 均衡数量 q* = 70.3896
需要の弾力性 Ed = -1.200
供給の弾力性 Es = +0.800
[従価税の帰着] 税率を変えたときの価格・数量・税収
税率 消費者価格 生産者価格 数量 税収 消費者負担率
0% 9.1287 9.1287 70.3896 0.0000 nan%
5% 9.3086 8.8653 68.7603 30.4792 40.6%
10% 9.4835 8.6213 67.2419 57.9714 41.1%
20% 9.8193 8.1828 64.4914 105.5437 42.2%
30% 10.1388 7.7991 62.0606 145.2047 43.2%
理論値(弾力性比による消費者負担率)= Es/(Es+|Ed|) = 40.0%
※ 税率が小さいほど数値結果は線形近似の理論値に一致する。