ODE ๊ณ ๊ธ‰

ODE ๊ณ ๊ธ‰

๊ฐœ์š”

๊ฐ•์„ฑ(stiff) ๋ฌธ์ œ, ์•”์‹œ์  ๋ฐฉ๋ฒ•, scipy.integrate์˜ ๊ณ ๊ธ‰ ์‚ฌ์šฉ๋ฒ•์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ์‘์šฉ์—์„œ ์ž์ฃผ ๋“ฑ์žฅํ•˜๋Š” ์–ด๋ ค์šด ODE ๋ฌธ์ œ๋“ค์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.


1. ๊ฐ•์„ฑ ๋ฌธ์ œ (Stiff Problems)

1.1 ๊ฐ•์„ฑ์˜ ์ •์˜

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp

"""
๊ฐ•์„ฑ ๋ฌธ์ œ์˜ ํŠน์ง•:
1. ํ•ด์˜ ์„ฑ๋ถ„๋“ค์ด ๋งค์šฐ ๋‹ค๋ฅธ ์‹œ๊ฐ„ ์Šค์ผ€์ผ์„ ๊ฐ€์ง
2. ์ผ๋ถ€ ์„ฑ๋ถ„์€ ๋น ๋ฅด๊ฒŒ ๊ฐ์‡ ํ•˜๊ณ , ๋‹ค๋ฅธ ์„ฑ๋ถ„์€ ๋А๋ฆฌ๊ฒŒ ๋ณ€ํ™”
3. ๋ช…์‹œ์  ๋ฐฉ๋ฒ• ์‚ฌ์šฉ ์‹œ ์•ˆ์ •์„ฑ์„ ์œ„ํ•ด ๋งค์šฐ ์ž‘์€ ์Šคํ… ํ•„์š”
"""

def stiff_example():
    """๊ฐ•์„ฑ ์‹œ์Šคํ…œ ์˜ˆ์‹œ"""
    # dyโ‚/dt = -500*yโ‚ + 500*yโ‚‚
    # dyโ‚‚/dt = yโ‚ - yโ‚‚
    #
    # ๊ณ ์œ ๊ฐ’: ฮปโ‚ โ‰ˆ -500.002, ฮปโ‚‚ โ‰ˆ -0.998
    # ๋น„์œจ: |ฮปโ‚/ฮปโ‚‚| โ‰ˆ 500 (๊ฐ•์„ฑ๋น„)

    def stiff_system(t, y):
        return [-500*y[0] + 500*y[1], y[0] - y[1]]

    y0 = [1.0, 0.0]
    t_span = (0, 5)

    # ๋ช…์‹œ์  RK45 (๋งŽ์€ ์Šคํ… ํ•„์š”)
    sol_rk45 = solve_ivp(stiff_system, t_span, y0, method='RK45',
                         rtol=1e-6, atol=1e-9)

    # ์•”์‹œ์  Radau (์ ์€ ์Šคํ…)
    sol_radau = solve_ivp(stiff_system, t_span, y0, method='Radau',
                          rtol=1e-6, atol=1e-9)

    print(f"RK45 ์Šคํ… ์ˆ˜: {len(sol_rk45.t)}")
    print(f"Radau ์Šคํ… ์ˆ˜: {len(sol_radau.t)}")

    fig, axes = plt.subplots(1, 2, figsize=(14, 5))

    axes[0].semilogy(sol_rk45.t, np.abs(sol_rk45.y[0]), 'b-', label='yโ‚')
    axes[0].semilogy(sol_rk45.t, np.abs(sol_rk45.y[1]), 'r-', label='yโ‚‚')
    axes[0].set_xlabel('t')
    axes[0].set_ylabel('|y|')
    axes[0].set_title(f'RK45 ({len(sol_rk45.t)} ์Šคํ…)')
    axes[0].legend()
    axes[0].grid(True)

    axes[1].semilogy(sol_radau.t, np.abs(sol_radau.y[0]), 'b-', label='yโ‚')
    axes[1].semilogy(sol_radau.t, np.abs(sol_radau.y[1]), 'r-', label='yโ‚‚')
    axes[1].set_xlabel('t')
    axes[1].set_ylabel('|y|')
    axes[1].set_title(f'Radau ({len(sol_radau.t)} ์Šคํ…)')
    axes[1].legend()
    axes[1].grid(True)

    plt.tight_layout()
    plt.show()

stiff_example()

1.2 ํ™”ํ•™ ๋ฐ˜์‘ ๋™์—ญํ•™

def chemical_kinetics():
    """๊ฐ•์„ฑ ํ™”ํ•™ ๋ฐ˜์‘ ์‹œ์Šคํ…œ (Robertson ๋ฌธ์ œ)"""
    # A โ†’ B (kโ‚ = 0.04)
    # B + B โ†’ C + B (kโ‚‚ = 3e7)
    # B + C โ†’ A + C (kโ‚ƒ = 1e4)

    k1, k2, k3 = 0.04, 3e7, 1e4

    def robertson(t, y):
        A, B, C = y
        dA = -k1*A + k3*B*C
        dB = k1*A - k2*B*B - k3*B*C
        dC = k2*B*B
        return [dA, dB, dC]

    y0 = [1.0, 0.0, 0.0]
    t_span = (0, 1e7)
    t_eval = np.logspace(-5, 7, 200)

    # BDF ๋ฐฉ๋ฒ• (๊ฐ•์„ฑ ๋ฌธ์ œ์— ์ ํ•ฉ)
    sol = solve_ivp(robertson, t_span, y0, method='BDF',
                    t_eval=t_eval, rtol=1e-8, atol=1e-10)

    fig, axes = plt.subplots(1, 2, figsize=(14, 5))

    # ์„ ํ˜• ์Šค์ผ€์ผ
    axes[0].semilogx(sol.t, sol.y[0], label='A')
    axes[0].semilogx(sol.t, sol.y[2], label='C')
    axes[0].set_xlabel('t')
    axes[0].set_ylabel('๋†๋„')
    axes[0].set_title('Robertson ํ™”ํ•™ ๋ฐ˜์‘ (A, C)')
    axes[0].legend()
    axes[0].grid(True)

    # B๋Š” ๋งค์šฐ ์ž‘์€ ๊ฐ’์ด๋ฏ€๋กœ ๋ณ„๋„ ํ‘œ์‹œ
    axes[1].semilogx(sol.t, sol.y[1] * 1e4, label='B ร— 10โด')
    axes[1].set_xlabel('t')
    axes[1].set_ylabel('๋†๋„ ร— 10โด')
    axes[1].set_title('Robertson ํ™”ํ•™ ๋ฐ˜์‘ (B)')
    axes[1].legend()
    axes[1].grid(True)

    plt.tight_layout()
    plt.show()

chemical_kinetics()

2. ์•”์‹œ์  ๋ฐฉ๋ฒ•

2.1 ํ›„์ง„ ์˜ค์ผ๋Ÿฌ (๋‹ค์‹œ ๋ณด๊ธฐ)

def backward_euler_system(f, jacobian, y0, t_span, n_steps, tol=1e-10):
    """
    ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ํ›„์ง„ ์˜ค์ผ๋Ÿฌ + ๋‰ดํ„ด-๋žฉ์Šจ

    y_{n+1} = y_n + h * f(t_{n+1}, y_{n+1})
    """
    t = np.linspace(t_span[0], t_span[1], n_steps + 1)
    h = t[1] - t[0]
    n = len(y0)

    y = np.zeros((n_steps + 1, n))
    y[0] = y0

    for i in range(n_steps):
        y_guess = y[i].copy()

        for _ in range(100):
            F = y_guess - y[i] - h * np.array(f(t[i+1], y_guess))
            J = np.eye(n) - h * np.array(jacobian(t[i+1], y_guess))

            delta = np.linalg.solve(J, -F)
            y_guess = y_guess + delta

            if np.linalg.norm(delta) < tol:
                break

        y[i+1] = y_guess

    return t, y

# ํ…Œ์ŠคํŠธ: ๊ฐ•์„ฑ ์‹œ์Šคํ…œ
def stiff_f(t, y):
    return [-500*y[0] + 500*y[1], y[0] - y[1]]

def stiff_jacobian(t, y):
    return [[-500, 500], [1, -1]]

t, y = backward_euler_system(stiff_f, stiff_jacobian, [1.0, 0.0], (0, 1), 50)

plt.figure(figsize=(10, 5))
plt.plot(t, y[:, 0], 'b-o', label='yโ‚')
plt.plot(t, y[:, 1], 'r-o', label='yโ‚‚')
plt.xlabel('t')
plt.ylabel('y')
plt.title('ํ›„์ง„ ์˜ค์ผ๋Ÿฌ (50 ์Šคํ…)')
plt.legend()
plt.grid(True)
plt.show()

2.2 Crank-Nicolson ๋ฐฉ๋ฒ•

def crank_nicolson(f, jacobian, y0, t_span, n_steps, tol=1e-10):
    """
    Crank-Nicolson (ํŠธ๋ž˜ํ”ผ์กฐ์ด๋‹ฌ ๊ทœ์น™)

    y_{n+1} = y_n + h/2 * (f(t_n, y_n) + f(t_{n+1}, y_{n+1}))

    2์ฐจ ์ •ํ™•๋„, A-์•ˆ์ •
    """
    t = np.linspace(t_span[0], t_span[1], n_steps + 1)
    h = t[1] - t[0]
    n = len(y0)

    y = np.zeros((n_steps + 1, n))
    y[0] = y0

    for i in range(n_steps):
        f_n = np.array(f(t[i], y[i]))
        y_guess = y[i] + h * f_n  # ์ดˆ๊ธฐ ์ถ”์ธก (์ „์ง„ ์˜ค์ผ๋Ÿฌ)

        for _ in range(100):
            f_new = np.array(f(t[i+1], y_guess))
            F = y_guess - y[i] - h/2 * (f_n + f_new)
            J = np.eye(n) - h/2 * np.array(jacobian(t[i+1], y_guess))

            delta = np.linalg.solve(J, -F)
            y_guess = y_guess + delta

            if np.linalg.norm(delta) < tol:
                break

        y[i+1] = y_guess

    return t, y

# ๋น„๊ต
t_be, y_be = backward_euler_system(stiff_f, stiff_jacobian, [1.0, 0.0], (0, 1), 20)
t_cn, y_cn = crank_nicolson(stiff_f, stiff_jacobian, [1.0, 0.0], (0, 1), 20)

# ์ฐธ์กฐ ํ•ด
sol_ref = solve_ivp(stiff_f, (0, 1), [1.0, 0.0], method='Radau',
                    t_eval=np.linspace(0, 1, 200))

plt.figure(figsize=(10, 5))
plt.plot(sol_ref.t, sol_ref.y[0], 'k-', linewidth=2, label='์ฐธ์กฐ')
plt.plot(t_be, y_be[:, 0], 'bo-', label='ํ›„์ง„ ์˜ค์ผ๋Ÿฌ')
plt.plot(t_cn, y_cn[:, 0], 'rs-', label='Crank-Nicolson')
plt.xlabel('t')
plt.ylabel('yโ‚')
plt.title('์•”์‹œ์  ๋ฐฉ๋ฒ• ๋น„๊ต')
plt.legend()
plt.grid(True)
plt.show()

2.3 BDF ๋ฐฉ๋ฒ•

"""
BDF (Backward Differentiation Formula) ๋ฐฉ๋ฒ•

BDF1 (ํ›„์ง„ ์˜ค์ผ๋Ÿฌ):
    y_{n+1} - y_n = h * f(t_{n+1}, y_{n+1})

BDF2:
    (3y_{n+1} - 4y_n + y_{n-1}) / 2 = h * f(t_{n+1}, y_{n+1})

BDF3:
    (11y_{n+1} - 18y_n + 9y_{n-1} - 2y_{n-2}) / 6 = h * f(t_{n+1}, y_{n+1})

ํŠน์ง•:
- A-์•ˆ์ • (BDF1, BDF2)
- ๊ฐ•์„ฑ ๋ฌธ์ œ์— ํšจ๊ณผ์ 
- scipy์˜ 'BDF' ์†”๋ฒ„๊ฐ€ ๊ฐ€๋ณ€ ์ฐจ์ˆ˜ BDF ๊ตฌํ˜„
"""

def bdf2_solver(f, jacobian, y0, t_span, n_steps, tol=1e-10):
    """BDF2 ๋ฐฉ๋ฒ•"""
    t = np.linspace(t_span[0], t_span[1], n_steps + 1)
    h = t[1] - t[0]
    n = len(y0)

    y = np.zeros((n_steps + 1, n))
    y[0] = y0

    # ์ฒซ ์Šคํ…์€ ํ›„์ง„ ์˜ค์ผ๋Ÿฌ๋กœ
    y_guess = y[0].copy()
    for _ in range(100):
        F = y_guess - y[0] - h * np.array(f(t[1], y_guess))
        J = np.eye(n) - h * np.array(jacobian(t[1], y_guess))
        delta = np.linalg.solve(J, -F)
        y_guess = y_guess + delta
        if np.linalg.norm(delta) < tol:
            break
    y[1] = y_guess

    # ์ดํ›„ BDF2
    for i in range(1, n_steps):
        y_guess = y[i].copy()

        for _ in range(100):
            f_new = np.array(f(t[i+1], y_guess))
            # BDF2: (3y_{n+1} - 4y_n + y_{n-1})/2 = h*f(t_{n+1}, y_{n+1})
            F = 1.5*y_guess - 2*y[i] + 0.5*y[i-1] - h * f_new
            J = 1.5*np.eye(n) - h * np.array(jacobian(t[i+1], y_guess))

            delta = np.linalg.solve(J, -F)
            y_guess = y_guess + delta

            if np.linalg.norm(delta) < tol:
                break

        y[i+1] = y_guess

    return t, y

t_bdf2, y_bdf2 = bdf2_solver(stiff_f, stiff_jacobian, [1.0, 0.0], (0, 1), 20)

plt.figure(figsize=(10, 5))
plt.plot(sol_ref.t, sol_ref.y[0], 'k-', linewidth=2, label='์ฐธ์กฐ')
plt.plot(t_bdf2, y_bdf2[:, 0], 'go-', label='BDF2')
plt.xlabel('t')
plt.ylabel('yโ‚')
plt.title('BDF2 ๋ฐฉ๋ฒ•')
plt.legend()
plt.grid(True)
plt.show()

3. scipy.integrate ๊ณ ๊ธ‰

3.1 ์•ผ์ฝ”๋น„์•ˆ ์ œ๊ณต

def with_jacobian():
    """์•ผ์ฝ”๋น„์•ˆ์„ ์ œ๊ณตํ•˜๋ฉด ์„ฑ๋Šฅ ํ–ฅ์ƒ"""

    def system(t, y):
        return [
            -100*y[0] + y[1],
            y[0] - 100*y[1] + y[2],
            y[1] - 100*y[2]
        ]

    def jacobian(t, y):
        return [
            [-100, 1, 0],
            [1, -100, 1],
            [0, 1, -100]
        ]

    y0 = [1.0, 0.0, 0.0]
    t_span = (0, 0.5)

    import time

    # ์•ผ์ฝ”๋น„์•ˆ ์—†์ด
    start = time.time()
    sol1 = solve_ivp(system, t_span, y0, method='Radau')
    time1 = time.time() - start

    # ์•ผ์ฝ”๋น„์•ˆ ์ œ๊ณต
    start = time.time()
    sol2 = solve_ivp(system, t_span, y0, method='Radau', jac=jacobian)
    time2 = time.time() - start

    print(f"์•ผ์ฝ”๋น„์•ˆ ์—†์ด: {time1:.4f}์ดˆ, {len(sol1.t)} ์Šคํ…")
    print(f"์•ผ์ฝ”๋น„์•ˆ ์ œ๊ณต: {time2:.4f}์ดˆ, {len(sol2.t)} ์Šคํ…")

with_jacobian()

3.2 Dense Output

def dense_output_example():
    """Dense output์œผ๋กœ ์—ฐ์†์ ์ธ ํ•ด ์–ป๊ธฐ"""

    def oscillator(t, y):
        return [y[1], -y[0]]

    sol = solve_ivp(oscillator, (0, 10), [1, 0],
                    method='RK45', dense_output=True)

    # ์ž„์˜์˜ ์‹œ๊ฐ„์—์„œ ํ•ด ํ‰๊ฐ€
    t_dense = np.linspace(0, 10, 1000)
    y_dense = sol.sol(t_dense)

    plt.figure(figsize=(10, 5))
    plt.plot(t_dense, y_dense[0], 'b-', label='Dense output')
    plt.plot(sol.t, sol.y[0], 'ro', markersize=5, label='์†”๋ฒ„ ์Šคํ…')
    plt.xlabel('t')
    plt.ylabel('y')
    plt.title('Dense Output')
    plt.legend()
    plt.grid(True)
    plt.show()

dense_output_example()

3.3 ์งˆ๋Ÿ‰ ํ–‰๋ ฌ (DAE)

def dae_example():
    """
    ๋ฏธ๋ถ„-๋Œ€์ˆ˜ ๋ฐฉ์ •์‹ (DAE)

    M * y' = f(t, y)

    ์—ฌ๊ธฐ์„œ M์ด ํŠน์ด ํ–‰๋ ฌ์ด๋ฉด ๋Œ€์ˆ˜ ๊ตฌ์†์กฐ๊ฑด ํฌํ•จ
    """

    # ์˜ˆ: ๋‹จ์ง„์ž (์ œ์•ฝ์กฐ๊ฑด: xยฒ + yยฒ = Lยฒ)
    # x'' = ฮปx
    # y'' = ฮปy - g
    # xยฒ + yยฒ = Lยฒ

    # ์ธ๋ฑ์Šค-1 ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜
    g = 9.8
    L = 1.0

    def pendulum(t, y):
        x, y_pos, vx, vy, lam = y

        # ๊ฐ€์†๋„
        ax = lam * x
        ay = lam * y_pos - g

        # ๊ตฌ์†์กฐ๊ฑด (ฮป ๊ฒฐ์ •)
        # ์‚ฌ์‹ค ์ด๊ฑด ๋” ๋ณต์žกํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”...
        # ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋งŒ ๋ณด์—ฌ์คŒ

        return [vx, vy, ax, ay, 0]

    # scipy์—์„œ๋Š” mass_matrix ์˜ต์…˜์œผ๋กœ DAE ํ’€ ์ˆ˜ ์žˆ์Œ (Radau)
    # ์—ฌ๊ธฐ์„œ๋Š” ๊ฐœ๋…๋งŒ ์„ค๋ช…

    print("DAE๋Š” scipy.integrate.solve_ivp์˜ Radau ์†”๋ฒ„์™€")
    print("mass_matrix ์˜ต์…˜์œผ๋กœ ํ’€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")

dae_example()

4. ๊ฒฝ๊ณ„๊ฐ’ ๋ฌธ์ œ (BVP)

4.1 ์ŠˆํŒ… ๋ฐฉ๋ฒ•

from scipy.optimize import brentq

def shooting_method():
    """
    ์ŠˆํŒ… ๋ฐฉ๋ฒ•์œผ๋กœ BVP ํ’€๊ธฐ

    y'' = -y, y(0) = 0, y(ฯ€) = 0
    ์ •ํ™•ํ•ด: y = sin(x)
    """

    def ode(t, y):
        return [y[1], -y[0]]

    def shoot(initial_slope):
        """์ฃผ์–ด์ง„ ์ดˆ๊ธฐ ๊ธฐ์šธ๊ธฐ๋กœ IVP ํ’€๊ธฐ"""
        sol = solve_ivp(ode, (0, np.pi), [0, initial_slope],
                        dense_output=True)
        return sol.sol(np.pi)[0]  # y(ฯ€) ๋ฐ˜ํ™˜

    # ์˜ฌ๋ฐ”๋ฅธ ์ดˆ๊ธฐ ๊ธฐ์šธ๊ธฐ ์ฐพ๊ธฐ
    slope = brentq(shoot, 0.5, 1.5)
    print(f"์ฐพ์€ ์ดˆ๊ธฐ ๊ธฐ์šธ๊ธฐ: {slope:.6f} (์ •ํ™•๊ฐ’: 1.0)")

    # ์ตœ์ข… ํ•ด
    sol = solve_ivp(ode, (0, np.pi), [0, slope], dense_output=True)
    t = np.linspace(0, np.pi, 100)

    plt.figure(figsize=(10, 5))
    plt.plot(t, sol.sol(t)[0], 'b-', label='์ˆ˜์น˜ํ•ด')
    plt.plot(t, np.sin(t), 'r--', label='์ •ํ™•ํ•ด sin(x)')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('์ŠˆํŒ… ๋ฐฉ๋ฒ•')
    plt.legend()
    plt.grid(True)
    plt.show()

shooting_method()

4.2 scipy.integrate.solve_bvp

from scipy.integrate import solve_bvp

def bvp_example():
    """
    y'' + y = 0, y(0) = 0, y(ฯ€) = 0

    1์ฐจ ์‹œ์Šคํ…œ์œผ๋กœ ๋ณ€ํ™˜:
    yโ‚' = yโ‚‚
    yโ‚‚' = -yโ‚
    """

    def ode(x, y):
        return np.vstack([y[1], -y[0]])

    def bc(ya, yb):
        return np.array([ya[0], yb[0]])  # y(0) = 0, y(ฯ€) = 0

    # ์ดˆ๊ธฐ ์ถ”์ธก
    x = np.linspace(0, np.pi, 10)
    y = np.zeros((2, x.size))
    y[0] = np.sin(x)  # ์ดˆ๊ธฐ ์ถ”์ธก

    sol = solve_bvp(ode, bc, x, y)

    x_plot = np.linspace(0, np.pi, 100)
    y_plot = sol.sol(x_plot)

    plt.figure(figsize=(10, 5))
    plt.plot(x_plot, y_plot[0], 'b-', label='์ˆ˜์น˜ํ•ด')
    plt.plot(x_plot, np.sin(x_plot), 'r--', label='์ •ํ™•ํ•ด')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('solve_bvp')
    plt.legend()
    plt.grid(True)
    plt.show()

bvp_example()

4.3 ๋น„์„ ํ˜• BVP

def nonlinear_bvp():
    """
    ๋น„์„ ํ˜• BVP: y'' = yยฒ - 1
    y(0) = 0, y(1) = 1
    """

    def ode(x, y):
        return np.vstack([y[1], y[0]**2 - 1])

    def bc(ya, yb):
        return np.array([ya[0], yb[0] - 1])

    x = np.linspace(0, 1, 10)
    y = np.zeros((2, x.size))
    y[0] = x  # ์„ ํ˜• ์ดˆ๊ธฐ ์ถ”์ธก

    sol = solve_bvp(ode, bc, x, y)

    if sol.success:
        x_plot = np.linspace(0, 1, 100)
        y_plot = sol.sol(x_plot)

        plt.figure(figsize=(10, 5))
        plt.plot(x_plot, y_plot[0], 'b-', linewidth=2)
        plt.scatter([0, 1], [0, 1], color='red', s=100, zorder=5)
        plt.xlabel('x')
        plt.ylabel('y')
        plt.title("๋น„์„ ํ˜• BVP: y'' = yยฒ - 1")
        plt.grid(True)
        plt.show()
    else:
        print("์ˆ˜๋ ด ์‹คํŒจ")

nonlinear_bvp()

5. ํŠน์ˆ˜ ๋ฌธ์ œ

5.1 ์ฃผ๊ธฐ ๊ถค๋„ ์ฐพ๊ธฐ

def find_periodic_orbit():
    """Van der Pol ์ง„๋™์ž์˜ ์ฃผ๊ธฐ ๊ถค๋„"""
    mu = 1.0

    def vdp(t, y):
        return [y[1], mu * (1 - y[0]**2) * y[1] - y[0]]

    # ํฌ์•™์นด๋ ˆ ๋‹จ๋ฉด
    def poincare(t, y):
        return y[1]  # y' = 0์ผ ๋•Œ

    poincare.direction = -1  # ์œ„์—์„œ ์•„๋ž˜๋กœ ๊ต์ฐจ

    # ์ถฉ๋ถ„ํžˆ ๊ธด ์‹œ๊ฐ„ ์ ๋ถ„
    sol = solve_ivp(vdp, (0, 100), [2, 0], events=poincare,
                    dense_output=True, max_step=0.01)

    # ๋ฆฌ๋ฐ‹ ์‚ฌ์ดํด์— ์ˆ˜๋ ด ํ›„์˜ ๊ต์ฐจ์ ๋“ค
    crossings = sol.y_events[0][-5:, 0]  # ๋งˆ์ง€๋ง‰ 5๊ฐœ ๊ต์ฐจ์ ์˜ x ๊ฐ’
    print(f"๋ฆฌ๋ฐ‹ ์‚ฌ์ดํด ์ง„ํญ: {np.mean(crossings):.6f}")

    # ๋งˆ์ง€๋ง‰ ์ฃผ๊ธฐ ์‹œ๊ฐํ™”
    t_last = sol.t_events[0][-2:]
    period = t_last[1] - t_last[0]
    print(f"์ฃผ๊ธฐ: {period:.6f}")

    t_plot = np.linspace(t_last[0], t_last[1], 200)
    y_plot = sol.sol(t_plot)

    fig, axes = plt.subplots(1, 2, figsize=(12, 5))

    axes[0].plot(y_plot[0], y_plot[1])
    axes[0].set_xlabel('x')
    axes[0].set_ylabel("x'")
    axes[0].set_title('๋ฆฌ๋ฐ‹ ์‚ฌ์ดํด')
    axes[0].grid(True)

    axes[1].plot(t_plot - t_last[0], y_plot[0])
    axes[1].set_xlabel('t (์ฃผ๊ธฐ ๋‚ด)')
    axes[1].set_ylabel('x')
    axes[1].set_title(f'ํ•œ ์ฃผ๊ธฐ (T = {period:.4f})')
    axes[1].grid(True)

    plt.tight_layout()
    plt.show()

find_periodic_orbit()

5.2 ๋ถ„๊ธฐ ๋ถ„์„

def bifurcation_analysis():
    """ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋”ฐ๋ฅธ ์ •์ƒ ์ƒํƒœ ๋ณ€ํ™”"""
    # dy/dt = r*y - yยณ
    # ํ‰ํ˜•์ : y* = 0 ๋˜๋Š” y* = ยฑโˆšr (r > 0)

    r_values = np.linspace(-1, 2, 100)

    plt.figure(figsize=(10, 6))

    # ์•ˆ์ • ๋ถ„๊ธฐ
    r_pos = r_values[r_values >= 0]
    plt.plot(r_pos, np.sqrt(r_pos), 'b-', linewidth=2, label='์•ˆ์ •')
    plt.plot(r_pos, -np.sqrt(r_pos), 'b-', linewidth=2)

    # ๋ถˆ์•ˆ์ • ๋ถ„๊ธฐ
    plt.plot(r_values[r_values <= 0], np.zeros_like(r_values[r_values <= 0]),
             'r--', linewidth=2, label='๋ถˆ์•ˆ์ •')
    plt.plot(r_values[r_values > 0], np.zeros_like(r_values[r_values > 0]),
             'r--', linewidth=2)

    plt.xlabel('r')
    plt.ylabel('y*')
    plt.title("ํ”ผ์น˜ํฌํฌ ๋ถ„๊ธฐ: y' = ry - yยณ")
    plt.legend()
    plt.grid(True)
    plt.axhline(y=0, color='k', linewidth=0.5)
    plt.axvline(x=0, color='k', linewidth=0.5)
    plt.show()

bifurcation_analysis()

์—ฐ์Šต ๋ฌธ์ œ

๋ฌธ์ œ 1

๋‹ค์Œ ๊ฐ•์„ฑ ์‹œ์Šคํ…œ์„ BDF์™€ Radau๋กœ ํ’€๊ณ  ๋น„๊ตํ•˜์„ธ์š”: dyโ‚/dt = -1000yโ‚ + yโ‚‚ dyโ‚‚/dt = 999yโ‚ - 2*yโ‚‚ yโ‚(0) = 1, yโ‚‚(0) = 0

def exercise_1():
    def system(t, y):
        return [-1000*y[0] + y[1], 999*y[0] - 2*y[1]]

    y0 = [1.0, 0.0]
    t_span = (0, 1)

    sol_bdf = solve_ivp(system, t_span, y0, method='BDF',
                        t_eval=np.linspace(0, 1, 100))
    sol_radau = solve_ivp(system, t_span, y0, method='Radau',
                          t_eval=np.linspace(0, 1, 100))

    fig, axes = plt.subplots(1, 2, figsize=(12, 5))

    axes[0].semilogy(sol_bdf.t, np.abs(sol_bdf.y[0]), label='yโ‚')
    axes[0].semilogy(sol_bdf.t, np.abs(sol_bdf.y[1]), label='yโ‚‚')
    axes[0].set_title('BDF')
    axes[0].legend()
    axes[0].grid(True)

    axes[1].semilogy(sol_radau.t, np.abs(sol_radau.y[0]), label='yโ‚')
    axes[1].semilogy(sol_radau.t, np.abs(sol_radau.y[1]), label='yโ‚‚')
    axes[1].set_title('Radau')
    axes[1].legend()
    axes[1].grid(True)

    plt.tight_layout()
    plt.show()

exercise_1()

์š”์•ฝ

๋ฌธ์ œ ์œ ํ˜• ๊ถŒ์žฅ ์†”๋ฒ„ ํŠน์ง•
์ผ๋ฐ˜ ๋น„๊ฐ•์„ฑ RK45, DOP853 ๋ช…์‹œ์ , ๋น ๋ฆ„
๊ฐ•์„ฑ ๋ฌธ์ œ Radau, BDF ์•”์‹œ์ , ์•ˆ์ •
DAE Radau + mass_matrix ๋Œ€์ˆ˜ ๊ตฌ์†์กฐ๊ฑด
BVP solve_bvp, ์ŠˆํŒ… ๊ฒฝ๊ณ„์กฐ๊ฑด
์•”์‹œ์  ๋ฐฉ๋ฒ• ์ฐจ์ˆ˜ A-์•ˆ์ •
ํ›„์ง„ ์˜ค์ผ๋Ÿฌ 1 O
Crank-Nicolson 2 O
BDF1-2 1-2 O
BDF3-6 3-6 X
to navigate between lessons