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()