18. MHD ์ˆ˜์น˜ํ•ด๋ฒ• (MHD Numerical Methods)

18. MHD ์ˆ˜์น˜ํ•ด๋ฒ• (MHD Numerical Methods)

ํ•™์Šต ๋ชฉํ‘œ

  • MHD ๋ฐฉ์ •์‹์˜ ๋ณด์กด ํ˜•ํƒœ ์ดํ•ด
  • ์œ ํ•œ ์ฒด์ ๋ฒ• ๊ธฐ์ดˆ ํ•™์Šต
  • Godunov ์œ ํ˜• ์Šคํ‚ด ์ดํ•ด
  • MHD Riemann ๋ฌธ์ œ ํŒŒ์•…
  • ๊ฐ„๋‹จํ•œ MHD ์ถฉ๊ฒฉํŒŒ ๊ด€ ๋ฌธ์ œ ๊ตฌํ˜„
  • div B = 0 ์ œ์•ฝ์กฐ๊ฑด ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•

1. MHD ๋ฐฉ์ •์‹์˜ ๋ณด์กด ํ˜•ํƒœ

1.1 1D MHD ๋ณด์กด ํ˜•ํƒœ

1D ์ด์ƒ MHD ๋ณด์กด ํ˜•ํƒœ:

โˆ‚U/โˆ‚t + โˆ‚F/โˆ‚x = 0

๋ณด์กด ๋ณ€์ˆ˜ U:
    โŽก ฯ     โŽค  (๋ฐ€๋„)
    โŽข ฯvx   โŽฅ  (x-์šด๋™๋Ÿ‰)
    โŽข ฯvy   โŽฅ  (y-์šด๋™๋Ÿ‰)
U = โŽข ฯvz   โŽฅ  (z-์šด๋™๋Ÿ‰)
    โŽข By    โŽฅ  (y-์ž๊ธฐ์žฅ)
    โŽข Bz    โŽฅ  (z-์ž๊ธฐ์žฅ)
    โŽฃ E     โŽฆ  (์ด ์—๋„ˆ์ง€)

(Bx = const, 1D์—์„œ)

ํ”Œ๋Ÿญ์Šค F:
    โŽก ฯvx                           โŽค
    โŽข ฯvxยฒ + p* - Bxยฒ/ฮผโ‚€            โŽฅ
    โŽข ฯvxvy - BxBy/ฮผโ‚€               โŽฅ
F = โŽข ฯvxvz - BxBz/ฮผโ‚€               โŽฅ
    โŽข vxBy - vyBx                   โŽฅ
    โŽข vxBz - vzBx                   โŽฅ
    โŽฃ (E + p*)vx - Bx(vยทB)/ฮผโ‚€       โŽฆ

์—ฌ๊ธฐ์„œ:
p* = p + Bยฒ/2ฮผโ‚€  (์ด ์••๋ ฅ)
E = p/(ฮณ-1) + ฯvยฒ/2 + Bยฒ/2ฮผโ‚€  (์ด ์—๋„ˆ์ง€)
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import eig

# ๋ฌผ๋ฆฌ ์ƒ์ˆ˜ (์ฝ”๋“œ ๋‹จ์œ„: ฮผโ‚€ = 1)
gamma = 5/3  # ๋น„์—ด๋น„

def primitive_to_conservative(rho, vx, vy, vz, Bx, By, Bz, p):
    """์›์‹œ๋ณ€์ˆ˜ -> ๋ณด์กด๋ณ€์ˆ˜ ๋ณ€ํ™˜"""
    E = p / (gamma - 1) + 0.5 * rho * (vx**2 + vy**2 + vz**2) + \
        0.5 * (Bx**2 + By**2 + Bz**2)

    U = np.array([rho, rho*vx, rho*vy, rho*vz, By, Bz, E])
    return U

def conservative_to_primitive(U, Bx):
    """๋ณด์กด๋ณ€์ˆ˜ -> ์›์‹œ๋ณ€์ˆ˜ ๋ณ€ํ™˜"""
    rho = U[0]
    vx = U[1] / rho
    vy = U[2] / rho
    vz = U[3] / rho
    By = U[4]
    Bz = U[5]
    E = U[6]

    p = (gamma - 1) * (E - 0.5 * rho * (vx**2 + vy**2 + vz**2) -
                       0.5 * (Bx**2 + By**2 + Bz**2))

    return rho, vx, vy, vz, Bx, By, Bz, p

def compute_flux(U, Bx):
    """ํ”Œ๋Ÿญ์Šค ๊ณ„์‚ฐ"""
    rho, vx, vy, vz, _, By, Bz, p = conservative_to_primitive(U, Bx)

    B2 = Bx**2 + By**2 + Bz**2
    p_star = p + 0.5 * B2  # ์ด ์••๋ ฅ
    E = U[6]
    vB = vx * Bx + vy * By + vz * Bz

    F = np.array([
        rho * vx,
        rho * vx**2 + p_star - Bx**2,
        rho * vx * vy - Bx * By,
        rho * vx * vz - Bx * Bz,
        vx * By - vy * Bx,
        vx * Bz - vz * Bx,
        (E + p_star) * vx - Bx * vB
    ])

    return F

def mhd_wave_speeds(rho, vx, p, Bx, By, Bz):
    """MHD ํŒŒ๋™ ์†๋„ ๊ณ„์‚ฐ"""
    B2 = Bx**2 + By**2 + Bz**2
    cs2 = gamma * p / rho  # ์Œ์† ์ œ๊ณฑ
    ca2 = B2 / rho         # Alfven ์†๋„ ์ œ๊ณฑ
    cax2 = Bx**2 / rho     # x-๋ฐฉํ–ฅ Alfven

    # ๋น ๋ฅธ/๋А๋ฆฐ ์ž๊ธฐ์ŒํŒŒ
    term1 = 0.5 * (cs2 + ca2)
    term2 = 0.5 * np.sqrt((cs2 + ca2)**2 - 4 * cs2 * cax2)

    cf = np.sqrt(term1 + term2)  # Fast
    ca = np.sqrt(cax2)           # Alfven
    cs = np.sqrt(max(term1 - term2, 0))  # Slow

    return cf, ca, cs

print("=" * 60)
print("1D MHD ๋ณด์กด ํ˜•ํƒœ")
print("=" * 60)

# ์˜ˆ์ œ: ์ดˆ๊ธฐ ์ƒํƒœ
rho = 1.0
vx, vy, vz = 0.0, 0.0, 0.0
Bx, By, Bz = 1.0, 0.5, 0.0
p = 1.0

U = primitive_to_conservative(rho, vx, vy, vz, Bx, By, Bz, p)
F = compute_flux(U, Bx)

print("\n์›์‹œ ๋ณ€์ˆ˜:")
print(f"  ฯ={rho}, v=({vx},{vy},{vz}), B=({Bx},{By},{Bz}), p={p}")
print("\n๋ณด์กด ๋ณ€์ˆ˜ U:")
print(f"  {U}")
print("\nํ”Œ๋Ÿญ์Šค F:")
print(f"  {F}")

cf, ca, cs = mhd_wave_speeds(rho, vx, p, Bx, By, Bz)
print(f"\nํŒŒ๋™ ์†๋„: cf={cf:.3f}, ca={ca:.3f}, cs={cs:.3f}")

1.2 MHD ๊ณ ์œ  ๊ตฌ์กฐ

MHD ํŠน์„ฑ (7๊ฐœ ํŒŒ๋™):

ฮปโ‚ = vx - cf  (๋น ๋ฅธ ์ž๊ธฐ์ŒํŒŒ, ์ขŒ)
ฮปโ‚‚ = vx - ca  (Alfven ํŒŒ, ์ขŒ)
ฮปโ‚ƒ = vx - cs  (๋А๋ฆฐ ์ž๊ธฐ์ŒํŒŒ, ์ขŒ)
ฮปโ‚„ = vx       (์—”ํŠธ๋กœํ”ผ ํŒŒ)
ฮปโ‚… = vx + cs  (๋А๋ฆฐ ์ž๊ธฐ์ŒํŒŒ, ์šฐ)
ฮปโ‚† = vx + ca  (Alfven ํŒŒ, ์šฐ)
ฮปโ‚‡ = vx + cf  (๋น ๋ฅธ ์ž๊ธฐ์ŒํŒŒ, ์šฐ)

์—ฌ๊ธฐ์„œ:
cf = โˆš[(csยฒ + caยฒ)/2 + โˆš((csยฒ + caยฒ)ยฒ - 4csยฒcaxยฒ)/2]  (fast)
cs = โˆš[(csยฒ + caยฒ)/2 - โˆš((csยฒ + caยฒ)ยฒ - 4csยฒcaxยฒ)/2]  (slow)
ca = |Bx|/โˆšฯ                                          (Alfven)
def visualize_mhd_characteristics():
    """MHD ํŠน์„ฑ ์‹œ๊ฐํ™”"""

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

    # (1) ํŒŒ๋™ ์†๋„ vs Bx (By = 0.5, Bz = 0 ๊ณ ์ •)
    ax1 = axes[0]

    rho = 1.0
    p = 1.0
    vx = 0.0
    By, Bz = 0.5, 0.0

    Bx_range = np.linspace(0.01, 2.0, 100)

    cf_list, ca_list, cs_list = [], [], []

    for Bx in Bx_range:
        cf, ca, cs = mhd_wave_speeds(rho, vx, p, Bx, By, Bz)
        cf_list.append(cf)
        ca_list.append(ca)
        cs_list.append(cs)

    ax1.plot(Bx_range, cf_list, 'b-', linewidth=2, label='Fast (cf)')
    ax1.plot(Bx_range, ca_list, 'g--', linewidth=2, label='Alfven (ca)')
    ax1.plot(Bx_range, cs_list, 'r-', linewidth=2, label='Slow (cs)')

    # ์Œ์† ์ฐธ์กฐ์„ 
    cs_sound = np.sqrt(gamma * p / rho)
    ax1.axhline(y=cs_sound, color='gray', linestyle=':', label=f'Sound ({cs_sound:.2f})')

    ax1.set_xlabel('Bx')
    ax1.set_ylabel('Wave Speed')
    ax1.set_title('MHD ํŒŒ๋™ ์†๋„ vs Bx')
    ax1.legend()
    ax1.grid(True, alpha=0.3)

    # (2) x-t ๋‹ค์ด์–ด๊ทธ๋žจ (ํŠน์„ฑ์„ )
    ax2 = axes[1]

    # ํŠน์ • ์ƒํƒœ์—์„œ์˜ ํŠน์„ฑ์„ 
    Bx = 1.0
    cf, ca, cs = mhd_wave_speeds(rho, vx, p, Bx, By, Bz)

    x0 = 0  # ์ดˆ๊ธฐ ์œ„์น˜
    t = np.linspace(0, 1, 50)

    # 7๊ฐœ ํŠน์„ฑ์„ 
    speeds = [-cf, -ca, -cs, 0, cs, ca, cf]
    labels = ['-cf', '-ca', '-cs', 'entropy', '+cs', '+ca', '+cf']
    colors = ['blue', 'green', 'red', 'black', 'red', 'green', 'blue']

    for speed, label, color in zip(speeds, labels, colors):
        x = x0 + speed * t
        ax2.plot(x, t, color=color, linewidth=1.5, label=label)

    ax2.set_xlabel('x')
    ax2.set_ylabel('t')
    ax2.set_title('MHD ํŠน์„ฑ์„  (x-t ๋‹ค์ด์–ด๊ทธ๋žจ)')
    ax2.legend(loc='upper right', fontsize=8)
    ax2.grid(True, alpha=0.3)
    ax2.set_xlim(-2, 2)

    plt.tight_layout()
    plt.savefig('mhd_characteristics.png', dpi=150, bbox_inches='tight')
    plt.show()

# visualize_mhd_characteristics()

2. ์œ ํ•œ ์ฒด์ ๋ฒ• ๊ธฐ์ดˆ

2.1 ์ ๋ถ„ ํ˜•ํƒœ์™€ ์…€ ํ‰๊ท 

์œ ํ•œ ์ฒด์ ๋ฒ• (Finite Volume Method):

์ ๋ถ„ ํ˜•ํƒœ:
d/dt โˆซ U dx + [F(xโ‚‚) - F(xโ‚)] = 0

์…€ ํ‰๊ท :
Uแตข = (1/ฮ”x) โˆซ_{xแตขโ‚‹โ‚/โ‚‚}^{xแตขโ‚Šโ‚/โ‚‚} U dx

๋ฐ˜์ด์‚ฐํ™”:
dUแตข/dt = -(F_{i+1/2} - F_{i-1/2}) / ฮ”x

์ˆ˜์น˜ ํ”Œ๋Ÿญ์Šค:
F_{i+1/2} = F(Uแตข, Uแตขโ‚Šโ‚)  (Riemann ์†”๋ฒ„ ๋˜๋Š” ๊ทผ์‚ฌ)

์žฅ์ :
- ๋ณด์กด ํ˜•ํƒœ ์ž๋™ ๋งŒ์กฑ
- ๋ถˆ์—ฐ์† ์ฒ˜๋ฆฌ ์ž์—ฐ์Šค๋Ÿฌ์›€
- ๋‹ค์–‘ํ•œ ๊ฒฉ์ž ํ˜•ํƒœ ์ ์šฉ ๊ฐ€๋Šฅ
def finite_volume_concept():
    """์œ ํ•œ ์ฒด์ ๋ฒ• ๊ฐœ๋… ์‹œ๊ฐํ™”"""

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

    # (1) ์…€ ํ‰๊ท ๊ณผ ํ”Œ๋Ÿญ์Šค
    ax1 = axes[0]

    # ์…€ ๊ฒฝ๊ณ„
    x_faces = np.arange(0, 6)
    x_centers = x_faces[:-1] + 0.5

    # ์…€ ํ‰๊ท ๊ฐ’ (์˜ˆ์‹œ)
    U_avg = np.array([1.0, 0.8, 1.2, 0.6, 0.9])

    # ์…€ ๊ทธ๋ฆฌ๊ธฐ
    for i, (x_l, x_r, U) in enumerate(zip(x_faces[:-1], x_faces[1:], U_avg)):
        ax1.fill([x_l, x_r, x_r, x_l], [0, 0, U, U], alpha=0.3,
                color=f'C{i}', edgecolor='black', linewidth=1.5)
        ax1.text((x_l + x_r)/2, U/2, f'$U_{i+1}$', ha='center', va='center', fontsize=11)

    # ํ”Œ๋Ÿญ์Šค ํ™”์‚ดํ‘œ
    for x in x_faces[1:-1]:
        ax1.annotate('', xy=(x, 1.5), xytext=(x-0.3, 1.5),
                    arrowprops=dict(arrowstyle='->', color='red', lw=2))
        ax1.text(x, 1.6, f'$F_{{i+1/2}}$', ha='center', fontsize=10, color='red')

    ax1.set_xlabel('x')
    ax1.set_ylabel('U')
    ax1.set_title('์œ ํ•œ ์ฒด์ ๋ฒ•: ์…€ ํ‰๊ท ๊ณผ ์ˆ˜์น˜ ํ”Œ๋Ÿญ์Šค')
    ax1.set_xlim(-0.5, 5.5)
    ax1.set_ylim(0, 2)
    ax1.grid(True, alpha=0.3)

    # (2) Riemann ๋ฌธ์ œ
    ax2 = axes[1]

    # ์ดˆ๊ธฐ ๋ถˆ์—ฐ์†
    x = np.linspace(-1, 1, 200)
    U_L = 1.0
    U_R = 0.5

    U_init = np.where(x < 0, U_L, U_R)
    ax2.plot(x, U_init, 'b-', linewidth=2, label='์ดˆ๊ธฐ ์กฐ๊ฑด')

    # Riemann ํ•ด (๊ฐœ๋…์ )
    # ์ถฉ๊ฒฉํŒŒ, ์ ‘์ด‰ ๋ถˆ์—ฐ์†, ํŒฝ์ฐฝํŒŒ ํ‘œ์‹œ
    t = 0.3
    x_shock = 0.3  # ์ถฉ๊ฒฉํŒŒ ์œ„์น˜

    U_riemann = np.where(x < -0.2, U_L,
                        np.where(x < 0, U_L - (U_L - 0.8) * (x + 0.2) / 0.2,
                                np.where(x < x_shock, 0.8, U_R)))

    ax2.plot(x, U_riemann, 'r--', linewidth=2, label=f't = {t}')

    # ํŒŒ๋™ ํ‘œ์‹œ
    ax2.axvline(x=-0.2, color='green', linestyle=':', label='ํŒฝ์ฐฝํŒŒ ์‹œ์ž‘')
    ax2.axvline(x=0, color='purple', linestyle=':', label='์ ‘์ด‰ ๋ถˆ์—ฐ์†')
    ax2.axvline(x=x_shock, color='orange', linestyle=':', label='์ถฉ๊ฒฉํŒŒ')

    ax2.set_xlabel('x')
    ax2.set_ylabel('U')
    ax2.set_title('Riemann ๋ฌธ์ œ: ๋ถˆ์—ฐ์† ์ดˆ๊ธฐ์กฐ๊ฑด์˜ ์‹œ๊ฐ„ ์ „๊ฐœ')
    ax2.legend()
    ax2.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig('finite_volume.png', dpi=150, bbox_inches='tight')
    plt.show()

# finite_volume_concept()

3. Godunov ์œ ํ˜• ์Šคํ‚ด

3.1 Lax-Friedrichs ํ”Œ๋Ÿญ์Šค

Lax-Friedrichs (LxF) ํ”Œ๋Ÿญ์Šค:

F_{i+1/2} = (1/2)[F(Uแตข) + F(Uแตขโ‚Šโ‚)] - (ฮ”x/2ฮ”t)(Uแตขโ‚Šโ‚ - Uแตข)

๋˜๋Š” ์ง€์—ญ LxF:
F_{i+1/2} = (1/2)[F(Uแตข) + F(Uแตขโ‚Šโ‚)] - (ฮฑ/2)(Uแตขโ‚Šโ‚ - Uแตข)

ฮฑ = max(|ฮป|)  (์ตœ๋Œ€ ํŒŒ๋™ ์†๋„)

ํŠน์ง•:
- ๊ฐ„๋‹จํ•˜๊ณ  ๊ฒฌ๊ณ ํ•จ
- 1์ฐจ ์ •ํ™•๋„
- ์ˆ˜์น˜ ํ™•์‚ฐ ํผ

3.2 HLL/HLLD ํ”Œ๋Ÿญ์Šค

HLL (Harten-Lax-van Leer) ํ”Œ๋Ÿญ์Šค:

๊ฐ€์ •: ์ขŒ์ธก/์šฐ์ธก ํŒŒ๋™ ์†๋„ SL, SR ๋งŒ ๊ณ ๋ ค

        โŽง F_L                          if SL โ‰ฅ 0
F_HLL = โŽจ (SR*F_L - SL*F_R + SL*SR*(U_R - U_L))/(SR - SL)  if SL < 0 < SR
        โŽฉ F_R                          if SR โ‰ค 0

ํŒŒ๋™ ์†๋„ ์ถ”์ •:
SL = min(vx_L - cf_L, vx_R - cf_R)
SR = max(vx_L + cf_L, vx_R + cf_R)

HLLD (MHD์šฉ):
- ์ค‘๊ฐ„ ์ƒํƒœ๋ฅผ ๋” ์„ธ๋ถ„ํ™”
- ์ ‘์ด‰ ๋ถˆ์—ฐ์†๊ณผ Alfven ํŒŒ ๊ตฌ๋ถ„
- MHD์—์„œ ๋” ์ •ํ™•
def lax_friedrichs_flux(U_L, U_R, Bx, max_speed=None):
    """Lax-Friedrichs ํ”Œ๋Ÿญ์Šค"""
    F_L = compute_flux(U_L, Bx)
    F_R = compute_flux(U_R, Bx)

    if max_speed is None:
        # ์ตœ๋Œ€ ํŒŒ๋™ ์†๋„ ๊ณ„์‚ฐ
        rho_L, vx_L, vy_L, vz_L, _, By_L, Bz_L, p_L = conservative_to_primitive(U_L, Bx)
        rho_R, vx_R, vy_R, vz_R, _, By_R, Bz_R, p_R = conservative_to_primitive(U_R, Bx)

        cf_L, _, _ = mhd_wave_speeds(rho_L, vx_L, p_L, Bx, By_L, Bz_L)
        cf_R, _, _ = mhd_wave_speeds(rho_R, vx_R, p_R, Bx, By_R, Bz_R)

        max_speed = max(abs(vx_L) + cf_L, abs(vx_R) + cf_R)

    F = 0.5 * (F_L + F_R) - 0.5 * max_speed * (U_R - U_L)
    return F

def hll_flux(U_L, U_R, Bx):
    """HLL ํ”Œ๋Ÿญ์Šค"""
    F_L = compute_flux(U_L, Bx)
    F_R = compute_flux(U_R, Bx)

    rho_L, vx_L, vy_L, vz_L, _, By_L, Bz_L, p_L = conservative_to_primitive(U_L, Bx)
    rho_R, vx_R, vy_R, vz_R, _, By_R, Bz_R, p_R = conservative_to_primitive(U_R, Bx)

    cf_L, _, _ = mhd_wave_speeds(rho_L, vx_L, p_L, Bx, By_L, Bz_L)
    cf_R, _, _ = mhd_wave_speeds(rho_R, vx_R, p_R, Bx, By_R, Bz_R)

    SL = min(vx_L - cf_L, vx_R - cf_R)
    SR = max(vx_L + cf_L, vx_R + cf_R)

    if SL >= 0:
        return F_L
    elif SR <= 0:
        return F_R
    else:
        F_HLL = (SR * F_L - SL * F_R + SL * SR * (U_R - U_L)) / (SR - SL)
        return F_HLL

4. MHD ์ถฉ๊ฒฉํŒŒ ๊ด€ ๋ฌธ์ œ

4.1 Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€

Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€ (1988):
- MHD ์ฝ”๋“œ ํ‘œ์ค€ ํ…Œ์ŠคํŠธ ๋ฌธ์ œ
- Sod ์ถฉ๊ฒฉํŒŒ ๊ด€์˜ MHD ๋ฒ„์ „

์ดˆ๊ธฐ ์กฐ๊ฑด:
์ขŒ์ธก (x < 0.5):          ์šฐ์ธก (x โ‰ฅ 0.5):
ฯ = 1.0                   ฯ = 0.125
p = 1.0                   p = 0.1
vx = vy = vz = 0          vx = vy = vz = 0
Bx = 0.75                 Bx = 0.75
By = 1.0                  By = -1.0
Bz = 0                    Bz = 0

๊ฒฝ๊ณ„ ์กฐ๊ฑด: ์œ ์ถœ (outflow)
์ตœ์ข… ์‹œ๊ฐ„: t = 0.1

ํ•ด์˜ ๊ตฌ์กฐ:
- ๋น ๋ฅธ ํฌ๋ฐ•ํŒŒ (fast rarefaction)
- ๋ณตํ•ฉํŒŒ (compound wave)
- ์ ‘์ด‰ ๋ถˆ์—ฐ์† (contact)
- ๋А๋ฆฐ ์ถฉ๊ฒฉํŒŒ (slow shock)
- ๋น ๋ฅธ ํฌ๋ฐ•ํŒŒ (fast rarefaction)
class MHD_1D_Solver:
    """1D MHD ์œ ํ•œ ์ฒด์ ๋ฒ• ์†”๋ฒ„"""

    def __init__(self, Nx=400, x_range=(0, 1), Bx=0.75):
        self.Nx = Nx
        self.x_min, self.x_max = x_range
        self.dx = (self.x_max - self.x_min) / Nx
        self.x = np.linspace(self.x_min + 0.5*self.dx,
                            self.x_max - 0.5*self.dx, Nx)
        self.Bx = Bx

        # ๋ณด์กด ๋ณ€์ˆ˜ ๋ฐฐ์—ด (7 components)
        self.U = np.zeros((7, Nx))

    def set_brio_wu(self):
        """Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€ ์ดˆ๊ธฐ์กฐ๊ฑด"""
        for i, x in enumerate(self.x):
            if x < 0.5:
                rho, vx, vy, vz = 1.0, 0.0, 0.0, 0.0
                By, Bz, p = 1.0, 0.0, 1.0
            else:
                rho, vx, vy, vz = 0.125, 0.0, 0.0, 0.0
                By, Bz, p = -1.0, 0.0, 0.1

            self.U[:, i] = primitive_to_conservative(rho, vx, vy, vz,
                                                     self.Bx, By, Bz, p)

    def compute_dt(self, cfl=0.5):
        """์‹œ๊ฐ„ ๋‹จ๊ณ„ ๊ณ„์‚ฐ (CFL ์กฐ๊ฑด)"""
        max_speed = 0

        for i in range(self.Nx):
            rho, vx, vy, vz, _, By, Bz, p = conservative_to_primitive(
                self.U[:, i], self.Bx)
            cf, _, _ = mhd_wave_speeds(rho, vx, p, self.Bx, By, Bz)
            max_speed = max(max_speed, abs(vx) + cf)

        return cfl * self.dx / max_speed

    def step(self, dt, flux_func='lxf'):
        """ํ•œ ์‹œ๊ฐ„ ๋‹จ๊ณ„ ์ „์ง„"""
        # ํ”Œ๋Ÿญ์Šค ๊ณ„์‚ฐ
        F = np.zeros((7, self.Nx + 1))

        for i in range(self.Nx + 1):
            # ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ (outflow)
            if i == 0:
                U_L = self.U[:, 0]
                U_R = self.U[:, 0]
            elif i == self.Nx:
                U_L = self.U[:, -1]
                U_R = self.U[:, -1]
            else:
                U_L = self.U[:, i-1]
                U_R = self.U[:, i]

            if flux_func == 'lxf':
                F[:, i] = lax_friedrichs_flux(U_L, U_R, self.Bx)
            else:
                F[:, i] = hll_flux(U_L, U_R, self.Bx)

        # ์—…๋ฐ์ดํŠธ
        self.U = self.U - dt / self.dx * (F[:, 1:] - F[:, :-1])

    def run(self, t_final, cfl=0.5, flux_func='lxf'):
        """์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰"""
        t = 0
        step_count = 0

        while t < t_final:
            dt = self.compute_dt(cfl)
            if t + dt > t_final:
                dt = t_final - t

            self.step(dt, flux_func)
            t += dt
            step_count += 1

        print(f"์™„๋ฃŒ: {step_count} steps, final t = {t:.4f}")

    def get_primitives(self):
        """์›์‹œ ๋ณ€์ˆ˜ ๋ฐ˜ํ™˜"""
        rho = np.zeros(self.Nx)
        vx = np.zeros(self.Nx)
        vy = np.zeros(self.Nx)
        vz = np.zeros(self.Nx)
        By = np.zeros(self.Nx)
        Bz = np.zeros(self.Nx)
        p = np.zeros(self.Nx)

        for i in range(self.Nx):
            rho[i], vx[i], vy[i], vz[i], _, By[i], Bz[i], p[i] = \
                conservative_to_primitive(self.U[:, i], self.Bx)

        return rho, vx, vy, vz, By, Bz, p


def run_brio_wu_test():
    """Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜"""

    # ์†”๋ฒ„ ์ƒ์„ฑ
    solver = MHD_1D_Solver(Nx=400, x_range=(0, 1), Bx=0.75)
    solver.set_brio_wu()

    # ์ดˆ๊ธฐ ์ƒํƒœ ์ €์žฅ
    rho_init, vx_init, vy_init, _, By_init, _, p_init = solver.get_primitives()
    x = solver.x

    # ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰
    t_final = 0.1
    solver.run(t_final, cfl=0.5, flux_func='hll')

    # ์ตœ์ข… ์ƒํƒœ
    rho, vx, vy, _, By, _, p = solver.get_primitives()

    # ์‹œ๊ฐํ™”
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))

    # ๋ฐ€๋„
    axes[0, 0].plot(x, rho_init, 'b--', alpha=0.5, label='Initial')
    axes[0, 0].plot(x, rho, 'b-', linewidth=1.5, label='Final')
    axes[0, 0].set_ylabel(r'$\rho$')
    axes[0, 0].set_title('๋ฐ€๋„')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)

    # ์••๋ ฅ
    axes[0, 1].plot(x, p_init, 'r--', alpha=0.5, label='Initial')
    axes[0, 1].plot(x, p, 'r-', linewidth=1.5, label='Final')
    axes[0, 1].set_ylabel('p')
    axes[0, 1].set_title('์••๋ ฅ')
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3)

    # x-์†๋„
    axes[0, 2].plot(x, vx_init, 'g--', alpha=0.5, label='Initial')
    axes[0, 2].plot(x, vx, 'g-', linewidth=1.5, label='Final')
    axes[0, 2].set_ylabel(r'$v_x$')
    axes[0, 2].set_title('x-์†๋„')
    axes[0, 2].legend()
    axes[0, 2].grid(True, alpha=0.3)

    # y-์†๋„
    axes[1, 0].plot(x, vy_init, 'm--', alpha=0.5, label='Initial')
    axes[1, 0].plot(x, vy, 'm-', linewidth=1.5, label='Final')
    axes[1, 0].set_ylabel(r'$v_y$')
    axes[1, 0].set_xlabel('x')
    axes[1, 0].set_title('y-์†๋„')
    axes[1, 0].legend()
    axes[1, 0].grid(True, alpha=0.3)

    # By
    axes[1, 1].plot(x, By_init, 'c--', alpha=0.5, label='Initial')
    axes[1, 1].plot(x, By, 'c-', linewidth=1.5, label='Final')
    axes[1, 1].set_ylabel(r'$B_y$')
    axes[1, 1].set_xlabel('x')
    axes[1, 1].set_title('y-์ž๊ธฐ์žฅ')
    axes[1, 1].legend()
    axes[1, 1].grid(True, alpha=0.3)

    # ์ด ์••๋ ฅ
    B2 = solver.Bx**2 + By**2
    p_total = p + 0.5 * B2
    B2_init = solver.Bx**2 + By_init**2
    p_total_init = p_init + 0.5 * B2_init

    axes[1, 2].plot(x, p_total_init, 'k--', alpha=0.5, label='Initial')
    axes[1, 2].plot(x, p_total, 'k-', linewidth=1.5, label='Final')
    axes[1, 2].set_ylabel(r'$p + B^2/2$')
    axes[1, 2].set_xlabel('x')
    axes[1, 2].set_title('์ด ์••๋ ฅ')
    axes[1, 2].legend()
    axes[1, 2].grid(True, alpha=0.3)

    plt.suptitle(f'Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€ (t = {t_final})', fontsize=14)
    plt.tight_layout()
    plt.savefig('brio_wu_test.png', dpi=150, bbox_inches='tight')
    plt.show()

    return solver

# solver = run_brio_wu_test()

5. div B = 0 ์ œ์•ฝ์กฐ๊ฑด

5.1 ๋ฌธ์ œ์ ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

โˆ‡ยทB = 0 ์ œ์•ฝ์กฐ๊ฑด:

๋ฌผ๋ฆฌ์  ์˜๋ฏธ:
- ์ž๊ธฐ ๋‹จ๊ทน์ž ์—†์Œ
- ํ•ญ์ƒ ๋งŒ์กฑํ•ด์•ผ ํ•˜๋Š” ์ œ์•ฝ

์ˆ˜์น˜์  ๋ฌธ์ œ:
- ์ด์‚ฐํ™” ์˜ค์ฐจ๋กœ โˆ‡ยทB โ‰  0 ๋ฐœ์ƒ ๊ฐ€๋Šฅ
- "์ž๊ธฐ ๋‹จ๊ทน์ž" ์ถ•์ 
- ๋น„๋ฌผ๋ฆฌ์  ํž˜ ๋ฐœ์ƒ, ๋ถˆ์•ˆ์ •์„ฑ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:

1. Constrained Transport (CT):
   - ์ž๊ธฐ์žฅ์„ ์…€ ๋ฉด์— ์ €์žฅ
   - ์ „๊ธฐ์žฅ์„ ์…€ ๋ชจ์„œ๋ฆฌ์— ์ €์žฅ
   - ๊ตฌ์กฐ์ ์œผ๋กœ โˆ‡ยทB = 0 ๋ณด์žฅ
   - Yee ๊ฒฉ์ž์™€ ์œ ์‚ฌ

2. Projection Method:
   - Hodge ๋ถ„ํ•ด: B = B_sol + โˆ‡ฯ†
   - Poisson ๋ฐฉ์ •์‹: โˆ‡ยฒฯ† = โˆ‡ยทB
   - ์ˆ˜์ •: B_new = B - โˆ‡ฯ†

3. Divergence Cleaning:
   a) Parabolic: โˆ‚B/โˆ‚t = -chยฒโˆ‡(โˆ‡ยทB)
   b) Hyperbolic: โˆ‚ฯˆ/โˆ‚t + chยฒโˆ‡ยทB = 0,
                  โˆ‚B/โˆ‚t + chโˆ‡ฯˆ = 0
   (GLM: Generalized Lagrange Multiplier)

4. Powell 8-wave:
   - ์†Œ์Šค ํ•ญ ์ถ”๊ฐ€: S = -(โˆ‡ยทB) [0, B, v, vยทB]แต€
   - ๋น„๋ณด์กด์ , ์ •์ƒ ์ƒํƒœ์—์„œ ์œ ํšจ
def divergence_cleaning_demo():
    """๋ฐœ์‚ฐ ํด๋ฆฌ๋‹ ๊ฐœ๋… ์‹œ์—ฐ"""

    print("=" * 60)
    print("div B = 0 ์ œ์•ฝ์กฐ๊ฑด ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•")
    print("=" * 60)

    methods = """
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚              div B = 0 ์ œ์•ฝ์กฐ๊ฑด ์ฒ˜๋ฆฌ                         โ”‚
    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    โ”‚                                                             โ”‚
    โ”‚ 1. Constrained Transport (CT)                               โ”‚
    โ”‚    - ๊ตฌ์กฐ์ ์œผ๋กœ โˆ‡ยทB = 0 ๋ณด์žฅ                                โ”‚
    โ”‚    - ์ž๊ธฐ์žฅ: ์…€ ๋ฉด ์ค‘์‹ฌ                                     โ”‚
    โ”‚    - ์ „๊ธฐ์žฅ: ์…€ ๋ชจ์„œ๋ฆฌ                                      โ”‚
    โ”‚    - Stokes ์ •๋ฆฌ๋กœ ์ž๋™ ๋งŒ์กฑ                                โ”‚
    โ”‚                                                             โ”‚
    โ”‚ 2. Projection Method                                        โ”‚
    โ”‚    - Helmholtz ๋ถ„ํ•ด                                         โ”‚
    โ”‚    - Poisson ๋ฐฉ์ •์‹ ํ’€์ด ํ•„์š”                               โ”‚
    โ”‚    - ๊ณ„์‚ฐ ๋น„์šฉ ๋†’์Œ                                         โ”‚
    โ”‚                                                             โ”‚
    โ”‚ 3. GLM (Hyperbolic Cleaning)                                โ”‚
    โ”‚    - ์ถ”๊ฐ€ ์Šค์นผ๋ผ ๋ณ€์ˆ˜ ฯˆ ๋„์ž…                                โ”‚
    โ”‚    - โˆ‚ฯˆ/โˆ‚t + chยฒโˆ‡ยทB = -(chยฒ/cpยฒ)ฯˆ                          โ”‚
    โ”‚    - โˆ‚B/โˆ‚t + ... + chโˆ‡ฯˆ = 0                                โ”‚
    โ”‚    - ์˜ค๋ฅ˜๊ฐ€ ํŒŒ๋™์œผ๋กœ ์ „ํŒŒ๋˜์–ด ๋น ์ ธ๋‚˜๊ฐ                      โ”‚
    โ”‚                                                             โ”‚
    โ”‚ 4. Powell Source Terms                                      โ”‚
    โ”‚    - ๋น„๋ณด์กด์  ์†Œ์Šค ํ•ญ ์ถ”๊ฐ€                                  โ”‚
    โ”‚    - โˆ‚U/โˆ‚t + โˆ‚F/โˆ‚x = -(โˆ‡ยทB)S                               โ”‚
    โ”‚    - ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์—๋„ˆ์ง€ ๋ณด์กด ์œ„๋ฐ˜                            โ”‚
    โ”‚                                                             โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    """
    print(methods)

    # ์‹œ๊ฐํ™”: CT ๊ฒฉ์ž ๊ตฌ์กฐ
    fig, axes = plt.subplots(1, 2, figsize=(14, 6))

    # (1) Constrained Transport ๊ฒฉ์ž
    ax1 = axes[0]

    # ์…€ ๊ฒฉ์ž
    for i in range(5):
        ax1.axhline(y=i, color='gray', linestyle='-', linewidth=0.5)
        ax1.axvline(x=i, color='gray', linestyle='-', linewidth=0.5)

    # Bx (์ˆ˜์ง ๋ฉด)
    for i in range(5):
        for j in range(4):
            ax1.plot(i, j+0.5, 'b>', markersize=12)

    # By (์ˆ˜ํ‰ ๋ฉด)
    for i in range(4):
        for j in range(5):
            ax1.plot(i+0.5, j, 'r^', markersize=12)

    # Ez (๋ชจ์„œ๋ฆฌ)
    for i in range(5):
        for j in range(5):
            ax1.plot(i, j, 'go', markersize=8)

    ax1.set_xlabel('x')
    ax1.set_ylabel('y')
    ax1.set_title('Constrained Transport ๊ฒฉ์ž')
    ax1.set_aspect('equal')
    ax1.set_xlim(-0.5, 4.5)
    ax1.set_ylim(-0.5, 4.5)

    ax1.plot([], [], 'b>', markersize=10, label='Bx (x-๋ฉด)')
    ax1.plot([], [], 'r^', markersize=10, label='By (y-๋ฉด)')
    ax1.plot([], [], 'go', markersize=8, label='Ez (๋ชจ์„œ๋ฆฌ)')
    ax1.legend(loc='upper right')

    # (2) GLM ๋ฐฉ๋ฒ• ๊ฐœ๋…
    ax2 = axes[1]

    x = np.linspace(0, 10, 100)
    t = 0

    # ์ดˆ๊ธฐ div B ์˜ค๋ฅ˜ (๊ฐ€์šฐ์‹œ์•ˆ)
    div_B = np.exp(-(x - 3)**2)

    # ์‹œ๊ฐ„ ์ „๊ฐœ (์–‘์ชฝ์œผ๋กœ ์ „ํŒŒ)
    times = [0, 1, 2, 3]
    colors = plt.cm.viridis(np.linspace(0, 1, len(times)))

    for ti, color in zip(times, colors):
        # GLM: ์˜ค๋ฅ˜๊ฐ€ ยฑch ์†๋„๋กœ ์ „ํŒŒ
        ch = 1.5
        div_B_t = 0.5 * (np.exp(-(x - 3 - ch*ti)**2) +
                        np.exp(-(x - 3 + ch*ti)**2)) * np.exp(-0.5*ti)
        ax2.plot(x, div_B_t, color=color, linewidth=1.5, label=f't = {ti}')

    ax2.set_xlabel('x')
    ax2.set_ylabel(r'$\nabla \cdot B$ error')
    ax2.set_title('GLM: ์˜ค๋ฅ˜๊ฐ€ ๋„๋ฉ”์ธ ๋ฐ–์œผ๋กœ ์ „ํŒŒ')
    ax2.legend()
    ax2.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig('div_b_cleaning.png', dpi=150, bbox_inches='tight')
    plt.show()

# divergence_cleaning_demo()

6. ๊ณ ํ•ด์ƒ๋„ ์Šคํ‚ด

6.1 MUSCL-Hancock

MUSCL-Hancock (2์ฐจ ์ •ํ™•๋„):

1. ์„ ํ˜• ์žฌ๊ตฌ์„ฑ:
   U_L = Uแตข + 0.5 * ฯ†(r) * (Uแตข - Uแตขโ‚‹โ‚)
   U_R = Uแตขโ‚Šโ‚ - 0.5 * ฯ†(r) * (Uแตขโ‚Šโ‚‚ - Uแตขโ‚Šโ‚)

   ฯ†(r): ๊ธฐ์šธ๊ธฐ ์ œํ•œ์ž (limiter)
   - minmod: ฯ†(r) = max(0, min(1, r))
   - MC: ฯ†(r) = max(0, min((1+r)/2, 2, 2r))
   - van Leer: ฯ†(r) = (r + |r|)/(1 + |r|)

2. Predictor (๋ฐ˜๋‹จ๊ณ„ ์ „์ง„):
   U_L^{n+1/2} = U_L - (ฮ”t/2ฮ”x)(F(U_L) - F(U_Lโป))

3. Riemann ํ’€์ด ๋ฐ ํ”Œ๋Ÿญ์Šค ๊ณ„์‚ฐ

์žฅ์ :
- 2์ฐจ ์ •ํ™•๋„ (๋งค๋„๋Ÿฌ์šด ์˜์—ญ)
- TVD (Total Variation Diminishing)
- ์ง„๋™ ์–ต์ œ
def minmod(a, b):
    """Minmod ๋ฆฌ๋ฏธํ„ฐ"""
    if a * b <= 0:
        return 0
    elif abs(a) < abs(b):
        return a
    else:
        return b

def mc_limiter(a, b):
    """MC (Monotonized Central) ๋ฆฌ๋ฏธํ„ฐ"""
    if a * b <= 0:
        return 0
    c = 0.5 * (a + b)
    return np.sign(c) * min(abs(c), 2*abs(a), 2*abs(b))

def muscl_reconstruct(U, i, limiter='minmod'):
    """MUSCL ์žฌ๊ตฌ์„ฑ"""
    if limiter == 'minmod':
        lim_func = minmod
    else:
        lim_func = mc_limiter

    Nx = U.shape[1]

    # ๊ธฐ์šธ๊ธฐ ๊ณ„์‚ฐ
    if i > 0 and i < Nx - 1:
        slope_L = U[:, i] - U[:, i-1]
        slope_R = U[:, i+1] - U[:, i]

        slope = np.array([lim_func(slope_L[k], slope_R[k])
                         for k in range(len(slope_L))])
    else:
        slope = np.zeros(U.shape[0])

    U_L = U[:, i] - 0.5 * slope  # ์™ผ์ชฝ ์ƒํƒœ
    U_R = U[:, i] + 0.5 * slope  # ์˜ค๋ฅธ์ชฝ ์ƒํƒœ

    return U_L, U_R


def run_brio_wu_high_resolution():
    """๊ณ ํ•ด์ƒ๋„ Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€"""

    # ์†”๋ฒ„ (๋” ๋งŽ์€ ์…€)
    solver_lr = MHD_1D_Solver(Nx=200, x_range=(0, 1), Bx=0.75)
    solver_hr = MHD_1D_Solver(Nx=800, x_range=(0, 1), Bx=0.75)

    solver_lr.set_brio_wu()
    solver_hr.set_brio_wu()

    t_final = 0.1

    solver_lr.run(t_final, cfl=0.5, flux_func='hll')
    solver_hr.run(t_final, cfl=0.5, flux_func='hll')

    # ๋น„๊ต
    fig, axes = plt.subplots(1, 3, figsize=(15, 4))

    x_lr = solver_lr.x
    x_hr = solver_hr.x

    rho_lr, vx_lr, _, _, By_lr, _, p_lr = solver_lr.get_primitives()
    rho_hr, vx_hr, _, _, By_hr, _, p_hr = solver_hr.get_primitives()

    axes[0].plot(x_lr, rho_lr, 'b-', linewidth=1.5, label='Nx=200')
    axes[0].plot(x_hr, rho_hr, 'r-', linewidth=1, alpha=0.7, label='Nx=800')
    axes[0].set_ylabel(r'$\rho$')
    axes[0].set_xlabel('x')
    axes[0].set_title('๋ฐ€๋„')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)

    axes[1].plot(x_lr, vx_lr, 'b-', linewidth=1.5, label='Nx=200')
    axes[1].plot(x_hr, vx_hr, 'r-', linewidth=1, alpha=0.7, label='Nx=800')
    axes[1].set_ylabel(r'$v_x$')
    axes[1].set_xlabel('x')
    axes[1].set_title('x-์†๋„')
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)

    axes[2].plot(x_lr, By_lr, 'b-', linewidth=1.5, label='Nx=200')
    axes[2].plot(x_hr, By_hr, 'r-', linewidth=1, alpha=0.7, label='Nx=800')
    axes[2].set_ylabel(r'$B_y$')
    axes[2].set_xlabel('x')
    axes[2].set_title('y-์ž๊ธฐ์žฅ')
    axes[2].legend()
    axes[2].grid(True, alpha=0.3)

    plt.suptitle('๊ฒฉ์ž ์ˆ˜๋ ด ํ…Œ์ŠคํŠธ (1์ฐจ HLL)', fontsize=14)
    plt.tight_layout()
    plt.savefig('brio_wu_convergence.png', dpi=150, bbox_inches='tight')
    plt.show()

# run_brio_wu_high_resolution()

7. MHD ์ฝ”๋“œ ๊ฒ€์ฆ

7.1 ํ‘œ์ค€ ํ…Œ์ŠคํŠธ ๋ฌธ์ œ

MHD ์ฝ”๋“œ ๊ฒ€์ฆ์šฉ ํ‘œ์ค€ ๋ฌธ์ œ:

1. Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€ (1D)
   - ์ถฉ๊ฒฉํŒŒ, ์ ‘์ด‰ ๋ถˆ์—ฐ์†, ํฌ๋ฐ•ํŒŒ
   - ๋ณตํ•ฉํŒŒ ๊ตฌ์กฐ

2. Orszag-Tang ์™€๋ฅ˜ (2D)
   - ๊ณ ๋„์˜ ๋น„์„ ํ˜• ์ƒํ˜ธ์ž‘์šฉ
   - ์ž‘์€ ์Šค์ผ€์ผ ๊ตฌ์กฐ ๋ฐœ์ƒ
   - ์ถฉ๊ฒฉํŒŒ ์ƒํ˜ธ์ž‘์šฉ

3. MHD ํšŒ์ „ ๋ถˆ์—ฐ์† (1D)
   - Alfven ํŒŒ ์ •ํ™•๋„ ๊ฒ€์ฆ
   - ์ˆœ์ˆ˜ ํšŒ์ „ (ฯ, p ์ผ์ •)

4. ํญ๋ฐœ ๋ฌธ์ œ (2D/3D)
   - MHD Sedov-Taylor
   - ์ž๊ธฐ์žฅ ์˜ํ–ฅ

5. ์ž๊ธฐ ๋ฃจํ”„ ์ด๋ฅ˜ (2D)
   - div B = 0 ๋ณด์กด ๊ฒ€์ฆ
   - ์›ํ˜• ์ž๊ธฐ ๊ตฌ์กฐ ์ด๋™

์ •ํ™•๋„ ๊ฒ€์ฆ:
- ํ•ด์„ํ•ด๊ฐ€ ์žˆ๋Š” ๋ฌธ์ œ ์‚ฌ์šฉ
- ๊ฒฉ์ž ์ˆ˜๋ ด ํ…Œ์ŠคํŠธ
- ๋ณด์กด๋Ÿ‰ ํ™•์ธ (์งˆ๋Ÿ‰, ์šด๋™๋Ÿ‰, ์—๋„ˆ์ง€)
def test_problems_overview():
    """MHD ํ‘œ์ค€ ํ…Œ์ŠคํŠธ ๋ฌธ์ œ ๊ฐœ์š”"""

    print("=" * 60)
    print("MHD ํ‘œ์ค€ ํ…Œ์ŠคํŠธ ๋ฌธ์ œ")
    print("=" * 60)

    tests = """
    1D ํ…Œ์ŠคํŠธ:
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚ ๋ฌธ์ œ          โ”‚ ๊ฒ€์ฆ ๋Œ€์ƒ              โ”‚ ๋‚œ์ด๋„        โ”‚
    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    โ”‚ Brio-Wu       โ”‚ ์ถฉ๊ฒฉํŒŒ ํฌ์ฐฉ, ๊ธฐ๋ณธ ํŒŒ๋™ โ”‚ ๊ธฐ๋ณธ          โ”‚
    โ”‚ Dai-Woodward  โ”‚ 7ํŒŒ ๊ตฌ์กฐ ์ „์ฒด          โ”‚ ์ค‘๊ธ‰          โ”‚
    โ”‚ Einfeldt 1203 โ”‚ ๋‚ฎ์€ ฮฒ, ๊ฐ•ํ•œ ์ž๊ธฐ์žฅ    โ”‚ ๋„์ „์         โ”‚
    โ”‚ Ryu-Jones     โ”‚ ๋‹ค์–‘ํ•œ MHD ํŒŒ๋™        โ”‚ ์ค‘๊ธ‰          โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

    2D ํ…Œ์ŠคํŠธ:
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚ ๋ฌธ์ œ          โ”‚ ๊ฒ€์ฆ ๋Œ€์ƒ              โ”‚ ๋‚œ์ด๋„        โ”‚
    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    โ”‚ Orszag-Tang   โ”‚ ๋น„์„ ํ˜• ๋ฐœ์ „, ๋‚œ๋ฅ˜      โ”‚ ํ‘œ์ค€          โ”‚
    โ”‚ ์ž๊ธฐ ํšŒ์ „ ๋ถˆ์•ˆโ”‚ ์„ ํ˜• ์„ฑ์žฅ๋ฅ  ๋น„๊ต       โ”‚ ๋ฌผ๋ฆฌ ๊ฒ€์ฆ     โ”‚
    โ”‚ ๋ฃจํ”„ ์ด๋ฅ˜     โ”‚ div B, ์ˆ˜์น˜ ํ™•์‚ฐ       โ”‚ ๊ธฐ๋ณธ          โ”‚
    โ”‚ ํญ๋ฐœ ๋ฌธ์ œ     โ”‚ ๊ตฌ๋ฉด ๋Œ€์นญ ๋ณด์กด         โ”‚ ๋„์ „์         โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

    ์ˆ˜๋ ด ํ…Œ์ŠคํŠธ:
    - L1, L2, Lโˆž ๋…ธ๋ฆ„ ๊ณ„์‚ฐ
    - ๊ฒฉ์ž ํ•ด์ƒ๋„ 2๋ฐฐ์”ฉ ์ฆ๊ฐ€
    - ์˜ˆ์ƒ ์ˆ˜๋ ด ์ฐจ์ˆ˜ ํ™•์ธ (1์ฐจ: O(h), 2์ฐจ: O(hยฒ))
    """
    print(tests)

test_problems_overview()

8. ์—ฐ์Šต ๋ฌธ์ œ

์—ฐ์Šต 1: ํŒŒ๋™ ์†๋„

ฯ = 1, p = 0.5, Bx = 1, By = 0.5, Bz = 0 ์ผ ๋•Œ ๋น ๋ฅธ/๋А๋ฆฐ/Alfven ํŒŒ๋™ ์†๋„๋ฅผ ๊ณ„์‚ฐํ•˜์‹œ์˜ค. (ฮณ = 5/3)

์—ฐ์Šต 2: Lax-Friedrichs

1D ์„ ํ˜• ์ด๋ฅ˜๋ฐฉ์ •์‹์— Lax-Friedrichs ์Šคํ‚ด์„ ์ ์šฉํ•˜๊ณ , ์ˆ˜์น˜ ํ™•์‚ฐ ๊ณ„์ˆ˜๋ฅผ ์œ ๋„ํ•˜์‹œ์˜ค.

์—ฐ์Šต 3: HLL vs LxF

Brio-Wu ๋ฌธ์ œ๋ฅผ HLL๊ณผ Lax-Friedrichs ํ”Œ๋Ÿญ์Šค๋กœ ๊ฐ๊ฐ ํ’€๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•˜์‹œ์˜ค.

์—ฐ์Šต 4: div B ์˜ค๋ฅ˜

2D MHD ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—์„œ div B ์˜ค๋ฅ˜๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์‹œ์˜ค.


9. ์ฐธ๊ณ ์ž๋ฃŒ

ํ•ต์‹ฌ ๋…ผ๋ฌธ

  • Brio & Wu (1988) "An Upwind Differencing Scheme for the Equations of Ideal Magnetohydrodynamics"
  • Dedner et al. (2002) "Hyperbolic Divergence Cleaning for the MHD Equations"
  • Toth (2000) "The โˆ‡ยทB = 0 Constraint in Shock-Capturing Magnetohydrodynamics Codes"

๊ต์žฌ

  • Toro, "Riemann Solvers and Numerical Methods for Fluid Dynamics"
  • LeVeque, "Finite Volume Methods for Hyperbolic Problems"

MHD ์ฝ”๋“œ

  • Athena++ (Stone et al.)
  • PLUTO (Mignone et al.)
  • FLASH (Fryxell et al.)
  • Pencil Code (Brandenburg et al.)

์š”์•ฝ

MHD ์ˆ˜์น˜ํ•ด๋ฒ• ํ•ต์‹ฌ:

1. ๋ณด์กด ํ˜•ํƒœ:
   โˆ‚U/โˆ‚t + โˆ‚F/โˆ‚x = 0
   U = [ฯ, ฯv, B, E]แต€ (7 ๋ณ€์ˆ˜, 1D)

2. ์œ ํ•œ ์ฒด์ ๋ฒ•:
   dUแตข/dt = -(F_{i+1/2} - F_{i-1/2})/ฮ”x
   ์ˆ˜์น˜ ํ”Œ๋Ÿญ์Šค๋กœ Riemann ๋ฌธ์ œ ๊ทผ์‚ฌ

3. ์ˆ˜์น˜ ํ”Œ๋Ÿญ์Šค:
   - Lax-Friedrichs: ๊ฐ„๋‹จ, 1์ฐจ, ํ™•์‚ฐ ํผ
   - HLL: 2ํŒŒ ๊ทผ์‚ฌ, ์ค‘๊ฐ„ ์ •ํ™•๋„
   - HLLD: MHD ์ตœ์ ํ™”, ๊ณ ์ •ํ™•๋„

4. CFL ์กฐ๊ฑด:
   ฮ”t โ‰ค CFL ร— ฮ”x / (|v| + cf)
   cf: ๋น ๋ฅธ ์ž๊ธฐ์ŒํŒŒ ์†๋„

5. div B = 0:
   - CT: ๊ตฌ์กฐ์  ๋ณด์กด
   - GLM: ์Œ๊ณก์„ ํ˜• ํด๋ฆฌ๋‹
   - Projection: Poisson ํ’€์ด

6. ๊ณ ํ•ด์ƒ๋„ ์Šคํ‚ด:
   - MUSCL + limiter
   - PPM, WENO
   - 2์ฐจ ์ด์ƒ ์ •ํ™•๋„

7. ๊ฒ€์ฆ:
   - Brio-Wu ์ถฉ๊ฒฉํŒŒ ๊ด€
   - ๊ฒฉ์ž ์ˆ˜๋ ด ํ…Œ์ŠคํŠธ
   - ๋ณด์กด๋Ÿ‰ ํ™•์ธ

๋‹ค์Œ ๋ ˆ์Šจ์—์„œ๋Š” ํ”Œ๋ผ์ฆˆ๋งˆ ์‹œ๋ฎฌ๋ ˆ์ด์…˜๊ณผ PIC (Particle-In-Cell) ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

to navigate between lessons