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) ๋ฐฉ๋ฒ์ ๋ค๋ฃน๋๋ค.