03_ode_euler.py

Download
python 424 lines 12.4 KB
  1"""
  2์ƒ๋ฏธ๋ถ„๋ฐฉ์ •์‹ - ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• (Euler Method)
  3Ordinary Differential Equations - Euler Method
  4
  5์ดˆ๊ธฐ๊ฐ’ ๋ฌธ์ œ dy/dx = f(x, y), y(xโ‚€) = yโ‚€ ๋ฅผ ์ˆ˜์น˜์ ์œผ๋กœ ํ‘ธ๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
  6"""
  7
  8import numpy as np
  9import matplotlib.pyplot as plt
 10from typing import Callable, Tuple, List
 11
 12
 13# =============================================================================
 14# 1. ์ „์ง„ ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• (Forward Euler Method)
 15# =============================================================================
 16def euler_forward(
 17    f: Callable[[float, float], float],
 18    y0: float,
 19    t_span: Tuple[float, float],
 20    h: float
 21) -> Tuple[np.ndarray, np.ndarray]:
 22    """
 23    ์ „์ง„ ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• (๋ช…์‹œ์  ์˜ค์ผ๋Ÿฌ)
 24
 25    y_{n+1} = y_n + h * f(t_n, y_n)
 26
 27    ์˜ค์ฐจ: O(h) - 1์ฐจ ์ •ํ™•๋„
 28    ์•ˆ์ •์„ฑ: ์กฐ๊ฑด๋ถ€ ์•ˆ์ •
 29
 30    Args:
 31        f: dy/dt = f(t, y)
 32        y0: ์ดˆ๊ธฐ๊ฐ’ y(t0)
 33        t_span: (t0, tf) ์‹œ๊ฐ„ ๊ตฌ๊ฐ„
 34        h: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
 35
 36    Returns:
 37        (t ๋ฐฐ์—ด, y ๋ฐฐ์—ด)
 38    """
 39    t0, tf = t_span
 40    n_steps = int((tf - t0) / h)
 41
 42    t = np.linspace(t0, tf, n_steps + 1)
 43    y = np.zeros(n_steps + 1)
 44    y[0] = y0
 45
 46    for i in range(n_steps):
 47        y[i + 1] = y[i] + h * f(t[i], y[i])
 48
 49    return t, y
 50
 51
 52# =============================================================================
 53# 2. ํ›„์ง„ ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• (Backward Euler Method)
 54# =============================================================================
 55def euler_backward(
 56    f: Callable[[float, float], float],
 57    df_dy: Callable[[float, float], float],
 58    y0: float,
 59    t_span: Tuple[float, float],
 60    h: float,
 61    newton_tol: float = 1e-10,
 62    max_iter: int = 50
 63) -> Tuple[np.ndarray, np.ndarray]:
 64    """
 65    ํ›„์ง„ ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• (์•”์‹œ์  ์˜ค์ผ๋Ÿฌ)
 66
 67    y_{n+1} = y_n + h * f(t_{n+1}, y_{n+1})
 68
 69    ์•”์‹œ์  ๋ฐฉ์ •์‹์„ ๋‰ดํ„ด๋ฒ•์œผ๋กœ ํ•ด๊ฒฐ
 70    ์˜ค์ฐจ: O(h) - 1์ฐจ ์ •ํ™•๋„
 71    ์•ˆ์ •์„ฑ: ๋ฌด์กฐ๊ฑด ์•ˆ์ • (stiff ๋ฌธ์ œ์— ์ ํ•ฉ)
 72
 73    Args:
 74        f: dy/dt = f(t, y)
 75        df_dy: โˆ‚f/โˆ‚y (์•ผ์ฝ”๋น„์•ˆ)
 76        y0: ์ดˆ๊ธฐ๊ฐ’
 77        t_span: ์‹œ๊ฐ„ ๊ตฌ๊ฐ„
 78        h: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
 79    """
 80    t0, tf = t_span
 81    n_steps = int((tf - t0) / h)
 82
 83    t = np.linspace(t0, tf, n_steps + 1)
 84    y = np.zeros(n_steps + 1)
 85    y[0] = y0
 86
 87    for i in range(n_steps):
 88        # ๋‰ดํ„ด๋ฒ•์œผ๋กœ y_{n+1} ๊ตฌํ•˜๊ธฐ
 89        # g(y) = y - y_n - h*f(t_{n+1}, y) = 0
 90        y_new = y[i]  # ์ดˆ๊ธฐ ์ถ”์ •๊ฐ’ (์ „์ง„ ์˜ค์ผ๋Ÿฌ)
 91        t_new = t[i + 1]
 92
 93        for _ in range(max_iter):
 94            g = y_new - y[i] - h * f(t_new, y_new)
 95            dg = 1 - h * df_dy(t_new, y_new)
 96
 97            if abs(dg) < 1e-15:
 98                break
 99
100            delta = g / dg
101            y_new = y_new - delta
102
103            if abs(delta) < newton_tol:
104                break
105
106        y[i + 1] = y_new
107
108    return t, y
109
110
111# =============================================================================
112# 3. ์ˆ˜์ • ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• (Modified Euler / Heun's Method)
113# =============================================================================
114def euler_modified(
115    f: Callable[[float, float], float],
116    y0: float,
117    t_span: Tuple[float, float],
118    h: float
119) -> Tuple[np.ndarray, np.ndarray]:
120    """
121    ์ˆ˜์ • ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• (Heun's Method)
122
123    ์˜ˆ์ธก: y*_{n+1} = y_n + h * f(t_n, y_n)
124    ์ˆ˜์ •: y_{n+1} = y_n + h/2 * [f(t_n, y_n) + f(t_{n+1}, y*_{n+1})]
125
126    ์˜ค์ฐจ: O(hยฒ) - 2์ฐจ ์ •ํ™•๋„
127    RK2์˜ ์ผ์ข…
128    """
129    t0, tf = t_span
130    n_steps = int((tf - t0) / h)
131
132    t = np.linspace(t0, tf, n_steps + 1)
133    y = np.zeros(n_steps + 1)
134    y[0] = y0
135
136    for i in range(n_steps):
137        k1 = f(t[i], y[i])
138        y_pred = y[i] + h * k1
139        k2 = f(t[i + 1], y_pred)
140        y[i + 1] = y[i] + h * (k1 + k2) / 2
141
142    return t, y
143
144
145# =============================================================================
146# 4. ์—ฐ๋ฆฝ ODE ํ’€๊ธฐ
147# =============================================================================
148def euler_system(
149    f: Callable[[float, np.ndarray], np.ndarray],
150    y0: np.ndarray,
151    t_span: Tuple[float, float],
152    h: float
153) -> Tuple[np.ndarray, np.ndarray]:
154    """
155    ์—ฐ๋ฆฝ ODE๋ฅผ ์ „์ง„ ์˜ค์ผ๋Ÿฌ๋กœ ํ’€๊ธฐ
156
157    dy/dt = f(t, y), y = [y1, y2, ..., yn]
158
159    Args:
160        f: ๋ฒกํ„ฐ ํ•จ์ˆ˜ f(t, y) -> dy/dt
161        y0: ์ดˆ๊ธฐ๊ฐ’ ๋ฒกํ„ฐ
162        t_span: ์‹œ๊ฐ„ ๊ตฌ๊ฐ„
163        h: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
164
165    Returns:
166        (t ๋ฐฐ์—ด, y ๋ฐฐ์—ด) - y.shape = (n_steps+1, n_vars)
167    """
168    t0, tf = t_span
169    n_steps = int((tf - t0) / h)
170    n_vars = len(y0)
171
172    t = np.linspace(t0, tf, n_steps + 1)
173    y = np.zeros((n_steps + 1, n_vars))
174    y[0] = y0
175
176    for i in range(n_steps):
177        y[i + 1] = y[i] + h * np.array(f(t[i], y[i]))
178
179    return t, y
180
181
182# =============================================================================
183# 5. 2์ฐจ ODE๋ฅผ 1์ฐจ ์—ฐ๋ฆฝ์œผ๋กœ ๋ณ€ํ™˜
184# =============================================================================
185def solve_second_order(
186    f: Callable[[float, float, float], float],
187    y0: float,
188    v0: float,
189    t_span: Tuple[float, float],
190    h: float
191) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
192    """
193    2์ฐจ ODE: y'' = f(t, y, y')
194    ๋ณ€ํ™˜: yโ‚ = y, yโ‚‚ = y'
195          yโ‚' = yโ‚‚
196          yโ‚‚' = f(t, yโ‚, yโ‚‚)
197
198    Args:
199        f: y'' = f(t, y, y')
200        y0: ์ดˆ๊ธฐ ์œ„์น˜
201        v0: ์ดˆ๊ธฐ ์†๋„
202        t_span, h: ์‹œ๊ฐ„ ๊ตฌ๊ฐ„๊ณผ ๊ฐ„๊ฒฉ
203
204    Returns:
205        (t, y, v) - ์œ„์น˜์™€ ์†๋„
206    """
207    def system(t, state):
208        y, v = state
209        return np.array([v, f(t, y, v)])
210
211    t, solution = euler_system(system, np.array([y0, v0]), t_span, h)
212    return t, solution[:, 0], solution[:, 1]
213
214
215# =============================================================================
216# ์˜ค์ฐจ ๋ถ„์„
217# =============================================================================
218def analyze_euler_error():
219    """์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ•์˜ ์˜ค์ฐจ ๋ถ„์„"""
220    # dy/dt = y, y(0) = 1  โ†’  y = e^t
221    f = lambda t, y: y
222    df_dy = lambda t, y: 1
223    y0 = 1
224    t_span = (0, 1)
225    exact = lambda t: np.exp(t)
226
227    print("\n์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• ์˜ค์ฐจ ๋ถ„์„ (dy/dt = y, y(0) = 1)")
228    print("-" * 70)
229    print(f"{'h':>10} | {'์ „์ง„ ์˜ค์ผ๋Ÿฌ':>15} | {'์ˆ˜์ • ์˜ค์ผ๋Ÿฌ':>15} | {'ํ›„์ง„ ์˜ค์ผ๋Ÿฌ':>15}")
230    print("-" * 70)
231
232    hs = [0.1, 0.05, 0.025, 0.0125, 0.00625]
233
234    for h in hs:
235        t1, y1 = euler_forward(f, y0, t_span, h)
236        t2, y2 = euler_modified(f, y0, t_span, h)
237        t3, y3 = euler_backward(f, df_dy, y0, t_span, h)
238
239        error1 = abs(y1[-1] - exact(1))
240        error2 = abs(y2[-1] - exact(1))
241        error3 = abs(y3[-1] - exact(1))
242
243        print(f"{h:>10.5f} | {error1:>15.2e} | {error2:>15.2e} | {error3:>15.2e}")
244
245
246# =============================================================================
247# ์‹œ๊ฐํ™”
248# =============================================================================
249def plot_euler_comparison():
250    """์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• ๋น„๊ต ์‹œ๊ฐํ™”"""
251    # dy/dt = -2y + sin(t), y(0) = 1
252    f = lambda t, y: -2*y + np.sin(t)
253    df_dy = lambda t, y: -2
254    y0 = 1
255    t_span = (0, 5)
256
257    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
258
259    # ์ •ํ™•ํ•ด (scipy ์‚ฌ์šฉ)
260    from scipy.integrate import odeint
261    t_exact = np.linspace(0, 5, 500)
262    y_exact = odeint(lambda y, t: f(t, y), y0, t_exact).flatten()
263
264    # ๋‹ค์–‘ํ•œ h ๊ฐ’ ๋น„๊ต
265    hs = [0.5, 0.25, 0.1]
266    colors = ['r', 'g', 'b']
267
268    ax = axes[0, 0]
269    ax.plot(t_exact, y_exact, 'k-', linewidth=2, label='์ •ํ™•ํ•ด')
270    for h, c in zip(hs, colors):
271        t, y = euler_forward(f, y0, t_span, h)
272        ax.plot(t, y, f'{c}o-', markersize=4, label=f'h={h}')
273    ax.set_title('์ „์ง„ ์˜ค์ผ๋Ÿฌ')
274    ax.legend()
275    ax.grid(True)
276
277    ax = axes[0, 1]
278    ax.plot(t_exact, y_exact, 'k-', linewidth=2, label='์ •ํ™•ํ•ด')
279    for h, c in zip(hs, colors):
280        t, y = euler_modified(f, y0, t_span, h)
281        ax.plot(t, y, f'{c}o-', markersize=4, label=f'h={h}')
282    ax.set_title('์ˆ˜์ • ์˜ค์ผ๋Ÿฌ')
283    ax.legend()
284    ax.grid(True)
285
286    # ์•ˆ์ •์„ฑ ๋น„๊ต (stiff ๋ฌธ์ œ)
287    # dy/dt = -50(y - cos(t)), y(0) = 0
288    f_stiff = lambda t, y: -50*(y - np.cos(t))
289    df_stiff = lambda t, y: -50
290    h = 0.05
291    t_span_stiff = (0, 1)
292
293    ax = axes[1, 0]
294    t_ex = np.linspace(0, 1, 500)
295    y_ex = odeint(lambda y, t: f_stiff(t, y), 0, t_ex).flatten()
296    ax.plot(t_ex, y_ex, 'k-', linewidth=2, label='์ •ํ™•ํ•ด')
297
298    t1, y1 = euler_forward(f_stiff, 0, t_span_stiff, h)
299    t2, y2 = euler_backward(f_stiff, df_stiff, 0, t_span_stiff, h)
300
301    ax.plot(t1, y1, 'r.-', label=f'์ „์ง„ ์˜ค์ผ๋Ÿฌ (h={h})')
302    ax.plot(t2, y2, 'b.-', label=f'ํ›„์ง„ ์˜ค์ผ๋Ÿฌ (h={h})')
303    ax.set_title('Stiff ๋ฌธ์ œ ์•ˆ์ •์„ฑ')
304    ax.legend()
305    ax.grid(True)
306
307    # ์กฐํ™” ์ง„๋™์ž
308    # y'' = -y  โ†’  y' = v, v' = -y
309    def harmonic(t, state):
310        y, v = state
311        return np.array([v, -y])
312
313    ax = axes[1, 1]
314    t_ho = np.linspace(0, 20, 1000)
315    y_ho_exact = np.cos(t_ho)  # y(0)=1, y'(0)=0
316
317    t, sol = euler_system(harmonic, np.array([1, 0]), (0, 20), 0.1)
318    ax.plot(t_ho, y_ho_exact, 'k-', linewidth=2, label='์ •ํ™•ํ•ด')
319    ax.plot(t, sol[:, 0], 'r-', label='์˜ค์ผ๋Ÿฌ (h=0.1)')
320    ax.set_title('์กฐํ™” ์ง„๋™์ž y\'\' = -y')
321    ax.set_xlabel('t')
322    ax.legend()
323    ax.grid(True)
324
325    plt.tight_layout()
326    plt.savefig('/opt/projects/01_Personal/03_Study/Numerical_Simulation/examples/ode_euler.png', dpi=150)
327    plt.close()
328    print("๊ทธ๋ž˜ํ”„ ์ €์žฅ: ode_euler.png")
329
330
331# =============================================================================
332# ํ…Œ์ŠคํŠธ
333# =============================================================================
334def main():
335    print("=" * 60)
336    print("์ƒ๋ฏธ๋ถ„๋ฐฉ์ •์‹ - ์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ•")
337    print("=" * 60)
338
339    # ์˜ˆ์ œ 1: ์ง€์ˆ˜ ๊ฐ์‡  dy/dt = -y
340    print("\n[์˜ˆ์ œ 1] ์ง€์ˆ˜ ๊ฐ์‡ : dy/dt = -y, y(0) = 1")
341    print("-" * 40)
342
343    f = lambda t, y: -y
344    exact = lambda t: np.exp(-t)
345
346    t, y_forward = euler_forward(f, 1, (0, 2), 0.2)
347    t, y_modified = euler_modified(f, 1, (0, 2), 0.2)
348
349    print(f"t=2์—์„œ:")
350    print(f"  ์ •ํ™•๊ฐ’:       {exact(2):.6f}")
351    print(f"  ์ „์ง„ ์˜ค์ผ๋Ÿฌ:  {y_forward[-1]:.6f}")
352    print(f"  ์ˆ˜์ • ์˜ค์ผ๋Ÿฌ:  {y_modified[-1]:.6f}")
353
354    # ์˜ˆ์ œ 2: ๋กœ์ง€์Šคํ‹ฑ ๋ฐฉ์ •์‹
355    print("\n[์˜ˆ์ œ 2] ๋กœ์ง€์Šคํ‹ฑ ๋ฐฉ์ •์‹: dy/dt = y(1-y), y(0) = 0.1")
356    print("-" * 40)
357
358    f_logistic = lambda t, y: y * (1 - y)
359    exact_logistic = lambda t: 0.1 * np.exp(t) / (1 + 0.1 * (np.exp(t) - 1))
360
361    t, y = euler_modified(f_logistic, 0.1, (0, 5), 0.1)
362    print(f"t=5์—์„œ:")
363    print(f"  ์ •ํ™•๊ฐ’:       {exact_logistic(5):.6f}")
364    print(f"  ์ˆ˜์ • ์˜ค์ผ๋Ÿฌ:  {y[-1]:.6f}")
365
366    # ์˜ˆ์ œ 3: ์ง„์ž ์šด๋™ (2์ฐจ ODE)
367    print("\n[์˜ˆ์ œ 3] ๋‹จ์ง„์ž: ฮธ'' = -sin(ฮธ), ฮธ(0) = ฯ€/4, ฮธ'(0) = 0")
368    print("-" * 40)
369
370    f_pendulum = lambda t, theta, omega: -np.sin(theta)
371    t, theta, omega = solve_second_order(f_pendulum, np.pi/4, 0, (0, 10), 0.01)
372
373    print(f"์ฃผ๊ธฐ์  ์šด๋™ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ")
374    print(f"  ์ดˆ๊ธฐ ๊ฐ๋„: {np.degrees(np.pi/4):.1f}ยฐ")
375    print(f"  t=10์—์„œ ๊ฐ๋„: {np.degrees(theta[-1]):.2f}ยฐ")
376
377    # ์˜ˆ์ œ 4: Lotka-Volterra (ํฌ์‹์ž-ํ”ผ์‹์ž ๋ชจ๋ธ)
378    print("\n[์˜ˆ์ œ 4] Lotka-Volterra: ํฌ์‹์ž-ํ”ผ์‹์ž ๋ชจ๋ธ")
379    print("-" * 40)
380
381    alpha, beta, gamma, delta = 1.5, 1.0, 3.0, 1.0
382
383    def lotka_volterra(t, state):
384        x, y = state  # x=ํ”ผ์‹์ž, y=ํฌ์‹์ž
385        dx = alpha * x - beta * x * y
386        dy = delta * x * y - gamma * y
387        return np.array([dx, dy])
388
389    t, sol = euler_system(lotka_volterra, np.array([10, 5]), (0, 15), 0.001)
390    print(f"  ์ดˆ๊ธฐ: ํ”ผ์‹์ž={10}, ํฌ์‹์ž={5}")
391    print(f"  t=15: ํ”ผ์‹์ž={sol[-1, 0]:.2f}, ํฌ์‹์ž={sol[-1, 1]:.2f}")
392
393    # ์˜ค์ฐจ ๋ถ„์„
394    analyze_euler_error()
395
396    # ์‹œ๊ฐํ™”
397    try:
398        plot_euler_comparison()
399    except Exception as e:
400        print(f"๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์‹คํŒจ: {e}")
401
402    print("\n" + "=" * 60)
403    print("์˜ค์ผ๋Ÿฌ ๋ฐฉ๋ฒ• ์ •๋ฆฌ")
404    print("=" * 60)
405    print("""
406    | ๋ฐฉ๋ฒ•        | ์ •ํ™•๋„ | ์•ˆ์ •์„ฑ    | ํŠน์ง•                    |
407    |------------|--------|----------|-------------------------|
408    | ์ „์ง„ ์˜ค์ผ๋Ÿฌ | O(h)   | ์กฐ๊ฑด๋ถ€   | ๊ฐ€์žฅ ๋‹จ์ˆœ, ๋ช…์‹œ์         |
409    | ํ›„์ง„ ์˜ค์ผ๋Ÿฌ | O(h)   | ๋ฌด์กฐ๊ฑด   | ์•”์‹œ์ , stiff์— ์ ํ•ฉ     |
410    | ์ˆ˜์ • ์˜ค์ผ๋Ÿฌ | O(hยฒ)  | ์กฐ๊ฑด๋ถ€   | 2์ฐจ ์ •ํ™•๋„, RK2์˜ ์ผ์ข…   |
411
412    ํ•œ๊ณ„:
413    - ์ •ํ™•๋„๊ฐ€ ๋‚ฎ์Œ (๋” ๋†’์€ ์ฐจ์ˆ˜ ํ•„์š”์‹œ RK4 ์‚ฌ์šฉ)
414    - ์—๋„ˆ์ง€ ๋ณด์กด ๋ถˆ๊ฐ€ (์‹ฌํ”Œ๋ ‰ํ‹ฑ ์ ๋ถ„๊ธฐ ํ•„์š”)
415
416    ์‹ค๋ฌด:
417    - scipy.integrate.odeint: ์ ์‘์  ๋‹ค๋‹จ๊ณ„ ๋ฐฉ๋ฒ•
418    - scipy.integrate.solve_ivp: ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ• ์„ ํƒ ๊ฐ€๋Šฅ
419    """)
420
421
422if __name__ == "__main__":
423    main()