01. 무한급수와 수렴 (Infinite Series and Convergence)
01. 무한급수와 수렴 (Infinite Series and Convergence)¶
학습 목표¶
- 급수의 수렴과 발산을 정의하고, 부분합(partial sum)을 통해 수렴을 판별할 수 있다
- 비교 판정법, 비율 판정법, 근 판정법, 적분 판정법, 교대급수 판정법 등 주요 수렴 판정법을 적용할 수 있다
- 멱급수 (power series) 의 수렴 반경을 구하고, 항별 미분/적분을 수행할 수 있다
- 테일러 급수와 매클로린 급수를 이용하여 함수를 근사하고, 물리학 문제에 적용할 수 있다
- 점근 급수 (asymptotic series) 와 스털링 근사의 개념을 이해하고 물리학적 응용에 활용할 수 있다
1. 급수의 기본 개념¶
1.1 수열과 급수¶
수열 (sequence) 은 자연수에서 실수(또는 복소수)로의 함수입니다:
$$a_1, a_2, a_3, \ldots, a_n, \ldots$$
수열 $\{a_n\}$이 특정 값 $L$에 수렴한다는 것은:
$$\lim_{n \to \infty} a_n = L$$
즉, $n$이 충분히 클 때 $a_n$이 $L$에 임의로 가까워진다는 의미입니다.
급수 (series) 는 수열의 항들의 합입니다:
$$S = \sum_{n=1}^{\infty} a_n = a_1 + a_2 + a_3 + \cdots$$
급수가 수렴하기 위한 필요조건: $\lim_{n \to \infty} a_n = 0$
주의: 이것은 필요조건일 뿐 충분조건이 아닙니다. 조화급수 $\sum(1/n)$은 $a_n \to 0$이지만 발산합니다.
1.2 부분합과 수렴¶
부분합 (partial sum) $S_N$은 급수의 처음 $N$개 항의 합입니다:
$$S_N = \sum_{n=1}^{N} a_n = a_1 + a_2 + \cdots + a_N$$
급수의 수렴 (convergence): 부분합의 수열 $\{S_N\}$이 유한한 값 $S$에 수렴하면, 급수 $\sum a_n = S$로 수렴한다고 합니다.
import numpy as np
import matplotlib.pyplot as plt
# 예제: 기하급수 (geometric series)의 부분합
# sum_{n=0}^{inf} r^n = 1/(1-r) (|r| < 1)
def geometric_partial_sums(r, N_max):
"""기하급수의 부분합을 계산합니다."""
n_values = np.arange(N_max + 1)
terms = r ** n_values
partial_sums = np.cumsum(terms)
return n_values, partial_sums
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 수렴하는 경우: |r| < 1
r_conv = 0.5
n_vals, S_n = geometric_partial_sums(r_conv, 20)
exact = 1 / (1 - r_conv)
axes[0].plot(n_vals, S_n, 'bo-', markersize=4, label=f'S_N (r={r_conv})')
axes[0].axhline(y=exact, color='r', linestyle='--', label=f'S = {exact:.4f}')
axes[0].set_xlabel('N')
axes[0].set_ylabel('S_N')
axes[0].set_title(f'수렴하는 기하급수 (r = {r_conv})')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 조화급수 vs p-급수 비교
N_max = 100
n = np.arange(1, N_max + 1)
harmonic = np.cumsum(1.0 / n) # p = 1 (발산)
p2_series = np.cumsum(1.0 / n**2) # p = 2 (수렴)
p3_series = np.cumsum(1.0 / n**3) # p = 3 (수렴)
axes[1].plot(n, harmonic, 'r-', label='p=1 (조화급수, 발산)')
axes[1].plot(n, p2_series, 'b-', label=f'p=2 (수렴, pi^2/6 = {np.pi**2/6:.4f})')
axes[1].plot(n, p3_series, 'g-', label='p=3 (수렴)')
axes[1].axhline(y=np.pi**2/6, color='b', linestyle='--', alpha=0.5)
axes[1].set_xlabel('N')
axes[1].set_ylabel('S_N')
axes[1].set_title('p-급수 비교: sum(1/n^p)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('series_convergence.png', dpi=150, bbox_inches='tight')
plt.show()
핵심 급수 정리:
| 급수 | 수렴 조건 | 합 (수렴 시) |
|---|---|---|
| 기하급수 $\sum r^n$ | $|r| < 1$ | $1/(1-r)$ |
| p-급수 $\sum 1/n^p$ | $p > 1$ | $\zeta(p)$ |
| 조화급수 $\sum 1/n$ | 발산 | - |
2. 수렴 판정법¶
2.1 비교 판정법 (Comparison Test)¶
$0 \leq a_n \leq b_n$ (충분히 큰 $n$에 대해) 이면:
- $\sum b_n$ 수렴 $\Rightarrow$ $\sum a_n$ 수렴
- $\sum a_n$ 발산 $\Rightarrow$ $\sum b_n$ 발산
극한 비교 판정법 (Limit Comparison Test): $a_n > 0$, $b_n > 0$이고
$$\lim_{n \to \infty} \frac{a_n}{b_n} = L \quad (0 < L < \infty)$$
이면 $\sum a_n$과 $\sum b_n$은 동시에 수렴하거나 동시에 발산합니다.
2.2 비율 판정법 (Ratio Test)¶
$$\rho = \lim_{n \to \infty} \left|\frac{a_{n+1}}{a_n}\right|$$
- $\rho < 1$ 이면 절대 수렴 (absolutely convergent)
- $\rho > 1$ 이면 발산
- $\rho = 1$ 이면 판정 불능 (다른 판정법 사용)
물리학 팁: 비율 판정법은 팩토리얼이나 지수 함수가 포함된 급수에 특히 유용합니다.
2.3 근 판정법 (Root Test)¶
$$\rho = \lim_{n \to \infty} |a_n|^{1/n}$$
- $\rho < 1$ 이면 절대 수렴
- $\rho > 1$ 이면 발산
- $\rho = 1$ 이면 판정 불능
2.4 적분 판정법 (Integral Test)¶
$f(x)$가 $[1, \infty)$에서 양수, 연속, 감소 함수이고 $f(n) = a_n$이면:
$$\sum_{n=1}^{\infty} a_n \text{ 과 } \int_1^{\infty} f(x) \, dx \text{ 는 동시에 수렴 또는 발산}$$
예제: p-급수의 수렴 조건 유도
$$\int_1^{\infty} \frac{1}{x^p} \, dx = \left[\frac{x^{1-p}}{1-p}\right]_1^{\infty}$$
$p > 1$이면 적분이 수렴하므로 $\sum(1/n^p)$도 수렴합니다.
2.5 교대급수 판정법 (Alternating Series Test)¶
교대급수 $\sum (-1)^n b_n$ ($b_n > 0$)이 다음 조건을 만족하면 수렴합니다:
- $b_{n+1} \leq b_n$ (단조감소)
- $\lim_{n \to \infty} b_n = 0$
교대급수의 오차 한계: $|S - S_N| \leq b_{N+1}$ (다음 항의 절대값 이하)
import numpy as np
import sympy as sp
# 수렴 판정법 자동 적용 도구
def test_convergence(a_n_func, name="급수"):
"""주어진 급수에 대해 비율 판정법과 근 판정법을 적용합니다."""
n = sp.Symbol('n', positive=True, integer=True)
a_n = a_n_func(n)
print(f"=== {name}: sum a_n, a_n = {a_n} ===\n")
# 비율 판정법
ratio = sp.simplify(a_n_func(n + 1) / a_n)
rho_ratio = sp.limit(sp.Abs(ratio), n, sp.oo)
print(f"비율 판정법: |a_(n+1)/a_n| -> {rho_ratio}")
if rho_ratio < 1:
print(" => 절대 수렴\n")
elif rho_ratio > 1:
print(" => 발산\n")
else:
print(" => 판정 불능\n")
# 근 판정법
root = sp.Abs(a_n) ** (sp.Rational(1, n))
rho_root = sp.limit(root, n, sp.oo)
print(f"근 판정법: |a_n|^(1/n) -> {rho_root}")
if rho_root < 1:
print(" => 절대 수렴\n")
elif rho_root > 1:
print(" => 발산\n")
else:
print(" => 판정 불능\n")
# 테스트 예제들
n = sp.Symbol('n', positive=True, integer=True)
# 1) sum n! / n^n (수렴)
test_convergence(lambda n: sp.factorial(n) / n**n, "n!/n^n")
# 2) sum 1/n^2 (수렴)
test_convergence(lambda n: 1 / n**2, "1/n^2")
# 3) sum n^2 / 2^n (수렴)
test_convergence(lambda n: n**2 / 2**n, "n^2/2^n")
import numpy as np
import matplotlib.pyplot as plt
# 교대급수 시각화: ln(2) = sum_{n=1}^{inf} (-1)^{n+1} / n
N = 30
n = np.arange(1, N + 1)
terms = (-1.0) ** (n + 1) / n
partial_sums = np.cumsum(terms)
plt.figure(figsize=(10, 5))
plt.plot(n, partial_sums, 'bo-', markersize=5, label='부분합 S_N')
plt.axhline(y=np.log(2), color='r', linestyle='--',
label=f'ln(2) = {np.log(2):.6f}')
# 오차 범위 표시
for i in range(len(n)):
error_bound = 1.0 / (n[i] + 1)
plt.plot([n[i], n[i]],
[partial_sums[i] - error_bound, partial_sums[i] + error_bound],
'g-', alpha=0.3, linewidth=2)
plt.xlabel('N')
plt.ylabel('S_N')
plt.title('교대급수: ln(2) = 1 - 1/2 + 1/3 - 1/4 + ...')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('alternating_series.png', dpi=150, bbox_inches='tight')
plt.show()
3. 멱급수 (Power Series)¶
3.1 수렴 반경¶
멱급수 (power series) 는 다음과 같은 형태의 급수입니다:
$$f(x) = \sum_{n=0}^{\infty} c_n (x - a)^n = c_0 + c_1(x-a) + c_2(x-a)^2 + \cdots$$
여기서 $a$는 전개 중심 (center), $c_n$은 계수 (coefficients) 입니다.
수렴 반경 (radius of convergence) $R$:
$$R = \lim_{n \to \infty} \left|\frac{c_n}{c_{n+1}}\right| \quad \text{또는} \quad R = \frac{1}{\lim_{n \to \infty} |c_n|^{1/n}}$$
- $|x - a| < R$ 이면 절대 수렴
- $|x - a| > R$ 이면 발산
- $|x - a| = R$ 이면 별도 조사 필요
수렴 구간 (interval of convergence):
발산 수렴 발산
-------|========|========|---------> x
a-R a a+R
<---- R ---->
3.2 항별 미분과 적분¶
수렴 구간 내에서, 멱급수는 항별 미분 (term-by-term differentiation) 과 항별 적분 (term-by-term integration) 이 가능합니다:
$$f'(x) = \sum_{n=1}^{\infty} n \cdot c_n \cdot (x-a)^{n-1}$$
$$\int f(x) \, dx = C + \sum_{n=0}^{\infty} \frac{c_n \cdot (x-a)^{n+1}}{n+1}$$
미분과 적분 후에도 수렴 반경은 동일합니다 (경계점 수렴 여부는 달라질 수 있음).
import sympy as sp
x = sp.Symbol('x')
# 멱급수의 수렴 반경 계산 예제
print("=== 멱급수의 수렴 반경 ===\n")
# 1) sum x^n / n! (지수함수)
n = sp.Symbol('n', positive=True, integer=True)
c_n = 1 / sp.factorial(n)
c_n1 = 1 / sp.factorial(n + 1)
R1 = sp.limit(sp.Abs(c_n / c_n1), n, sp.oo)
print(f"sum x^n/n!: R = {R1} (모든 x에서 수렴)")
# 2) sum n * x^n (R = 1)
c_n = n
c_n1 = n + 1
R2 = sp.limit(sp.Abs(c_n / c_n1), n, sp.oo)
print(f"sum n*x^n: R = {R2}")
# 3) sum n! * x^n (R = 0, x=0에서만 수렴)
c_n = sp.factorial(n)
c_n1 = sp.factorial(n + 1)
R3 = sp.limit(sp.Abs(c_n / c_n1), n, sp.oo)
print(f"sum n!*x^n: R = {R3} (x=0에서만 수렴)")
# 항별 미분 시각화
print("\n=== 항별 미분: d/dx [1/(1-x)] = 1/(1-x)^2 ===")
# 1/(1-x) = sum x^n => 미분하면 1/(1-x)^2 = sum n*x^{n-1}
x_vals = np.linspace(-0.9, 0.9, 200)
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 원래 함수와 급수
for N in [3, 5, 10, 20]:
series_sum = sum(x_vals**k for k in range(N))
axes[0].plot(x_vals, series_sum, alpha=0.7, label=f'N={N}')
axes[0].plot(x_vals, 1/(1-x_vals), 'k--', linewidth=2, label='1/(1-x)')
axes[0].set_ylim(-5, 15)
axes[0].set_title('1/(1-x) = sum x^n')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 미분한 함수와 급수
for N in [3, 5, 10, 20]:
deriv_sum = sum(k * x_vals**(k-1) for k in range(1, N))
axes[1].plot(x_vals, deriv_sum, alpha=0.7, label=f'N={N}')
axes[1].plot(x_vals, 1/(1-x_vals)**2, 'k--', linewidth=2, label='1/(1-x)^2')
axes[1].set_ylim(-5, 30)
axes[1].set_title('1/(1-x)^2 = sum n*x^{n-1}')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('power_series_differentiation.png', dpi=150, bbox_inches='tight')
plt.show()
4. 테일러 급수와 매클로린 급수¶
4.1 테일러 전개¶
함수 $f(x)$가 $x = a$ 근방에서 무한 번 미분가능하면, 테일러 급수 (Taylor series) 로 전개할 수 있습니다:
$$f(x) = \sum_{n=0}^{\infty} \frac{f^{(n)}(a)}{n!} (x - a)^n$$
$$= f(a) + f'(a)(x-a) + \frac{f''(a)}{2!}(x-a)^2 + \frac{f'''(a)}{3!}(x-a)^3 + \cdots$$
$a = 0$인 경우를 매클로린 급수 (Maclaurin series) 라고 합니다.
테일러 나머지 (remainder) $R_N$: $N$차까지 전개한 후의 오차
$$R_N(x) = \frac{f^{(N+1)}(c)}{(N+1)!} (x-a)^{N+1} \quad \text{(라그랑주 나머지, } a < c < x\text{)}$$
4.2 주요 함수의 급수 표현¶
물리학에서 가장 빈번하게 사용되는 급수 전개:
$$e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots \quad \text{(모든 } x\text{)}$$
$$\sin(x) = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \cdots \quad \text{(모든 } x\text{)}$$
$$\cos(x) = 1 - \frac{x^2}{2!} + \frac{x^4}{4!} - \frac{x^6}{6!} + \cdots \quad \text{(모든 } x\text{)}$$
$$\ln(1+x) = x - \frac{x^2}{2} + \frac{x^3}{3} - \frac{x^4}{4} + \cdots \quad (-1 < x \leq 1)$$
$$(1+x)^p = 1 + px + \frac{p(p-1)}{2!} x^2 + \cdots \quad (|x| < 1, \text{ 이항급수})$$
$$\frac{1}{1-x} = 1 + x + x^2 + x^3 + \cdots \quad (|x| < 1)$$
$$\arctan(x) = x - \frac{x^3}{3} + \frac{x^5}{5} - \frac{x^7}{7} + \cdots \quad (|x| \leq 1)$$
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
# SymPy를 이용한 테일러 전개
x = sp.Symbol('x')
functions = {
'e^x': sp.exp(x),
'sin(x)': sp.sin(x),
'cos(x)': sp.cos(x),
'ln(1+x)': sp.ln(1 + x),
}
print("=== 주요 함수의 테일러 급수 (a=0, 6차까지) ===\n")
for name, func in functions.items():
taylor = sp.series(func, x, 0, n=7)
print(f"{name} = {taylor}\n")
# 테일러 급수의 수렴 시각화
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
x_vals = np.linspace(-2 * np.pi, 2 * np.pi, 500)
# sin(x)의 테일러 근사
ax = axes[0, 0]
ax.plot(x_vals, np.sin(x_vals), 'k-', linewidth=2, label='sin(x)')
for N in [1, 3, 5, 7, 9]:
approx = sum((-1)**k * x_vals**(2*k+1) / np.math.factorial(2*k+1)
for k in range(N))
ax.plot(x_vals, approx, alpha=0.7, label=f'{2*N-1}차')
ax.set_ylim(-2, 2)
ax.set_title('sin(x)의 테일러 근사')
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
# cos(x)의 테일러 근사
ax = axes[0, 1]
ax.plot(x_vals, np.cos(x_vals), 'k-', linewidth=2, label='cos(x)')
for N in [1, 2, 4, 6, 8]:
approx = sum((-1)**k * x_vals**(2*k) / np.math.factorial(2*k)
for k in range(N))
ax.plot(x_vals, approx, alpha=0.7, label=f'{2*N-2}차')
ax.set_ylim(-2, 2)
ax.set_title('cos(x)의 테일러 근사')
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
# e^x의 테일러 근사
x_exp = np.linspace(-3, 3, 300)
ax = axes[1, 0]
ax.plot(x_exp, np.exp(x_exp), 'k-', linewidth=2, label='e^x')
for N in [1, 2, 4, 6, 10]:
approx = sum(x_exp**k / np.math.factorial(k) for k in range(N + 1))
ax.plot(x_exp, approx, alpha=0.7, label=f'{N}차')
ax.set_ylim(-5, 20)
ax.set_title('e^x의 테일러 근사')
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
# ln(1+x)의 테일러 근사
x_ln = np.linspace(-0.95, 2, 300)
ax = axes[1, 1]
ax.plot(x_ln, np.log(1 + x_ln), 'k-', linewidth=2, label='ln(1+x)')
for N in [1, 3, 5, 10, 20]:
x_series = np.linspace(-0.95, 0.95, 300) # 수렴 구간 내
approx = sum((-1)**(k+1) * x_series**k / k for k in range(1, N + 1))
ax.plot(x_series, approx, alpha=0.7, label=f'{N}차')
ax.axvline(x=1, color='gray', linestyle=':', alpha=0.5, label='x=1 (경계)')
ax.axvline(x=-1, color='gray', linestyle=':', alpha=0.5, label='x=-1 (경계)')
ax.set_ylim(-4, 2)
ax.set_title('ln(1+x)의 테일러 근사')
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('taylor_series_visualization.png', dpi=150, bbox_inches='tight')
plt.show()
4.3 급수를 이용한 근사¶
물리학에서 급수 전개의 가장 중요한 응용은 소량 근사 (small-quantity approximation) 입니다.
$\epsilon \ll 1$ 일 때:
$$(1 + \epsilon)^p \approx 1 + p\epsilon + \frac{p(p-1)}{2} \epsilon^2 + \cdots$$
$$e^\epsilon \approx 1 + \epsilon + \frac{\epsilon^2}{2} + \cdots$$
$$\sin(\epsilon) \approx \epsilon - \frac{\epsilon^3}{6} + \cdots$$
$$\cos(\epsilon) \approx 1 - \frac{\epsilon^2}{2} + \cdots$$
$$\tan(\epsilon) \approx \epsilon + \frac{\epsilon^3}{3} + \cdots$$
$$\frac{1}{1+\epsilon} \approx 1 - \epsilon + \epsilon^2 - \cdots$$
import numpy as np
import matplotlib.pyplot as plt
# 소량 근사의 정확도 비교
epsilon = np.linspace(0, 1, 100)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# sqrt(1 + epsilon) = (1+epsilon)^{1/2} 의 근사
exact = np.sqrt(1 + epsilon)
order1 = 1 + epsilon / 2 # 1차 근사
order2 = 1 + epsilon / 2 - epsilon**2 / 8 # 2차 근사
order3 = 1 + epsilon/2 - epsilon**2/8 + epsilon**3/16 # 3차
axes[0].plot(epsilon, exact, 'k-', linewidth=2, label='정확값')
axes[0].plot(epsilon, order1, 'b--', label='1차: 1 + e/2')
axes[0].plot(epsilon, order2, 'r--', label='2차: 1 + e/2 - e^2/8')
axes[0].plot(epsilon, order3, 'g--', label='3차')
axes[0].set_xlabel('epsilon')
axes[0].set_ylabel('sqrt(1 + epsilon)')
axes[0].set_title('이항급수 근사: (1+e)^{1/2}')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 상대 오차
rel_err1 = np.abs(order1 - exact) / exact * 100
rel_err2 = np.abs(order2 - exact) / exact * 100
rel_err3 = np.abs(order3 - exact) / exact * 100
axes[1].semilogy(epsilon, rel_err1, 'b-', label='1차 근사')
axes[1].semilogy(epsilon, rel_err2, 'r-', label='2차 근사')
axes[1].semilogy(epsilon, rel_err3, 'g-', label='3차 근사')
axes[1].set_xlabel('epsilon')
axes[1].set_ylabel('상대 오차 (%)')
axes[1].set_title('근사 차수에 따른 상대 오차')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('approximation_error.png', dpi=150, bbox_inches='tight')
plt.show()
5. 점근 급수 (Asymptotic Series)¶
5.1 발산하지만 유용한 급수¶
점근 급수 (asymptotic series) 는 무한히 더해 나가면 발산하지만, 유한한 항까지의 부분합이 함수의 훌륭한 근사를 제공하는 급수입니다.
함수 $f(x)$에 대한 점근 전개:
$$f(x) \sim \sum_{n=0}^{\infty} \frac{a_n}{x^n} \quad (x \to \infty)$$
이것은 $N$개 항까지의 부분합에 대해 다음이 성립한다는 의미입니다:
$$\lim_{x \to \infty} x^N \left[f(x) - \sum_{n=0}^{N} \frac{a_n}{x^n}\right] = 0 \quad \text{(각 } N\text{에 대해)}$$
핵심: 급수 전체는 발산하지만, 최적 항 수 (optimal truncation) 에서 끊으면 매우 좋은 근사를 얻습니다. 일반적으로 항들의 크기가 감소하다가 다시 증가하기 시작하는 지점에서 끊는 것이 최적입니다.
5.2 스털링 근사¶
스털링 근사 (Stirling's approximation) 는 팩토리얼의 점근 근사입니다:
$$n! \sim \sqrt{2\pi n} \left(\frac{n}{e}\right)^n \quad (n \to \infty)$$
$$\ln(n!) \sim n\ln(n) - n + \frac{1}{2}\ln(2\pi n)$$
이 근사는 통계역학에서 볼츠만 엔트로피 계산, 조합론적 문제 등에 핵심적으로 사용됩니다.
더 정밀한 스털링 급수:
$$n! \sim \sqrt{2\pi n} \left(\frac{n}{e}\right)^n \left(1 + \frac{1}{12n} + \frac{1}{288n^2} - \frac{139}{51840n^3} + \cdots\right)$$
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import gamma, factorial
# 스털링 근사의 정확도
n_vals = np.arange(1, 51)
# 정확한 ln(n!)
exact_lnfact = np.array([np.sum(np.log(np.arange(1, n+1))) for n in n_vals])
# 스털링 근사 (여러 차수)
stirling_0 = n_vals * np.log(n_vals) - n_vals # 가장 간단한 형태
stirling_1 = stirling_0 + 0.5 * np.log(2 * np.pi * n_vals) # 1차 보정
stirling_2 = stirling_1 + 1 / (12 * n_vals) # 2차 보정
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 절대값 비교
axes[0].plot(n_vals, exact_lnfact, 'k-', linewidth=2, label='ln(n!) 정확값')
axes[0].plot(n_vals, stirling_0, 'b--', label='n*ln(n) - n')
axes[0].plot(n_vals, stirling_1, 'r--', label='+ (1/2)ln(2*pi*n)')
axes[0].set_xlabel('n')
axes[0].set_ylabel('ln(n!)')
axes[0].set_title('스털링 근사')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 상대 오차
rel_err0 = np.abs(stirling_0 - exact_lnfact) / exact_lnfact * 100
rel_err1 = np.abs(stirling_1 - exact_lnfact) / exact_lnfact * 100
rel_err2 = np.abs(stirling_2 - exact_lnfact) / exact_lnfact * 100
axes[1].semilogy(n_vals, rel_err0, 'b-', label='0차: n*ln(n)-n')
axes[1].semilogy(n_vals, rel_err1, 'r-', label='1차: + ln(sqrt(2*pi*n))')
axes[1].semilogy(n_vals, rel_err2, 'g-', label='2차: + 1/(12n)')
axes[1].set_xlabel('n')
axes[1].set_ylabel('상대 오차 (%)')
axes[1].set_title('스털링 근사의 상대 오차')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('stirling_approximation.png', dpi=150, bbox_inches='tight')
plt.show()
# 통계역학 응용: 볼츠만 엔트로피
print("=== 통계역학 응용: 이상기체 엔트로피 ===\n")
print("N개 입자가 W개 미시상태를 가질 때:")
print("S = k_B * ln(W)")
print("스털링 근사를 사용하면:")
print("ln(N!) ~ N*ln(N) - N")
print()
print("예: N = 10^23 (아보가드로 수 규모)")
N = 1e23
print(f"ln(N!) ~ N*ln(N) - N = {N * np.log(N) - N:.4e}")
print("직접 계산은 불가능하지만, 스털링 근사로 쉽게 계산 가능!")
6. 물리학에서의 응용¶
6.1 진자의 주기 (급수 전개)¶
단진자의 정확한 주기는 완전 타원 적분으로 주어집니다:
$$T = 4\sqrt{\frac{L}{g}} K\left(\sin\frac{\theta_0}{2}\right)$$
여기서 $K(k)$는 제1종 완전 타원 적분입니다. 이를 급수로 전개하면:
$$T = 2\pi\sqrt{\frac{L}{g}} \left[1 + \frac{1}{4}\sin^2\frac{\theta_0}{2} + \frac{9}{64}\sin^4\frac{\theta_0}{2} + \cdots\right]$$
$$\approx T_0 \left[1 + \frac{\theta_0^2}{16} + \frac{11\theta_0^4}{3072} + \cdots\right]$$
여기서 $T_0 = 2\pi\sqrt{L/g}$는 소각 근사에서의 주기입니다.
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import ellipk
# 단진자의 주기: 정확값 vs 급수 근사
g = 9.81 # m/s^2
L = 1.0 # m
T0 = 2 * np.pi * np.sqrt(L / g) # 소각 근사 주기
theta0 = np.linspace(0.01, np.pi * 0.95, 200) # 초기 각도 (rad)
k = np.sin(theta0 / 2)
# 정확한 주기 (완전 타원 적분)
T_exact = 4 * np.sqrt(L / g) * ellipk(k**2)
# 급수 근사 (소각 근사부터 고차항까지)
T_approx0 = T0 * np.ones_like(theta0) # 0차 (소각)
T_approx2 = T0 * (1 + (1/4) * k**2) # 2차
T_approx4 = T0 * (1 + (1/4)*k**2 + (9/64)*k**4) # 4차
T_approx6 = T0 * (1 + (1/4)*k**2 + (9/64)*k**4 + (25/256)*k**6)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 주기 비교
theta_deg = np.degrees(theta0)
axes[0].plot(theta_deg, T_exact / T0, 'k-', linewidth=2, label='정확값')
axes[0].plot(theta_deg, T_approx0 / T0, 'b--', label='0차 (소각근사)')
axes[0].plot(theta_deg, T_approx2 / T0, 'r--', label='2차 보정')
axes[0].plot(theta_deg, T_approx4 / T0, 'g--', label='4차 보정')
axes[0].plot(theta_deg, T_approx6 / T0, 'm--', label='6차 보정')
axes[0].set_xlabel('초기 각도 (도)')
axes[0].set_ylabel('T / T_0')
axes[0].set_title('단진자 주기: 급수 전개 근사')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 상대 오차
axes[1].semilogy(theta_deg, np.abs(T_approx0 - T_exact)/T_exact * 100,
'b-', label='0차 (소각)')
axes[1].semilogy(theta_deg, np.abs(T_approx2 - T_exact)/T_exact * 100,
'r-', label='2차')
axes[1].semilogy(theta_deg, np.abs(T_approx4 - T_exact)/T_exact * 100,
'g-', label='4차')
axes[1].semilogy(theta_deg, np.abs(T_approx6 - T_exact)/T_exact * 100,
'm-', label='6차')
axes[1].set_xlabel('초기 각도 (도)')
axes[1].set_ylabel('상대 오차 (%)')
axes[1].set_title('급수 근사의 상대 오차')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('pendulum_period.png', dpi=150, bbox_inches='tight')
plt.show()
# 수치 결과
print("=== 단진자 주기 비교 (L=1m) ===\n")
print(f"{'각도':>6s} {'정확값(s)':>10s} {'소각근사(s)':>12s} {'오차(%)':>8s}")
print("-" * 45)
for angle_deg in [5, 10, 30, 45, 60, 90, 120, 150]:
angle_rad = np.radians(angle_deg)
k_val = np.sin(angle_rad / 2)
T_ex = 4 * np.sqrt(L / g) * ellipk(k_val**2)
err = (T0 - T_ex) / T_ex * 100
print(f"{angle_deg:>5d} {T_ex:>10.6f} {T0:>12.6f} {err:>8.3f}")
6.2 상대론적 에너지의 비상대론적 근사¶
아인슈타인의 상대론적 에너지-운동량 관계:
$$E = \gamma mc^2 = \frac{mc^2}{\sqrt{1 - v^2/c^2}}$$
운동 에너지 $K = E - mc^2$를 급수 전개하면 ($\beta = v/c \ll 1$):
$$\gamma = \frac{1}{\sqrt{1 - \beta^2}} = 1 + \frac{1}{2}\beta^2 + \frac{3}{8}\beta^4 + \frac{5}{16}\beta^6 + \cdots$$
$$K = mc^2(\gamma - 1)$$
$$= \frac{1}{2}mv^2 + \frac{3}{8}\frac{mv^4}{c^2} + \frac{5}{16}\frac{mv^6}{c^4} + \cdots$$
여기서 첫 번째 항이 뉴턴 역학, 두 번째 항이 상대론적 보정(1차)입니다.
import numpy as np
import matplotlib.pyplot as plt
# 상대론적 운동 에너지의 급수 근사
beta = np.linspace(0.001, 0.99, 500) # v/c
# 정확한 상대론적 운동 에너지: K/(mc^2) = gamma - 1
gamma = 1 / np.sqrt(1 - beta**2)
K_exact = gamma - 1 # mc^2 단위
# 급수 근사 (이항급수 전개)
K_order2 = 0.5 * beta**2 # 뉴턴 역학
K_order4 = 0.5 * beta**2 + (3/8) * beta**4 # 1차 보정
K_order6 = 0.5 * beta**2 + (3/8) * beta**4 + (5/16) * beta**6 # 2차 보정
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 운동 에너지 비교
axes[0].plot(beta, K_exact, 'k-', linewidth=2, label='상대론적 (정확)')
axes[0].plot(beta, K_order2, 'b--', label='뉴턴: (1/2)mv^2')
axes[0].plot(beta, K_order4, 'r--', label='1차 보정')
axes[0].plot(beta, K_order6, 'g--', label='2차 보정')
axes[0].set_xlabel('beta = v/c')
axes[0].set_ylabel('K / (mc^2)')
axes[0].set_title('운동 에너지: 상대론 vs 뉴턴')
axes[0].set_ylim(0, 5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 뉴턴 역학의 상대 오차
beta_low = np.linspace(0.001, 0.5, 300)
gamma_low = 1 / np.sqrt(1 - beta_low**2)
K_exact_low = gamma_low - 1
K_newton = 0.5 * beta_low**2
K_corr1 = 0.5 * beta_low**2 + (3/8) * beta_low**4
rel_err_newton = np.abs(K_newton - K_exact_low) / K_exact_low * 100
rel_err_corr1 = np.abs(K_corr1 - K_exact_low) / K_exact_low * 100
axes[1].semilogy(beta_low, rel_err_newton, 'b-', label='뉴턴 역학 오차')
axes[1].semilogy(beta_low, rel_err_corr1, 'r-', label='1차 보정 후 오차')
axes[1].axhline(y=1, color='gray', linestyle=':', alpha=0.5, label='1% 오차선')
axes[1].set_xlabel('beta = v/c')
axes[1].set_ylabel('상대 오차 (%)')
axes[1].set_title('뉴턴 역학의 유효 범위')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('relativistic_energy.png', dpi=150, bbox_inches='tight')
plt.show()
# 결과 출력
print("=== 뉴턴 역학이 유효한 속도 범위 ===\n")
print(f"{'v/c':>6s} {'상대론적 K':>12s} {'뉴턴 K':>10s} {'오차(%)':>8s}")
print("-" * 42)
for b in [0.01, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.9]:
g = 1 / np.sqrt(1 - b**2)
K_rel = g - 1
K_new = 0.5 * b**2
err = abs(K_new - K_rel) / K_rel * 100
print(f"{b:>6.2f} {K_rel:>12.6f} {K_new:>10.6f} {err:>8.3f}")
6.3 전기 다중극 전개¶
점전하 $q$가 원점에서 거리 $d$인 곳에 있을 때, 원점에서 멀리 떨어진 점($r \gg d$)에서의 전위를 급수로 전개합니다.
관측점 P(r, theta)
*
|\
| \ r
| \
| \
|theta\
|------*--- 전하 q (거리 d, z축 위)
|
원점 O
관측점까지의 거리:
$$\frac{1}{|\mathbf{r} - \mathbf{d}|} = \frac{1}{r} \sum_{l=0}^{\infty} \left(\frac{d}{r}\right)^l P_l(\cos\theta)$$
여기서 $P_l$은 르장드르 다항식 (Legendre polynomials)입니다.
전기 쌍극자 (dipole)의 경우 ($+q$와 $-q$가 거리 $d$만큼 떨어져 있을 때):
$$V(r, \theta) \approx \frac{1}{4\pi\epsilon_0} \frac{p\cos\theta}{r^2} \quad (r \gg d)$$
여기서 $p = qd$는 쌍극자 모멘트 (dipole moment)입니다.
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import legendre
# 다중극 전개 (Multipole Expansion) 시각화
# 전하 q가 z축 위 d = 1에 있을 때, 1/|r - d*z_hat|의 급수 전개
def exact_potential_ratio(r, theta, d=1.0):
"""정확한 전위 비율: r / |r - d*z_hat| (1/r 제외한 부분)"""
# |r - d*z_hat|^2 = r^2 - 2*r*d*cos(theta) + d^2
dist = np.sqrt(r**2 - 2*r*d*np.cos(theta) + d**2)
return r / dist
def multipole_approx(r, theta, d=1.0, L_max=5):
"""다중극 전개: sum_{l=0}^{L_max} (d/r)^l * P_l(cos(theta))"""
result = np.zeros_like(r)
cos_theta = np.cos(theta)
for l in range(L_max + 1):
Pl = legendre(l)
result += (d / r)**l * Pl(cos_theta)
return result
# r/d 비율에 따른 다중극 전개의 정확도
r_values = np.linspace(1.5, 10, 200)
theta = np.pi / 4 # 45도
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
exact = exact_potential_ratio(r_values, theta)
for L_max in [0, 1, 2, 5, 10]:
approx = multipole_approx(r_values, theta, L_max=L_max)
axes[0].plot(r_values, approx, label=f'L_max = {L_max}')
axes[0].plot(r_values, exact, 'k--', linewidth=2, label='정확값')
axes[0].set_xlabel('r/d')
axes[0].set_ylabel('r * V(r) / (kq)')
axes[0].set_title(f'다중극 전개 (theta = 45도)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 쌍극자 전위 패턴 (2D)
r_grid = np.linspace(0.5, 5, 200)
theta_grid = np.linspace(0, 2*np.pi, 200)
R, Theta = np.meshgrid(r_grid, theta_grid)
X = R * np.sin(Theta)
Z = R * np.cos(Theta)
# 쌍극자 전위: V ~ cos(theta)/r^2 (단위 생략)
V_dipole = np.cos(Theta) / R**2
axes[1].contourf(X, Z, V_dipole, levels=np.linspace(-2, 2, 41),
cmap='RdBu_r', extend='both')
axes[1].set_xlabel('x')
axes[1].set_ylabel('z')
axes[1].set_title('전기 쌍극자 전위 패턴')
axes[1].set_aspect('equal')
axes[1].set_xlim(-3, 3)
axes[1].set_ylim(-3, 3)
# 전하 위치 표시
axes[1].plot(0, 0.1, 'r+', markersize=15, markeredgewidth=2)
axes[1].plot(0, -0.1, 'b_', markersize=15, markeredgewidth=2)
plt.tight_layout()
plt.savefig('multipole_expansion.png', dpi=150, bbox_inches='tight')
plt.show()
# 르장드르 다항식의 처음 몇 개
print("=== 르장드르 다항식 P_l(x) ===\n")
import sympy as sp
x_sym = sp.Symbol('x')
for l in range(6):
Pl = sp.legendre(l, x_sym)
print(f"P_{l}(x) = {sp.expand(Pl)}")
연습 문제¶
문제 1. 수렴 판정 (기본)¶
다음 급수의 수렴/발산을 판정하시오. 사용한 판정법을 명시하시오.
(a) $\sum_{n=1}^{\infty} \frac{n^2}{3^n}$
(b) $\sum_{n=2}^{\infty} \frac{1}{n \ln^2(n)}$
(c) $\sum_{n=1}^{\infty} \frac{(-1)^n n}{n^2 + 1}$
힌트: (a) 비율 판정법, (b) 적분 판정법, (c) 교대급수 판정법
문제 2. 멱급수의 수렴 반경 (기본)¶
다음 멱급수의 수렴 반경 $R$을 구하시오.
(a) $\sum_{n=0}^{\infty} \frac{n!}{(2n)!} x^n$
(b) $\sum_{n=1}^{\infty} \frac{(-1)^{n+1}}{n \cdot 3^n} x^n$
문제 3. 테일러 급수 근사 (중급)¶
함수 $f(x) = \sqrt{1 + x}$를 $x = 0$에서 3차까지 테일러 전개하고, 다음을 구하시오.
(a) $\sqrt{1.1}$의 근사값 ($x = 0.1$ 대입)
(b) 정확값과 비교하여 상대 오차를 계산하시오.
(c) Python으로 1차, 2차, 3차 근사의 오차를 비교하는 그래프를 그리시오.
문제 4. 물리학 응용 (중급)¶
진자의 최대 각도가 $\theta_0 = 30$도일 때:
(a) 주기의 정확한 값 (타원 적분)을 계산하시오.
(b) 소각 근사의 주기와 비교하여 오차를 구하시오.
(c) 2차 보정항까지 포함했을 때의 오차를 구하시오.
문제 5. 스털링 근사 (중급)¶
(a) 스털링 근사를 이용하여 $100!$의 상용로그 ($\log_{10}$)를 구하시오.
(b) Python의 정확한 계산과 비교하시오.
(c) 이상기체 $N$개 분자의 배치 경우의 수 $W = N! / (n_1! n_2! \cdots n_k!)$에서, 스털링 근사가 왜 필수적인지 설명하시오.
문제 6. 종합 응용 (고급)¶
전자의 속도가 $v = 0.1c$일 때:
(a) 상대론적 운동 에너지의 정확한 값을 $mc^2$ 단위로 구하시오.
(b) 뉴턴 역학의 운동 에너지와 비교하여 상대 오차를 구하시오.
(c) 1차 상대론적 보정항 $(3/8)mv^4/c^2$을 포함하면 오차가 얼마나 줄어드는지 계산하시오.
(d) 뉴턴 역학의 오차가 1% 미만이 되려면 $v/c$가 얼마 이하여야 하는지 구하시오.
# 연습 문제 풀이 도우미
import numpy as np
import sympy as sp
# 문제 1(a) 검증
n = sp.Symbol('n', positive=True, integer=True)
a_n = n**2 / 3**n
ratio = sp.simplify(sp.Abs((n+1)**2 / 3**(n+1)) / (n**2 / 3**n))
rho = sp.limit(ratio, n, sp.oo)
print(f"문제 1(a): 비율 = {rho} < 1 => 수렴")
# 문제 4 검증
from scipy.special import ellipk
L, g = 1.0, 9.81
theta0 = np.radians(30)
T0 = 2 * np.pi * np.sqrt(L / g)
k = np.sin(theta0 / 2)
T_exact = 4 * np.sqrt(L / g) * ellipk(k**2)
print(f"\n문제 4: T_exact = {T_exact:.6f}s, T_0 = {T0:.6f}s")
print(f"소각 근사 오차 = {abs(T0 - T_exact)/T_exact * 100:.4f}%")
T_corrected = T0 * (1 + (1/4) * k**2)
print(f"2차 보정 후 오차 = {abs(T_corrected - T_exact)/T_exact * 100:.6f}%")
# 문제 5 검증
import math
stirling_log10_100 = (100 * np.log(100) - 100 + 0.5 * np.log(200 * np.pi)) / np.log(10)
exact_log10_100 = np.log10(float(math.factorial(100)))
print(f"\n문제 5: log10(100!) 스털링 = {stirling_log10_100:.4f}")
print(f" log10(100!) 정확값 = {exact_log10_100:.4f}")
print(f" 오차 = {abs(stirling_log10_100 - exact_log10_100):.6f}")
참고 자료¶
교재¶
- Boas, M. L. (2005). Mathematical Methods in the Physical Sciences, 3rd ed., Chapter 1. Wiley.
- Arfken, G. B., Weber, H. J., & Harris, F. E. (2012). Mathematical Methods for Physicists, 7th ed., Chapter 5. Academic Press.
- Riley, K. F. et al. (2006). Mathematical Methods for Physics and Engineering, Chapter 4. Cambridge University Press.
온라인 자료¶
- MIT OCW 18.01SC: Single Variable Calculus - Sequences and Series
- Paul's Online Math Notes: Series & Sequences (https://tutorial.math.lamar.edu/)
- 3Blue1Brown: Taylor series 시각화
Python 도구¶
sympy.series(): 심볼릭 테일러 급수 계산scipy.special.ellipk(): 완전 타원 적분scipy.special.factorial(): 팩토리얼 계산numpy.cumsum(): 부분합 계산
다음 레슨¶
02. 복소수 (Complex Numbers)에서는 복소수의 대수적 연산, 극좌표와 지수 표현, 드모아브르 정리, 그리고 물리학에서 필수적인 오일러 공식을 다룹니다. 급수에서 배운 테일러 전개를 이용하여 $e^{ix} = \cos(x) + i\sin(x)$를 유도하게 됩니다.