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. 예측 (코드를 쓰기 전에)

아래 칸을 먼저 채운다. 계산하지 않는다.

  1. p=(2,1)p=(2,1), w=10w=10, u=x1x2u=\sqrt{x_1x_2}, 격자 간격 0.01: x=(2.5,5.0)x^{*}=(2.5,\,5.0), u3.54u^{*}\approx3.54, px=10p\cdot x^{*}=10(등호). 최대점이 격자 경계에 닿지 않는다.

    예측: ____

  2. p2p_210.101\to0.1\to0으로 보낸다(u=x1x2u=\sqrt{x_1x_2}): x2=550x_2^{*}=5\to50\to 격자 상한. p2=0p_2=0에서 격자 상한 X2X_210204010\to20\to40으로 두 배씩 키우면 x2x_2^{*}10204010\to20\to40으로 따라간다. p2>0p_2>0이면 X2w/p2X_2\ge w/p_2인 한 xx^{*}는 격자 상한과 무관하다. 같은 p2=0p_2=0에서 u=min{x1,x2}u=\min\{x_1,x_2\}로 바꾸면 u=5u^{*}=5이고, 최대점 집합 {(5,x2):5x2X2}\{(5,x_2): 5\le x_2\le X_2\}의 아래 끝 (5,5)(5,5)X2X_2를 키워도 그대로다 — 보증이 끊긴 자리에서 결론이 남는 사례다.

    예측: ____

  3. L=1L=1, p=2p=2, w=10w=10, u(x)=xu(x)=x이되 x=5x=5에서만 u=0u=0(A3 위반): 격자 간격 h=0.1,0.01,0.001h=0.1,\,0.01,\,0.001에서 격자 최대점 4.94.994.9994.9\to4.99\to4.999 — 5로 다가가되 5는 아니고, 상한 5는 달성되지 않는다. u=x(1,1)2u=-\lVert x-(1,1)\rVert^{2}(A4 위반): x=(1,1)x^{*}=(1,1), px=3<10p\cdot x^{*}=3<10 — 부등호가 유지된다. u=min{x1,x2}u=\min\{x_1,x_2\}(연속·미분불가·A4 성립): x=(3.33,3.33)x^{*}=(3.33,\,3.33)(이론값 10/310/3), px=9.9910p\cdot x^{*}=9.99\approx10 — 격자점 (3.33,3.34)(3.33,\,3.34)와 동률이다.

    예측: ____

2. 계산

패키지 호출로 답을 내지 않는다. 격자부터 직접 쌓는다 — 상자를 격자로 채우고, 예산 안의 점만 남기고, 가장 큰 uu를 고른다.

# 도구를 올리고 한국어 글꼴을 고른다
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager

CANDIDATES = ['Apple SD Gothic Neo', 'AppleGothic', 'NanumGothic',
              'Noto Sans CJK KR', 'Malgun Gothic']
available = {f.name for f in font_manager.fontManager.ttflist}
font = next((f for f in CANDIDATES if f in available), 'DejaVu Sans')
plt.rcParams['font.family'] = font
plt.rcParams['axes.unicode_minus'] = False
print('글꼴:', font)
글꼴: Apple SD Gothic Neo
# 상자를 격자로 채우고, 예산 안의 점만 남기고, 가장 큰 u 를 고른다
H = 0.01      # 격자 간격
TOL = 1e-9    # 부동소수 여유


def grid_argmax(u, p, w, X1, X2, h=H):
    x1 = np.arange(round(X1 / h) + 1) * h
    x2 = np.arange(round(X2 / h) + 1) * h
    G1, G2 = np.meshgrid(x1, x2, indexing='ij')
    inside = p[0] * G1 + p[1] * G2 <= w + TOL     # 예산제약 p.x <= w
    U = np.where(inside, u(G1, G2), -np.inf)      # 밖의 점은 후보에서 뺀다
    k = np.unravel_index(np.argmax(U), U.shape)
    return np.array([x1[k[0]], x2[k[1]]]), float(U[k]), U, (x1, x2)


def u_cd(a, b):        # sqrt(x1 x2)
    return np.sqrt(a * b)


def u_min(a, b):       # Leontief min{x1, x2}
    return np.minimum(a, b)


def u_sat(a, b):       # 포만점 (1,1)
    return -((a - 1.0) ** 2 + (b - 1.0) ** 2)

예측 1 — 기준 사례를 쌓는다. 상자 [0,w/p]\prod_\ell[0,w/p_\ell], 격자 최대점, 지출, 경계 접촉 여부를 함께 낸다.

# 기준 사례의 상자·최대점·지출·경계 접촉을 쌓는다
p, w = (2.0, 1.0), 10.0
X1, X2 = w / p[0], w / p[1]
xs, us, _, _ = grid_argmax(u_cd, p, w, X1, X2)
spend = p[0] * xs[0] + p[1] * xs[1]
touch = bool(abs(xs[0] - X1) < H or abs(xs[1] - X2) < H)
print(f'상자        = [0, {X1:.1f}] x [0, {X2:.1f}]')
print(f'x*          = ({xs[0]:.2f}, {xs[1]:.2f})')
print(f'u*          = {us:.4f}   (검산 sqrt(12.5) = {np.sqrt(12.5):.4f})')
print(f'p . x*      = {spend:.2f}   (w = {w:.0f}, 등호 여부 {abs(spend - w) < TOL})')
print(f'격자 경계 접촉 = {touch}')
상자        = [0, 5.0] x [0, 10.0]
x*          = (2.50, 5.00)
u*          = 3.5355   (검산 sqrt(12.5) = 3.5355)
p . x*      = 10.00   (w = 10, 등호 여부 True)
격자 경계 접촉 = False

예측 2p2p_2를 내리고, 격자 상한 X2X_2를 두 배씩 키우고, 효용함수를 바꾼다. 셋을 따로 돌린다.

# p2 를 1 -> 0.1 -> 0 으로 내리며 상자의 둘째 변과 x2* 를 쌓는다
print(f'{"p2":>5} {"상자 둘째 변":>12} {"X2":>6} {"x1*":>7} {"x2*":>8} {"p.x*":>7} {"상한 접촉":>9}')
for p2, X2g in ((1.0, 10.0), (0.1, 100.0), (0.0, 10.0)):
    edge = np.inf if p2 == 0.0 else w / p2
    xa, _, _, _ = grid_argmax(u_cd, (2.0, p2), w, 5.0, X2g)
    sp = 2.0 * xa[0] + p2 * xa[1]
    hit = bool(abs(xa[1] - X2g) < H)
    print(f'{p2:5.1f} {edge:12.1f} {X2g:6.0f} {xa[0]:7.2f} {xa[1]:8.2f} {sp:7.2f} {str(hit):>9}')
   p2      상자 둘째 변     X2     x1*      x2*    p.x*     상한 접촉
  1.0         10.0     10    2.50     5.00   10.00     False
  0.1        100.0    100    2.50    50.00   10.00     False
  0.0          inf     10    5.00    10.00   10.00      True
# 격자 상한 X2 를 두 배씩 키운다 — p2=0 과 p2=1 을 나란히 쌓는다
X2_LIST = [10.0, 20.0, 40.0]
top_zero, top_pos = [], []
for p2, box in ((0.0, top_zero), (1.0, top_pos)):
    line = []
    for X2g in X2_LIST:
        xb, _, _, _ = grid_argmax(u_cd, (2.0, p2), w, 5.0, X2g)
        box.append(xb[1])
        line.append(f'X2={X2g:.0f}: x*=({xb[0]:.2f}, {xb[1]:.2f})')
    print(f'p2={p2:.0f}, u=sqrt(x1x2)  ' + '   '.join(line))
fmt = lambda v: '[' + ', '.join(f'{z:.2f}' for z in v) + ']'
print(f'x2* 가 상한 {fmt(X2_LIST)} 를 따라가는가 — p2=0: {fmt(top_zero)}  p2=1: {fmt(top_pos)}')
p2=0, u=sqrt(x1x2)  X2=10: x*=(5.00, 10.00)   X2=20: x*=(5.00, 20.00)   X2=40: x*=(5.00, 40.00)
p2=1, u=sqrt(x1x2)  X2=10: x*=(2.50, 5.00)   X2=20: x*=(2.50, 5.00)   X2=40: x*=(2.50, 5.00)
x2* 가 상한 [10.00, 20.00, 40.00] 를 따라가는가 — p2=0: [10.00, 20.00, 40.00]  p2=1: [5.00, 5.00, 5.00]
# 같은 p2=0 에서 u=min 으로 바꾼다 — 최댓값과 최대점 집합의 아래 끝을 쌓는다
top_min = []
for X2g in X2_LIST:
    xc, uc, U, (g1, g2) = grid_argmax(u_min, (2.0, 0.0), w, 5.0, X2g)
    hit = np.argwhere(U >= uc - TOL)
    lo, hi = g2[hit[:, 1].min()], g2[hit[:, 1].max()]
    top_min.append(lo)
    print(f'X2={X2g:4.0f}  u*={uc:.2f}  아래 끝=({g1[hit[:, 0].min()]:.2f}, {lo:.2f})'
          f'  최대점 집합 x2 범위=[{lo:.2f}, {hi:.2f}]')
X2=  10  u*=5.00  아래 끝=(5.00, 5.00)  최대점 집합 x2 범위=[5.00, 10.00]
X2=  20  u*=5.00  아래 끝=(5.00, 5.00)  최대점 집합 x2 범위=[5.00, 20.00]
X2=  40  u*=5.00  아래 끝=(5.00, 5.00)  최대점 집합 x2 범위=[5.00, 40.00]

예측 3 — 가정을 하나씩 깬다. A3(연속)을 깨면 4단계가, A4(국소비포화)를 깨면 5단계가 무너진다. Leontief는 연속이므로 둘 다 선다.

# A3 위반: L=1, u(x)=x 이되 x=5 에서만 u=0 — 격자 간격을 줄이며 최대점을 쌓는다
H_LIST = [0.1, 0.01, 0.001]
top_h = []
for h in H_LIST:
    x = np.arange(round(5.0 / h) + 1) * h
    x = x[2.0 * x <= 10.0 + TOL]                       # 예산 2x <= 10
    u1 = np.where(np.abs(x - 5.0) < TOL, 0.0, x)       # x=5 에서만 값을 떨어뜨린다
    k = int(np.argmax(u1))
    top_h.append(x[k])
    print(f'h={h:<6} 격자 최대점 x*={x[k]:.3f}  u*={u1[k]:.3f}  상한 sup=5.000 (달성 안 됨)')
h=0.1    격자 최대점 x*=4.900  u*=4.900  상한 sup=5.000 (달성 안 됨)
h=0.01   격자 최대점 x*=4.990  u*=4.990  상한 sup=5.000 (달성 안 됨)
h=0.001  격자 최대점 x*=4.999  u*=4.999  상한 sup=5.000 (달성 안 됨)
# A4 위반: 포만점 (1,1) 이 예산집합 안에 있다 — 등호가 사라지는지 쌓는다
xd, ud, _, _ = grid_argmax(u_sat, p, w, 5.0, 10.0)
sp_d = 2.0 * xd[0] + xd[1]
print(f'x*=({xd[0]:.2f}, {xd[1]:.2f})  u*={ud if ud != 0.0 else 0.0:.4f}'
      f'  p.x*={sp_d:.2f} < w={w:.0f}  (등호 여부 {abs(sp_d - w) < TOL})')
x*=(1.00, 1.00)  u*=0.0000  p.x*=3.00 < w=10  (등호 여부 False)
# A4 성립·미분불가: Leontief 의 최대점과 동률 격자점을 쌓는다
xe, ue, U6, (g1, g2) = grid_argmax(u_min, p, w, 5.0, 10.0)
ties = np.argwhere(U6 >= ue - TOL)
print(f'x*=({xe[0]:.2f}, {xe[1]:.2f})  u*={ue:.2f}  이론값 10/3={10 / 3:.4f}'
      f'  p.x*={2 * xe[0] + xe[1]:.2f}')
print('동률 격자점:', ', '.join(f'({g1[i]:.2f}, {g2[j]:.2f})' for i, j in ties))
x*=(3.33, 3.33)  u*=3.33  이론값 10/3=3.3333  p.x*=9.99
동률 격자점: (3.33, 3.33), (3.33, 3.34)

세 예측을 한 장에 담는다.

# 세 예측을 한 장에 쌓는다: 상자 안의 최대점 · 상한을 키울 때 · 격자를 줄일 때
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12.6, 3.7))
t = np.linspace(0.0, 5.0, 200)
ax1.fill_between(t, 0.0, 10.0 - 2.0 * t, color='0.82')
ax1.plot([0, 5, 5, 0, 0], [0, 0, 10, 10, 0], ls='--', lw=1.0, color='0.35')
ax1.plot(xs[0], xs[1], 'o', color='crimson', zorder=5)
ax1.annotate(f'$x^*$=({xs[0]:.2f}, {xs[1]:.2f})', (xs[0], xs[1]), xytext=(2.6, 7.2), color='crimson')
ax1.set(xlabel='재화 1의 수량 $x_1$', ylabel='재화 2의 수량 $x_2$',
        title='예측 1 · 상자 안의 최대점', xlim=(0, 6), ylim=(0, 11.5))

ax2.plot(X2_LIST, top_zero, 'o-', label='$p_2=0$, $u=\\sqrt{x_1x_2}$')
ax2.plot(X2_LIST, top_min, 's--', label='$p_2=0$, $u=\\min$')
ax2.plot(X2_LIST, top_pos, '^:', label='$p_2=1$, $u=\\sqrt{x_1x_2}$')
ax2.set(xlabel='격자 상한 $X_2$', ylabel='$x_2^{*}$', title='예측 2 · 상한을 키울 때')
ax2.legend(fontsize=8)

ax3.plot(range(len(H_LIST)), top_h, 'o-', color='0.25')
ax3.axhline(5.0, ls='--', color='crimson')
ax3.text(0.55, 4.876, '상한 5 — 달성되지 않는다', color='crimson', fontsize=9)
ax3.set(xlabel='격자 간격 $h$', ylabel='격자 최대점 $x^{*}$', xticks=range(len(H_LIST)),
        xticklabels=['0.1', '0.01', '0.001'], ylim=(4.85, 5.05),
        title='예측 3 · 상한에 다가가되 닿지 않는다')
fig.tight_layout()
plt.show()
<Figure size 1260x370 with 3 Axes>

3. 대조

예측과 계산이 어긋난 지점을 적는다. 어느 쪽이 틀렸는지 판정한다.

예측결과어긋남원인

본문 확인: 어긋났으면 3절의 (eq-w01-9)로 돌아간다.