노트북은 예측 → 계산 → 대조 세 부분으로 고정한다. 새 개념은 도입하지 않는다. 본문에서 이미 유도한 것을 수치로 확인할 뿐이다.
1. 예측 (코드를 쓰기 전에)¶
아래 칸을 먼저 채운다. 계산하지 않는다.
, , , 격자 간격 0.01: , , (등호). 최대점이 격자 경계에 닿지 않는다.
예측: ____
를 으로 보낸다(): 격자 상한. 에서 격자 상한 를 으로 두 배씩 키우면 도 으로 따라간다. 이면 인 한 는 격자 상한과 무관하다. 같은 에서 로 바꾸면 이고, 최대점 집합 의 아래 끝 는 를 키워도 그대로다 — 보증이 끊긴 자리에서 결론이 남는 사례다.
예측: ____
, , , 이되 에서만 (A3 위반): 격자 간격 에서 격자 최대점 — 5로 다가가되 5는 아니고, 상한 5는 달성되지 않는다. (A4 위반): , — 부등호가 유지된다. (연속·미분불가·A4 성립): (이론값 ), — 격자점 와 동률이다.
예측: ____
2. 계산¶
패키지 호출로 답을 내지 않는다. 격자부터 직접 쌓는다 — 상자를 격자로 채우고, 예산 안의 점만 남기고, 가장 큰 를 고른다.
# 도구를 올리고 한국어 글꼴을 고른다
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 — 기준 사례를 쌓는다. 상자 , 격자 최대점, 지출, 경계 접촉 여부를 함께 낸다.
# 기준 사례의 상자·최대점·지출·경계 접촉을 쌓는다
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
예측 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()