Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

노트북은 예측 → 계산 → 대조 세 부분으로 고정한다. 새 개념은 도입하지 않는다. 본문에서 이미 유도한 것을 수치로 확인할 뿐이다.

1. 예측 (코드를 쓰기 전에)

아래 칸을 먼저 채운다. 계산하지 않는다. 파라미터는 u=x10.4x20.6u=x_1^{0.4}x_2^{0.6}, p=(1,2)p=(1,2), w=100w=100이다.

  1. 지출함수를 무차별곡선 위에서 직접 최소화해 얻은 e(p1,uˉ)e(p_1,\bar u)의 중심차분 기울기(스텝 10-3)는 h1=40.0h_1=40.0과 상대오차 10-5 이내로 일치한다.

    예측: ____

  2. Δp1{0.01,0.1,0.5}\Delta p_1\in\{0.01,\,0.1,\,0.5\}에서 ph(p)e(p)p'\cdot h(p)-e(p')는 약 0.0012, 0.114, 2.39이고, 0.01–0.1 구간의 log-log 기울기는 1.98로 2에 접근한다. 0.1–0.5 구간은 1.89로 2에서 벗어난다 — 3차항이다.

    예측: ____

  3. 효용극대화를 예산선 위에서 직접 풀어 얻은 v(p,w)v(p,w)의 중심차분 v/w\partial v/\partial w는 1계 조건의 λ=u1(x)/p1=0.3366\lambda^{*}=u_1(x^{*})/p_1=0.3366과 일치하고, (v/p1)/(v/w)=40.0=x1-(\partial v/\partial p_1)/(\partial v/\partial w)=40.0=x_1^{*}이다(Roy).

    예측: ____

2. 계산

패키지 호출로 답을 내지 않는다. 예산선과 무차별곡선 위의 1변수 문제를 격자 탐색·황금분할·중심차분으로 직접 쌓는다. 닫힌 형태는 쓰지 않는다 — 수치가 먼저 답하고 본문 식이 그 답을 설명한다.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager
from matplotlib.ticker import FuncFormatter, LogLocator, NullFormatter

# 한국어 글꼴: 후보 가운데 설치된 첫 번째를 고른다
have = {f.name for f in font_manager.fontManager.ttflist}
for cand in ['Apple SD Gothic Neo', 'AppleGothic', 'NanumGothic',
             'Noto Sans CJK KR', 'Malgun Gothic']:
    if cand in have:
        plt.rcParams['font.family'] = cand
        break
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['mathtext.fontset'] = 'cm'
plt.rcParams['figure.dpi'] = 110

# 문제의 파라미터
a, p1, p2, w = 0.4, 1.0, 2.0, 100.0
print('글꼴 :', plt.rcParams['font.family'][0])
print('파라미터 : a =', a, ', p = (%g, %g)' % (p1, p2), ', w =', w)
글꼴 : Apple SD Gothic Neo
파라미터 : a = 0.4 , p = (1, 2) , w = 100.0
GR = (np.sqrt(5.0) - 1.0) / 2.0


def golden_min(fun, lo, hi, tol=1e-11, maxit=400):
    """단봉 함수의 최솟점을 황금분할로 직접 좁힌다."""
    A, B = lo, hi
    c, d = B - GR * (B - A), A + GR * (B - A)
    fc, fd = fun(c), fun(d)
    for _ in range(maxit):
        if abs(B - A) < tol * max(1.0, abs(A) + abs(B)):
            break
        if fc < fd:
            B, d, fd = d, c, fc
            c = B - GR * (B - A)
            fc = fun(c)
        else:
            A, c, fc = c, d, fd
            d = A + GR * (B - A)
            fd = fun(d)
    x = 0.5 * (A + B)
    return x, fun(x)


def grid_then_golden(fun, lo, hi, n=5000):
    """격자 n점에서 최솟값의 이웃 구간을 고른 뒤 황금분할로 조인다."""
    xs = np.linspace(lo, hi, n)
    i = int(np.argmin(fun(xs)))
    return golden_min(fun, xs[max(i - 1, 0)], xs[min(i + 1, n - 1)])
def utility(x1, x2):
    """Cobb-Douglas 효용을 직접 쌓는다."""
    return x1 ** a * x2 ** (1.0 - a)


def umax(q1, q2, inc):
    """예산선 x2 = (w - p1 x1) / p2 위로 1변수화해 효용을 최대화한다."""
    def negu(x1):
        return -utility(x1, (inc - q1 * x1) / q2)

    lo, hi = 1e-6 * inc / q1, (1.0 - 1e-6) * inc / q1
    x1s, f = grid_then_golden(negu, lo, hi)
    return x1s, (inc - q1 * x1s) / q2, -f


def emin(q1, q2, ubar):
    """무차별곡선 x2 = (ubar / x1^a)^(1/(1-a)) 위에서 지출 p·x를 최소화한다."""
    def cost(x1):
        return q1 * x1 + q2 * (ubar / x1 ** a) ** (1.0 / (1.0 - a))

    x1s, e = grid_then_golden(cost, 0.05 * ubar, 20.0 * ubar)
    return x1s, (ubar / x1s ** a) ** (1.0 / (1.0 - a)), e


def cdiff(fun, x0, h=1e-3):
    """중심차분을 직접 쓴다."""
    return (fun(x0 + h) - fun(x0 - h)) / (2.0 * h)
# 기준점: 소비자 문제를 풀고 쌍대점 e(p, v(p,w)) = w 를 확인한다
x1s, x2s, v0 = umax(p1, p2, w)
lam = a * v0 / x1s / p1          # 1계 조건의 u_1(x*)/p_1
ubar = v0                        # 목표 효용을 최적 효용으로 둔다
h1n, h2n, e0 = emin(p1, p2, ubar)

print('%-22s %s' % ('x* (마셜 수요)', '(%.4f, %.4f)' % (x1s, x2s)))
print('%-22s %.4f' % ('v(p,w) = ubar', v0))
print('%-22s %.5f   (v/w = %.5f)' % ('lambda* = u_1/p_1', lam, v0 / w))
print('%-22s %s' % ('h (힉스 수요)', '(%.4f, %.4f)' % (h1n, h2n)))
print('%-22s %.4f   (w = %.1f)' % ('e(p, ubar)', e0, w))
x* (마셜 수요)             (40.0000, 30.0000)
v(p,w) = ubar          33.6587
lambda* = u_1/p_1      0.33659   (v/w = 0.33659)
h (힉스 수요)              (40.0000, 30.0000)
e(p, ubar)             100.0000   (w = 100.0)
# 예측 1 — 지출함수의 가격 기울기가 힉스 수요인가
de_dp1 = cdiff(lambda q: emin(q, p2, ubar)[2], p1, h=1e-3)
rel1 = abs(de_dp1 - h1n) / h1n

print('[예측 1] Shephard  de/dp1 = h1')
print('  중심차분 de/dp1 (스텝 1e-3) = %.6f' % de_dp1)
print('  최소화 해 h1                = %.6f' % h1n)
print('  상대오차                    = %.2e   (기준 1e-5 : %s)'
      % (rel1, '이내' if rel1 < 1e-5 else '초과'))
[예측 1] Shephard  de/dp1 = h1
  중심차분 de/dp1 (스텝 1e-3) = 40.000006
  최소화 해 h1                = 40.000001
  상대오차                    = 1.40e-07   (기준 1e-5 : 이내)
# 예측 2 — 재최적화 이득이 (Δp1)^2 로 줄어드는가
step = 1e-2
e_pp = (emin(p1 + step, p2, ubar)[2] - 2.0 * e0 + emin(p1 - step, p2, ubar)[2]) / step ** 2
coef = -0.5 * e_pp               # (eq-w05-17) 의 2차항 계수

print('[예측 2] 1차는 사라지고 2차가 남는다   (-1/2 dh1/dp1 = %.3f)' % coef)
print('%6s %12s %12s %10s %10s %9s' % ('dp1', "p'·h(p)", "e(p')", '차', '2차근사', '차/직접'))
gaps = {}
for d in (0.01, 0.1, 0.5):
    Lp = (p1 + d) * h1n + p2 * h2n
    ep = emin(p1 + d, p2, ubar)[2]
    gaps[d] = Lp - ep
    print('%6g %12.4f %12.4f %10.5f %10.5f %8.2f%%'
          % (d, Lp, ep, gaps[d], coef * d * d, 100.0 * gaps[d] / (h1n * d)))

s_small = np.log(gaps[0.1] / gaps[0.01]) / np.log(0.1 / 0.01)
s_large = np.log(gaps[0.5] / gaps[0.1]) / np.log(0.5 / 0.1)
print('  log-log 기울기  0.01-0.1 = %.2f     0.1-0.5 = %.2f' % (s_small, s_large))
[예측 2] 1차는 사라지고 2차가 남는다   (-1/2 dh1/dp1 = 12.000)
   dp1      p'·h(p)        e(p')          차       2차근사      차/직접
  0.01     100.4000     100.3988    0.00119    0.00120     0.30%
   0.1     104.0000     103.8860    0.11399    0.12000     2.85%
   0.5     120.0000     117.6079    2.39210    3.00010    11.96%
  log-log 기울기  0.01-0.1 = 1.98     0.1-0.5 = 1.89
# 예측 3 — 소득의 잠재가격과 Roy 항등식
dv_dw = cdiff(lambda inc: umax(p1, p2, inc)[2], w, h=1e-3)
dv_dp1 = cdiff(lambda q: umax(q, p2, w)[2], p1, h=1e-3)
roy = -dv_dp1 / dv_dw
mu = cdiff(lambda u_: emin(p1, p2, u_)[2], ubar, h=1e-3)

print('[예측 3] dv/dw = lambda* 와 Roy')
print('  중심차분 dv/dw        = %.6f' % dv_dw)
print('  1계 조건 lambda*      = %.6f   상대오차 %.2e' % (lam, abs(dv_dw - lam) / lam))
print('  중심차분 dv/dp1       = %.4f   공식 -lambda* x1* = %.4f' % (dv_dp1, -lam * x1s))
print('  Roy -(dv/dp1)/(dv/dw) = %.4f   x1* = %.4f' % (roy, x1s))
print('  mu* = de/dubar        = %.4f   1/lambda* = %.4f   곱 %.4f'
      % (mu, 1.0 / lam, lam * mu))
[예측 3] dv/dw = lambda* 와 Roy
  중심차분 dv/dw        = 0.336587
  1계 조건 lambda*      = 0.336587   상대오차 2.41e-08
  중심차분 dv/dp1       = -13.4635   공식 -lambda* x1* = -13.4635
  Roy -(dv/dp1)/(dv/dw) = 40.0000   x1* = 40.0000
  mu* = de/dubar        = 2.9710   1/lambda* = 2.9710   곱 1.0000
# 그림 1 — 지출함수는 장바구니 고정 직선족의 아래쪽 포락선이다
grid = np.linspace(0.4, 2.0, 120)
ecurve = np.array([emin(q, p2, ubar)[2] for q in grid])

fig, ax = plt.subplots(figsize=(6.4, 4.0))
for phat in (0.6, 1.0, 1.6):
    g1, g2, ee = emin(phat, p2, ubar)
    ax.plot(grid, grid * g1 + p2 * g2, lw=1.0, color='0.6')
    ax.plot([phat], [ee], 'o', color='crimson', ms=4, zorder=5)
ax.plot(grid, ecurve, lw=2.2, color='C0')
ax.plot([], [], lw=1.0, color='0.6', label='장바구니를 고정한 직선')
ax.plot(grid[:0], ecurve[:0], lw=2.2, color='C0', label='$e(p_1,\\bar u)$ — 직접 최소화')
ax.set_xlabel('가격 $p_1$')
ax.set_ylabel('지출')
ax.set_ylim(60, 150)
ax.legend(fontsize=9, loc='upper left')
ax.set_title('접점의 기울기 $h_1$ = %.2f' % h1n)
plt.show()
<Figure size 704x440 with 1 Axes>
# 그림 2 — 재최적화 이득과 직접효과를 log-log 로 가른다
dps = np.logspace(-3, 0, 25)
gapcurve = np.array([(p1 + d) * h1n + p2 * h2n - emin(p1 + d, p2, ubar)[2] for d in dps])

fig, ax = plt.subplots(figsize=(6.4, 4.0))
ax.loglog(dps, h1n * dps, lw=1.6, color='seagreen',
          label='직접효과 $h_1\\Delta p_1$ (기울기 1)')
ax.loglog(dps, gapcurve, lw=2.2, color='C0',
          label="재최적화 이득 $p'\\cdot h(p)-e(p')$ (기울기 2)")
ax.loglog(dps, coef * dps ** 2, ':', color='k', lw=1.2,
          label='2차항 $%.0f\\,(\\Delta p_1)^2$' % coef)
for d in (0.01, 0.1, 0.5):
    ax.plot([d], [gaps[d]], 'o', color='crimson', ms=5, zorder=5)
pow10 = FuncFormatter(lambda v, _: '$10^{%d}$' % int(round(np.log10(v))))
for axis in (ax.xaxis, ax.yaxis):      # 눈금을 10의 거듭제곱으로 직접 적는다
    axis.set_major_locator(LogLocator(base=10.0))
    axis.set_major_formatter(pow10)
    axis.set_minor_formatter(NullFormatter())
ax.set_xlabel('가격 변화 $\\Delta p_1$')
ax.set_ylabel('크기')
ax.legend(fontsize=8, loc='upper left')
ax.set_title('국소 기울기 %.2f (0.01-0.1) → %.2f (0.1-0.5)' % (s_small, s_large))
plt.show()
<Figure size 704x440 with 1 Axes>
# 예측 항목 셋에 대응하는 수치를 한자리에 모은다
print('%-4s %-46s %s' % ('항목', '계산값', '예측값'))
print('%-4s %-46s %s' % ('1',
      'de/dp1 = %.5f, h1 = %.5f, 상대오차 %.1e' % (de_dp1, h1n, rel1), 'h1 = 40.0, 1e-5 이내'))
print('%-4s %-46s %s' % ('2',
      '차 %.4f / %.3f / %.3f' % (gaps[0.01], gaps[0.1], gaps[0.5]), '0.0012 / 0.114 / 2.39'))
print('%-4s %-46s %s' % ('',
      '기울기 %.2f (0.01-0.1) / %.2f (0.1-0.5)' % (s_small, s_large), '1.98 / 1.89'))
print('%-4s %-46s %s' % ('3',
      'dv/dw = %.4f, lambda* = %.4f' % (dv_dw, lam), '0.3366'))
print('%-4s %-46s %s' % ('',
      'Roy = %.4f, x1* = %.4f' % (roy, x1s), '40.0'))
항목   계산값                                            예측값
1    de/dp1 = 40.00001, h1 = 40.00000, 상대오차 1.4e-07 h1 = 40.0, 1e-5 이내
2    차 0.0012 / 0.114 / 2.392                       0.0012 / 0.114 / 2.39
     기울기 1.98 (0.01-0.1) / 1.89 (0.1-0.5)           1.98 / 1.89
3    dv/dw = 0.3366, lambda* = 0.3366               0.3366
     Roy = 40.0000, x1* = 40.0000                   40.0

3. 대조

예측과 계산이 어긋난 지점을 적는다. 어느 쪽이 틀렸는지 판정한다. 어긋남의 후보 원인은 격자 해상도·차분 스텝·3차항 셋이다.

예측결과어긋남원인

본문 확인: 어긋났으면 (eq-w05-14)·(eq-w05-17)·(eq-w05-11)로 돌아간다.