18. ํ๋ก์ ํธ
18. ํ๋ก์ ํธ¶
ํ์ต ๋ชฉํ¶
์ด ๋ ์จ์ ๋ง์น๋ฉด ๋ค์์ ํ ์ ์์ต๋๋ค:
- ์๊ธฐ ์ฌ๊ฒฐํฉ์ ์์ ํ 2D ์ ํญ์ฑ MHD ์๋ฎฌ๋ ์ด์ ๊ตฌํํ๊ธฐ
- ํ์ ํ๋ ์ด ๋ชจ๋ธ์์ ์ฌ๊ฒฐํฉ๋ฅ ๊ณผ ์๋์ง ๋ณํ ๋ถ์ํ๊ธฐ
- ํ ์นด๋ง ํ๋ผ์ฆ๋ง๋ฅผ ์ํ 1D MHD ์์ ์ฑ ๋ถ์๊ธฐ ๊ตฌ์ถํ๊ธฐ
- ์์ ์ฑ ๊ธฐ์ค(Kruskal-Shafranov, Suydam) ์ ์ฉํ์ฌ ๋ถ๊ดด ์์ธกํ๊ธฐ
- ๊ตฌํ ์์์ ํ๊ท ์ฅ ๋ค์ด๋๋ชจ ์๋ฎฌ๋ ์ด์ ํ๊ธฐ
- ์ง๋ ๋ค์ด๋๋ชจ ํด์ ๋๋น ๋ค์ด์ด๊ทธ๋จ ๊ด์ฐฐํ๊ธฐ
- ์ด ๊ณผ์ ์์ ํ์ตํ ๋ชจ๋ MHD ๊ฐ๋ ์ ์ค์ฉ ์์ฉ์ ํตํฉํ๊ธฐ
ํ๋ก์ ํธ 1: ์๊ธฐ ์ฌ๊ฒฐํฉ์ ํตํ ํ์ ํ๋ ์ด ์๋ฎฌ๋ ์ด์ ¶
1.1 ๋ฌผ๋ฆฌ ๋ฐฐ๊ฒฝ¶
ํ์ ํ๋ ์ด๋ ํ์ ์ฝ๋ก๋์์ ์๊ธฐ ์๋์ง๊ฐ ํญ๋ฐ์ ์ผ๋ก ๋ฐฉ์ถ๋๋ ๊ฒ์ผ๋ก, ์๊ธฐ ์ฌ๊ฒฐํฉ์ ์ํด ๊ตฌ๋๋ฉ๋๋ค - ์๊ธฐ์ฅ ์ ์ ์์ํ์ ์ฌ๊ตฌ์ฑ์ผ๋ก ์๊ธฐ ์๋์ง๋ฅผ ์ด๋ ์๋์ง์ ์ด ์๋์ง๋ก ๋ณํํฉ๋๋ค.
ํต์ฌ ๋ฌผ๋ฆฌ: - ํฐ์ด๋ง ๋ถ์์ ์ฑ(Tearing Instability): ์ ๋ฅ ์ํธ์์ ์ ํญ์ฑ ๋ถ์์ ์ฑ โ ํ๋ผ์ฆ๋ชจ์ด๋(plasmoid) ํ์ฑ - Sweet-Parker ์ฌ๊ฒฐํฉ: ๊ณ ์ ๋ชจ๋ธ, ๋๋ฆฐ ์ฌ๊ฒฐํฉ๋ฅ $\sim \eta^{1/2}$ - ํ๋ผ์ฆ๋ชจ์ด๋ ๋งค๊ฐ ์ฌ๊ฒฐํฉ: 2์ฐจ ์์ผ๋๋๋ฅผ ํตํ ๋น ๋ฅธ ์ฌ๊ฒฐํฉ, $\eta$์ ๋ฌด๊ดํ ์๋
๊ด์ธก ๊ฐ๋ฅ๋: - ์ฌ๊ฒฐํฉ๋ฅ : ์ ์ ์๋ $v_{\text{in}}$ - ์๋์ง ๋ณํ: $\Delta E_{\text{mag}}$ โ $\Delta E_{\text{kin}} + \Delta E_{\text{th}}$ - ์ ๋ฅ ์ํธ ๊ตฌ์กฐ: ๋๊ป $\delta$, ๊ธธ์ด $L$ - ์ฌ๊ฒฐํฉ ์์ญ์์ ์จ๋ ์์น
1.2 ๋ฌธ์ ์ค์ ¶
๊ธฐํํ: $[-L_x, L_x] \times [-L_y, L_y]$ ์์์์ 2D Harris ์ ๋ฅ ์ํธ
์ด๊ธฐ ์กฐ๊ฑด: $$ B_x(x, y) = B_0 \tanh(y / a) $$ $$ B_y(x, y) = 0 $$ $$ \rho(x, y) = \rho_0 \left(1 + \beta \operatorname{sech}^2(y/a)\right) $$ $$ p(x, y) = p_0 + \frac{B_0^2}{2} \left(1 - \tanh^2(y/a)\right) $$
์ฌ๊ธฐ์: - $B_0 = 1.0$ (ํน์ฑ ์ฅ ๊ฐ๋) - $a = 0.5$ (์ ๋ฅ ์ํธ ๋ฐ ๋๊ป) - $\rho_0 = 1.0$ (๋ฐฐ๊ฒฝ ๋ฐ๋) - $\beta = 1.0$ (ํ๋ผ์ฆ๋ง ๋ฒ ํ ๋งค๊ฐ๋ณ์) - $p_0 = B_0^2 \beta / 2$
์ญ๋ (์ฌ๊ฒฐํฉ ํธ๋ฆฌ๊ฑฐ): $$ B_y(x, y) = \epsilon B_0 \cos(k_x x) \sin(\pi y / L_y) $$ $\epsilon = 0.1$, $k_x = 2\pi / L_x$์ ํจ๊ป.
๋งค๊ฐ๋ณ์: - ์์ญ: $L_x = 25.6$, $L_y = 12.8$ - ๊ทธ๋ฆฌ๋: $N_x = 512$, $N_y = 256$ - ์ ํญ: $\eta = 0.001$ (๊ตญ์ ๋๋ ๊ท ์ผ) - $\gamma = 5/3$ (์ด์ ๊ธฐ์ฒด)
๊ฒฝ๊ณ ์กฐ๊ฑด: - $x$์์ ์ฃผ๊ธฐ - $y$์์ ์ ๋ ๋ฒฝ ($\mathbf{v} \cdot \hat{y} = 0$, $\partial_y B_x = 0$, $B_y = 0$)
1.3 ๊ตฌํ¶
import numpy as np
import matplotlib.pyplot as plt
# Parameters
Lx, Ly = 25.6, 12.8
Nx, Ny = 512, 256
dx = Lx / Nx
dy = Ly / Ny
x = np.linspace(-Lx/2, Lx/2, Nx)
y = np.linspace(-Ly/2, Ly/2, Ny)
X, Y = np.meshgrid(x, y, indexing='ij')
# Physical parameters
B0 = 1.0
a = 0.5
rho0 = 1.0
beta_param = 1.0
p0 = B0**2 * beta_param / 2.0
gamma = 5.0 / 3.0
eta = 0.001
nu = 0.001 # Viscosity (for numerical stability)
CFL = 0.4
# Initial conditions
def initial_harris_sheet():
Bx = B0 * np.tanh(Y / a)
By = 0.1 * B0 * np.cos(2*np.pi*X/Lx) * np.sin(np.pi*Y/Ly) # Perturbation
Bz = np.zeros_like(Bx)
rho = rho0 * (1.0 + beta_param * (1.0/np.cosh(Y/a))**2)
p = p0 + B0**2/2.0 * (1.0 - np.tanh(Y/a)**2)
vx = np.zeros_like(Bx)
vy = np.zeros_like(Bx)
vz = np.zeros_like(Bx)
return rho, vx, vy, vz, p, Bx, By, Bz
# Conservative variables
def prim2cons(rho, vx, vy, vz, p, Bx, By, Bz):
v2 = vx**2 + vy**2 + vz**2
B2 = Bx**2 + By**2 + Bz**2
U1 = rho
U2 = rho * vx
U3 = rho * vy
U4 = rho * vz
U5 = p/(gamma-1.0) + 0.5*rho*v2 + 0.5*B2
U6 = Bx
U7 = By
U8 = Bz
return U1, U2, U3, U4, U5, U6, U7, U8
def cons2prim(U1, U2, U3, U4, U5, U6, U7, U8):
rho = U1
vx = U2 / rho
vy = U3 / rho
vz = U4 / rho
Bx = U6
By = U7
Bz = U8
v2 = vx**2 + vy**2 + vz**2
B2 = Bx**2 + By**2 + Bz**2
p = (gamma - 1.0) * (U5 - 0.5*rho*v2 - 0.5*B2)
p = np.maximum(p, 1e-6) # Floor
return rho, vx, vy, vz, p, Bx, By, Bz
# Flux computation (simplified HLL)
def flux_x(rho, vx, vy, vz, p, Bx, By, Bz):
v2 = vx**2 + vy**2 + vz**2
B2 = Bx**2 + By**2 + Bz**2
ptot = p + 0.5*B2
E = p/(gamma-1.0) + 0.5*rho*v2 + 0.5*B2
F1 = rho * vx
F2 = rho*vx*vx + ptot - Bx*Bx
F3 = rho*vx*vy - Bx*By
F4 = rho*vx*vz - Bx*Bz
F5 = (E + ptot)*vx - Bx*(vx*Bx + vy*By + vz*Bz)
F6 = np.zeros_like(Bx) # Bx constant in x
F7 = By*vx - Bx*vy
F8 = Bz*vx - Bx*vz
return F1, F2, F3, F4, F5, F6, F7, F8
def flux_y(rho, vx, vy, vz, p, Bx, By, Bz):
v2 = vx**2 + vy**2 + vz**2
B2 = Bx**2 + By**2 + Bz**2
ptot = p + 0.5*B2
E = p/(gamma-1.0) + 0.5*rho*v2 + 0.5*B2
G1 = rho * vy
G2 = rho*vy*vx - By*Bx
G3 = rho*vy*vy + ptot - By*By
G4 = rho*vy*vz - By*Bz
G5 = (E + ptot)*vy - By*(vx*Bx + vy*By + vz*Bz)
G6 = Bx*vy - By*vx
G7 = np.zeros_like(By) # By constant in y (with CT)
G8 = Bz*vy - By*vz
return G1, G2, G3, G4, G5, G6, G7, G8
# Simple HLL solver
def hll_flux_x(UL, UR, rhoL, vxL, vyL, vzL, pL, BxL, ByL, BzL,
rhoR, vxR, vyR, vzR, pR, BxR, ByR, BzR):
# Estimate wave speeds
csL = np.sqrt(gamma * pL / rhoL)
csR = np.sqrt(gamma * pR / rhoR)
SL = np.minimum(vxL - csL, vxR - csR)
SR = np.maximum(vxL + csL, vxR + csR)
FL = flux_x(rhoL, vxL, vyL, vzL, pL, BxL, ByL, BzL)
FR = flux_x(rhoR, vxR, vyR, vzR, pR, BxR, ByR, BzR)
# HLL flux
F_hll = []
for i in range(8):
F = np.where(SL >= 0, FL[i],
np.where(SR <= 0, FR[i],
(SR*FL[i] - SL*FR[i] + SL*SR*(UR[i] - UL[i])) / (SR - SL)))
F_hll.append(F)
return F_hll
# Main solver
def solve_reconnection():
# Initialize
rho, vx, vy, vz, p, Bx, By, Bz = initial_harris_sheet()
U = prim2cons(rho, vx, vy, vz, p, Bx, By, Bz)
t = 0.0
t_end = 50.0
step = 0
# Diagnostics
energy_mag = []
energy_kin = []
energy_th = []
times = []
print("Starting reconnection simulation...")
while t < t_end:
# Compute dt
cs = np.sqrt(gamma * p / rho)
vA = np.sqrt((Bx**2 + By**2 + Bz**2) / rho)
vmax = np.max(np.abs(vx) + np.abs(vy) + cs + vA)
dt = CFL * min(dx, dy) / vmax
if t + dt > t_end:
dt = t_end - t
# Update (simple forward Euler for demonstration; use RK2 in production)
# X-direction fluxes
Fx_L = []
Fx_R = []
for i in range(Nx):
iL = (i - 1) % Nx
iR = i
UL = [U[k][iL, :] for k in range(8)]
UR = [U[k][iR, :] for k in range(8)]
primL = cons2prim(*UL)
primR = cons2prim(*UR)
F = hll_flux_x(UL, UR, *primL, *primR)
if i == 0:
Fx_L = [np.zeros((Nx, Ny)) for _ in range(8)]
Fx_R = [np.zeros((Nx, Ny)) for _ in range(8)]
for k in range(8):
Fx_R[k][i, :] = F[k]
# Shift for left flux
for k in range(8):
Fx_L[k] = np.roll(Fx_R[k], 1, axis=0)
# Y-direction fluxes (similar, with boundary conditions)
# ... (omitted for brevity; apply conducting wall BC)
# Update conserved variables
U_new = []
for k in range(8):
dU = -(Fx_R[k] - Fx_L[k]) / dx # Only x-direction for simplicity
U_new.append(U[k] + dt * dU)
U = U_new
# Recover primitives
rho, vx, vy, vz, p, Bx, By, Bz = cons2prim(*U)
# Add resistivity (explicit diffusion)
Jz = (np.gradient(By, dx, axis=0) - np.gradient(Bx, dy, axis=1))
dBx_dt = -eta * np.gradient(Jz, dy, axis=1)
dBy_dt = eta * np.gradient(Jz, dx, axis=0)
Bx += dt * dBx_dt
By += dt * dBy_dt
# Update conserved variables with new B
U = prim2cons(rho, vx, vy, vz, p, Bx, By, Bz)
# Diagnostics
if step % 50 == 0:
E_mag = 0.5 * np.sum((Bx**2 + By**2 + Bz**2) * dx * dy)
E_kin = 0.5 * np.sum(rho * (vx**2 + vy**2 + vz**2) * dx * dy)
E_th = np.sum(p / (gamma - 1.0) * dx * dy)
energy_mag.append(E_mag)
energy_kin.append(E_kin)
energy_th.append(E_th)
times.append(t)
print(f"Step {step:5d}, t={t:6.2f}, E_mag={E_mag:.4f}, E_kin={E_kin:.4f}, E_th={E_th:.4f}")
t += dt
step += 1
if step > 10000: # Safety limit
break
return times, energy_mag, energy_kin, energy_th, rho, vx, vy, p, Bx, By, Jz
# Run simulation
times, E_mag, E_kin, E_th, rho_f, vx_f, vy_f, p_f, Bx_f, By_f, Jz_f = solve_reconnection()
# Plot energy evolution
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(times, E_mag, 'b-', label='Magnetic', linewidth=2)
ax.plot(times, E_kin, 'r--', label='Kinetic', linewidth=2)
ax.plot(times, E_th, 'g:', label='Thermal', linewidth=2)
E_tot = np.array(E_mag) + np.array(E_kin) + np.array(E_th)
ax.plot(times, E_tot, 'k-.', label='Total', linewidth=1.5)
ax.set_xlabel('Time')
ax.set_ylabel('Energy')
ax.set_title('Energy Evolution in Reconnection')
ax.legend()
ax.grid(True, alpha=0.3)
plt.savefig('reconnection_energy.png', dpi=150, bbox_inches='tight')
print("Energy plot saved: reconnection_energy.png")
plt.close()
# Plot final state
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Current density
im0 = axes[0, 0].contourf(X, Y, Jz_f, levels=30, cmap='RdBu_r')
axes[0, 0].set_title('Current Density $J_z$')
axes[0, 0].set_xlabel('x')
axes[0, 0].set_ylabel('y')
plt.colorbar(im0, ax=axes[0, 0])
# Temperature (pressure)
im1 = axes[0, 1].contourf(X, Y, p_f, levels=30, cmap='hot')
axes[0, 1].set_title('Pressure (Temperature)')
axes[0, 1].set_xlabel('x')
axes[0, 1].set_ylabel('y')
plt.colorbar(im1, ax=axes[0, 1])
# Velocity magnitude
v_mag = np.sqrt(vx_f**2 + vy_f**2)
im2 = axes[1, 0].contourf(X, Y, v_mag, levels=30, cmap='plasma')
axes[1, 0].streamplot(X.T, Y.T, vx_f.T, vy_f.T, color='k', density=1.0, linewidth=0.5)
axes[1, 0].set_title('Velocity Field')
axes[1, 0].set_xlabel('x')
axes[1, 0].set_ylabel('y')
plt.colorbar(im2, ax=axes[1, 0])
# Magnetic field lines
Ay = np.zeros_like(Bx_f) # Compute vector potential (simplified)
for i in range(1, Nx):
Ay[i, :] = Ay[i-1, :] + Bx_f[i, :] * dy
im3 = axes[1, 1].contour(X, Y, Ay, levels=20, colors='b', linewidths=1.5)
axes[1, 1].set_title('Magnetic Field Lines')
axes[1, 1].set_xlabel('x')
axes[1, 1].set_ylabel('y')
axes[1, 1].set_aspect('equal')
plt.tight_layout()
plt.savefig('reconnection_final.png', dpi=150, bbox_inches='tight')
print("Final state plot saved: reconnection_final.png")
plt.close()
1.4 ์์ ๊ฒฐ๊ณผ¶
- ์๋์ง ๋ณํ: ์๊ธฐ ์๋์ง ๊ฐ์, ์ด๋ ๋ฐ ์ด ์๋์ง ์ฆ๊ฐ
- ํ๋ผ์ฆ๋ชจ์ด๋ ํ์ฑ: ์ ๋ฅ ์ํธ์ 2์ฐจ ์์ผ๋๋ ๋ํ๋จ (ํด์๋ ์ถฉ๋ถํ๋ฉด)
- ์ฌ๊ฒฐํฉ๋ฅ : ์ ์ ์๋ $v_{\text{in}} \sim 0.01 - 0.1 v_A$ (Sweet-Parker $\sim \eta^{1/2}$๋ณด๋ค ๋น ๋ฆ)
- ์จ๋ ๊ธ์์น: X-์ ์์ ๊ตญ์ ๊ฐ์ด
1.5 ํ์ฅ¶
- ๊ตญ์ ์ ํญ: $\eta = \eta_0 + \eta_1 J^2$ ์ฌ์ฉ (๋น์ ์ ์ ํญ)
- 3D ์๋ฎฌ๋ ์ด์ : ๊ฐ์ด๋ ์ฅ $B_z$ ์ถ๊ฐ, drift-kink ๋ถ์์ ์ฑ ์ฐ๊ตฌ
- ์ ์ ๊ฐ์: ํ ์คํธ ์ ์ ์ฃผ์ , ์๋์งํ ์ถ์
- Sweet-Parker์ ๋น๊ต: ์ฌ๊ฒฐํฉ๋ฅ ๋ $\eta$ ์ธก์ , $M_A \propto S^{-1/2}$ (Sweet-Parker) ๋๋ $M_A \sim 0.01$ (ํ๋ผ์ฆ๋ชจ์ด๋) ๊ฒ์ฆ
ํ๋ก์ ํธ 2: ํ ์นด๋ง ๋ถ๊ดด ์์ธก¶
2.1 ๋ฌผ๋ฆฌ ๋ฐฐ๊ฒฝ¶
ํ ์นด๋ง(Tokamak): ํต์ตํฉ ํ๋ผ์ฆ๋ง๋ฅผ ์ํ ํํ ์๊ธฐ ๊ฐ๋ ์ฅ์น.
ํต์ฌ ๊ฐ๋ : - ์์ ์ธ์(Safety Factor): $q(r) = r B_\phi / (R B_\theta)$ (์ฅ์ ํผ์น) - ๋ถ๊ดด(Disruption): MHD ๋ถ์์ ์ฑ์ผ๋ก ์ธํ ๊ฐ๋ ์ ๊ฐ์์ค๋ฐ ์์ค โ ํ๋ผ์ฆ๋ง ์ข ๋ฃ, ๋ฒฝ์ ํฐ ํ
์์ ์ฑ ๊ธฐ์ค: - Kruskal-Shafranov: $q > 1$ ($m=1$ kink ์ต์ ) - Suydam ๊ธฐ์ค: ๊ตํ ๋ชจ๋์ ๋ํ ๊ตญ์ ์์ ์ฑ - ํฐ์ด๋ง ๋ชจ๋: ์ ๋ฆฌ์ ํ๋ฉด($q = m/n$)์ด $\Delta' > 0$์ด๋ฉด ๋ถ์์
2.2 ๋ฌธ์ ์ค์ ¶
์ํตํ ํ ์นด๋ง ๋ชจ๋ธ (1D): - ์๋ฐ๊ฒฝ: $a = 1.0$ m - ๋๋ฐ๊ฒฝ: $R_0 = 3.0$ m - ํํ ์ฅ: $B_\phi(r) = B_0 R_0 / (R_0 + r)$ (๊ทผ์ฌ) - ์ ๋ฅ ํ๋กํ์ผ $J_\phi(r)$๋ก๋ถํฐ ํด๋ก์ด๋ฌ ์ฅ
ํ ์คํธํ ์ ๋ฅ ํ๋กํ์ผ: - ํ๋กํ์ผ 1: ์ถ์์ ๋พฐ์กฑ (์์ ) $$ J_\phi(r) = J_0 \left(1 - (r/a)^2\right)^2 $$ - ํ๋กํ์ผ 2: ์ค๊ณต ์ ๋ฅ (๋ถ์์ ) $$ J_\phi(r) = J_0 (r/a) \left(1 - (r/a)^2\right) $$ - ํ๋กํ์ผ 3: ์์ง ๋พฐ์กฑ (๋ถ๊ดด ์ทจ์ฝ) $$ J_\phi(r) = J_0 \left(1 - \exp(-(r/a)^4)\right) $$
๋ชฉํ: $q(r)$ ๊ณ์ฐ, ์์ ์ฑ ํ์ธ, ๋ถ๊ดด ์์ธก.
2.3 ๊ตฌํ¶
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import cumulative_trapezoid
# Parameters
a = 1.0 # Minor radius (m)
R0 = 3.0 # Major radius (m)
B0 = 2.0 # Toroidal field at axis (T)
Nr = 200
r = np.linspace(0, a, Nr)
# Current profiles
def current_profile(r, profile_type='peaked'):
if profile_type == 'peaked':
J = 1.0e6 * (1.0 - (r/a)**2)**2
elif profile_type == 'hollow':
J = 1.0e6 * (r/a) * (1.0 - (r/a)**2)
elif profile_type == 'edge':
J = 1.0e6 * (1.0 - np.exp(-(r/a)**4))
else:
J = np.ones_like(r) * 1.0e6
return J
# Compute poloidal field from Ampere's law
def compute_Btheta(r, J):
"""B_theta(r) = (mu_0 / r) * integral_0^r J(r') r' dr'"""
mu0 = 4e-7 * np.pi
integrand = J * r
I_enc = cumulative_trapezoid(integrand, r, initial=0)
Btheta = mu0 * I_enc / (r + 1e-10) # Avoid r=0
return Btheta
# Toroidal field (approximate, ignoring r/R0 correction)
def compute_Bphi(r):
return B0 * R0 / (R0 + r)
# Safety factor
def compute_q(r, Btheta, Bphi):
"""q = r * Bphi / (R0 * Btheta)"""
q = r * Bphi / (R0 * Btheta + 1e-10)
return q
# Suydam criterion: d(ln p) / d(ln r) + (r * dq/dr / q)^2 > 0
def suydam_criterion(r, q, p):
"""Simplified: check d(ln q)/d(ln r) > some threshold"""
dq_dr = np.gradient(q, r)
shear = r / q * dq_dr
# Simplified: shear > 0.5 (stable)
return shear > 0.5
# Tearing mode Delta' (simplified estimate)
def estimate_delta_prime(r, q, m, n):
"""Find rational surface q = m/n, estimate Delta'"""
q_rational = m / n
idx = np.argmin(np.abs(q - q_rational))
rs = r[idx]
if idx > 5 and idx < Nr - 5:
# Estimate logarithmic derivative mismatch
dq_dr = np.gradient(q, r)
delta_prime = -2.0 * dq_dr[idx] / q[idx] # Crude estimate
else:
delta_prime = 0.0
return rs, delta_prime
# Analyze stability for a profile
def analyze_stability(profile_type):
J = current_profile(r, profile_type)
Btheta = compute_Btheta(r, J)
Bphi = compute_Bphi(r)
q = compute_q(r, Btheta, Bphi)
# Simple pressure profile (proportional to current)
p = 1e5 * (1.0 - (r/a)**2)**2
# Kruskal-Shafranov: q > 1 everywhere?
q_min = np.min(q[1:]) # Skip r=0
ks_stable = q_min > 1.0
# Suydam
shear = np.zeros_like(r)
shear[1:] = r[1:] / q[1:] * np.gradient(q, r)[1:]
suydam_stable = np.all(shear[1:] > 0.5)
# Tearing mode (m=2, n=1)
rs_21, delta_prime_21 = estimate_delta_prime(r, q, m=2, n=1)
tearing_stable = delta_prime_21 < 0
return {
'r': r,
'J': J,
'q': q,
'Btheta': Btheta,
'Bphi': Bphi,
'q_min': q_min,
'ks_stable': ks_stable,
'suydam_stable': suydam_stable,
'tearing_stable': tearing_stable,
'delta_prime_21': delta_prime_21,
'rs_21': rs_21
}
# Analyze all profiles
profiles = ['peaked', 'hollow', 'edge']
results = {ptype: analyze_stability(ptype) for ptype in profiles}
# Plot results
fig, axes = plt.subplots(2, 3, figsize=(16, 10))
colors = {'peaked': 'blue', 'hollow': 'red', 'edge': 'green'}
for i, ptype in enumerate(profiles):
res = results[ptype]
# Current profile
axes[0, i].plot(res['r'], res['J']/1e6, color=colors[ptype], linewidth=2)
axes[0, i].set_xlabel('r (m)')
axes[0, i].set_ylabel('$J_\\phi$ (MA/mยฒ)')
axes[0, i].set_title(f'{ptype.capitalize()} Current')
axes[0, i].grid(True, alpha=0.3)
# Safety factor
axes[1, i].plot(res['r'], res['q'], color=colors[ptype], linewidth=2, label='q(r)')
axes[1, i].axhline(1.0, color='k', linestyle='--', label='q=1 (Kruskal)')
axes[1, i].axhline(2.0, color='gray', linestyle=':', label='q=2')
axes[1, i].set_xlabel('r (m)')
axes[1, i].set_ylabel('Safety Factor q')
axes[1, i].set_title(f'q(r) - {ptype.capitalize()}')
axes[1, i].legend()
axes[1, i].grid(True, alpha=0.3)
axes[1, i].set_ylim([0, 10])
plt.tight_layout()
plt.savefig('tokamak_profiles.png', dpi=150, bbox_inches='tight')
print("Profile plots saved: tokamak_profiles.png")
plt.close()
# Stability summary
print("\n=== STABILITY ANALYSIS ===\n")
for ptype in profiles:
res = results[ptype]
print(f"--- {ptype.upper()} PROFILE ---")
print(f" q_min = {res['q_min']:.3f}")
print(f" Kruskal-Shafranov (q>1): {'STABLE' if res['ks_stable'] else 'UNSTABLE'}")
print(f" Suydam: {'STABLE' if res['suydam_stable'] else 'UNSTABLE'}")
print(f" Tearing (2,1) Delta' = {res['delta_prime_21']:.3f}: {'STABLE' if res['tearing_stable'] else 'UNSTABLE'}")
print(f" Rational surface (q=2): r_s = {res['rs_21']:.3f} m")
# Disruption prediction
if not res['ks_stable']:
print(f" PREDICTION: HIGH DISRUPTION RISK (Kruskal violation)")
elif not res['tearing_stable']:
print(f" PREDICTION: MEDIUM DISRUPTION RISK (Tearing unstable)")
else:
print(f" PREDICTION: LOW DISRUPTION RISK")
print()
# Estimate energy release during disruption
def estimate_disruption_energy(res):
"""Magnetic + thermal energy in plasma"""
B2 = res['Btheta']**2 + res['Bphi']**2
V = 2 * np.pi * R0 * np.pi * a**2 # Approximate volume
E_mag = np.trapz(B2 / (2 * 4e-7*np.pi) * 2*np.pi*R0*res['r'], res['r'])
p = 1e5 * (1.0 - (res['r']/a)**2)**2
E_th = np.trapz(1.5 * p * 2*np.pi*R0*res['r'], res['r'])
return E_mag, E_th
print("\n=== DISRUPTION ENERGY ESTIMATE ===\n")
for ptype in profiles:
res = results[ptype]
E_mag, E_th = estimate_disruption_energy(res)
print(f"{ptype.upper()}: E_mag = {E_mag/1e6:.2f} MJ, E_th = {E_th/1e6:.2f} MJ")
print(f" Total = {(E_mag + E_th)/1e6:.2f} MJ")
print()
2.4 ์์ ๊ฒฐ๊ณผ¶
- ๋พฐ์กฑ ํ๋กํ์ผ: ์์ (๋ชจ๋ ๊ธฐ์ค ๋ง์กฑ)
- ์ค๊ณต ํ๋กํ์ผ: ํ๊ณ ($q=2$ ํ๋ฉด์์ ํฐ์ด๋ง ๋ถ์์ ์ฑ ๊ฐ๋ฅ)
- ์์ง ํ๋กํ์ผ: ๋ถ์์ (Kruskal ์๋ฐ, ๋ถ๊ดด ๊ฐ๋ฅ์ฑ)
๋ถ๊ดด ์๋์ง: ~10-100 MJ (๋ฒฝ์ ํ ~ MN, ์ํ ํ์!)
2.5 ํ์ฅ¶
- ์ ๊ณ ์ ํฐ์ด๋ง ๋ชจ๋(NTM): ๋ถํธ์คํธ๋ฉ ์ ๋ฅ ์ญ๋ ํฌํจ
- ์์ง ๋ณ์ ์ฌ๊ฑด(VDE): ์ถ๋์นญ ๋ถ์์ ์ฑ
- ์ ํญ์ฑ ๋ฒฝ ๋ชจ๋: ๋ฒฝ ์์ ํ ํฌํจ
- ๋ถ๊ดด ์ํ: ๋๋ ๊ฐ์ค ์ฃผ์ (MGI) ์๋ฎฌ๋ ์ด์
ํ๋ก์ ํธ 3: ๊ตฌํ ์ ๋ค์ด๋๋ชจ¶
3.1 ๋ฌผ๋ฆฌ ๋ฐฐ๊ฒฝ¶
๋ค์ด๋๋ชจ ์ด๋ก : ์ ์ฒด ์ด๋์ ์ํ ์๊ธฐ์ฅ ์์ฒด ์ ์ง ์์ฑ (์: ์ง๊ตฌ ํต, ํ์ ๋๋ฅ ์์ญ).
ํต์ฌ ๊ฐ๋ : - ์ด๋ํ์ ๋ค์ด๋๋ชจ(Kinematic Dynamo): ์๋์ฅ $\mathbf{v}$ ๊ท์ , $\mathbf{B}$ ํ๊ธฐ - ํ๊ท ์ฅ ์ด๋ก : $\mathbf{B} = \mathbf{B}_0 + \mathbf{b}$ ๋ถ๋ฆฌ, ๋๋ฅ EMF $\langle \mathbf{v} \times \mathbf{b} \rangle = \alpha \mathbf{B}_0 - \beta \mathbf{J}$ - $\alpha$-ํจ๊ณผ: ์ฌ์ดํด๋ก ๋๋ฅ โ ๋์ ํ ์ฅ ์์ฑ - $\Omega$-ํจ๊ณผ: ๋ฏธ๋ถ ํ์ โ ํด๋ก์ด๋ฌ์์ ํํ ์ฅ
์ถ๋์นญ ํ๊ท ์ฅ ๋ฐฉ์ ์: $$ \frac{\partial A}{\partial t} = \eta \nabla^2 A + \alpha B_\phi $$ $$ \frac{\partial B_\phi}{\partial t} = \eta \nabla^2 B_\phi + r \sin\theta \, \mathbf{B}_p \cdot \nabla \Omega + \ldots $$ ์ฌ๊ธฐ์ $\mathbf{B}_p = \nabla \times (A \hat{\phi})$ (ํด๋ก์ด๋ฌ ์ฅ).
3.2 ๋ฌธ์ ์ค์ ¶
์์ญ: ๊ตฌํ ์ $r \in [r_i, r_o]$, $\theta \in [0, \pi]$ (์ถ๋์นญ, $\phi$-๋ฌด๊ด)
๋งค๊ฐ๋ณ์: - ๋ด๋ถ ๋ฐ๊ฒฝ: $r_i = 0.5$ - ์ธ๋ถ ๋ฐ๊ฒฝ: $r_o = 1.0$ - ๋ฏธ๋ถ ํ์ : $\Omega(r, \theta) = \Omega_0 \cos^2\theta \, (1 - (r/r_o)^2)$ (ํ์ํ) - $\alpha$-ํจ๊ณผ: $\alpha(r, \theta) = \alpha_0 \sin\theta \cos\theta$ (์๊ทน์ ์ ํธ) - ์๊ธฐ ํ์ฐ๋: $\eta = 10^{-3}$ - $\alpha_0 = 1.0$, $\Omega_0 = 10.0$
๋ฐฉ์ ์ (๋จ์ํ๋ 2D): $$ \frac{\partial A}{\partial t} = \eta \left(\frac{\partial^2 A}{\partial r^2} + \frac{1}{r^2}\frac{\partial^2 A}{\partial \theta^2}\right) + \alpha B_\phi $$ $$ \frac{\partial B_\phi}{\partial t} = \eta \left(\frac{\partial^2 B_\phi}{\partial r^2} + \frac{1}{r^2}\frac{\partial^2 B_\phi}{\partial \theta^2}\right) + C_\Omega \frac{\partial A}{\partial \theta} $$ ์ฌ๊ธฐ์ $C_\Omega = r \sin\theta \, \partial_r \Omega$.
๊ฒฝ๊ณ ์กฐ๊ฑด: - $r = r_i, r_o$์์ $A = 0$ (ํ๋ฉด์ ํํํ ์ฅ์ ) - $r = r_i, r_o$์์ $B_\phi = 0$ (์ธ๋ถ ์ง๊ณต)
3.3 ๊ตฌํ¶
import numpy as np
import matplotlib.pyplot as plt
# Parameters
ri, ro = 0.5, 1.0
Nr, Nth = 64, 64
r = np.linspace(ri, ro, Nr)
theta = np.linspace(0, np.pi, Nth)
R, Theta = np.meshgrid(r, theta, indexing='ij')
dr = (ro - ri) / (Nr - 1)
dtheta = np.pi / (Nth - 1)
eta = 1e-3
alpha0 = 1.0
Omega0 = 10.0
dt = 1e-4
t_end = 2.0
# Differential rotation
Omega = Omega0 * np.cos(Theta)**2 * (1.0 - (R/ro)**2)
# Alpha effect
alpha = alpha0 * np.sin(Theta) * np.cos(Theta)
# Omega-effect coefficient
C_Omega = R * np.sin(Theta) * np.gradient(Omega, dr, axis=0)
# Initialize fields
A = np.random.randn(Nr, Nth) * 0.01
Bphi = np.random.randn(Nr, Nth) * 0.01
# Apply BC
A[0, :] = 0
A[-1, :] = 0
Bphi[0, :] = 0
Bphi[-1, :] = 0
# Laplacian operator (finite differences)
def laplacian_2d(f, dr, dtheta, r):
"""Compute Laplacian in (r, theta) with metric terms."""
d2f_dr2 = np.zeros_like(f)
d2f_dtheta2 = np.zeros_like(f)
# r-direction (central differences)
d2f_dr2[1:-1, :] = (f[2:, :] - 2*f[1:-1, :] + f[:-2, :]) / dr**2
# theta-direction
d2f_dtheta2[:, 1:-1] = (f[:, 2:] - 2*f[:, 1:-1] + f[:, :-2]) / dtheta**2
# Add metric terms (simplified)
laplacian = d2f_dr2 + d2f_dtheta2 / r[:, np.newaxis]**2
return laplacian
# Time evolution
t = 0.0
step = 0
snapshots = []
print("Running spherical shell dynamo...")
while t < t_end:
# Compute Laplacians
lap_A = laplacian_2d(A, dr, dtheta, r)
lap_Bphi = laplacian_2d(Bphi, dr, dtheta, r)
# Source terms
dA_dt_theta = np.gradient(A, dtheta, axis=1)
source_A = alpha * Bphi
source_Bphi = C_Omega * dA_dt_theta
# Update
A += dt * (eta * lap_A + source_A)
Bphi += dt * (eta * lap_Bphi + source_Bphi)
# Apply BC
A[0, :] = 0
A[-1, :] = 0
Bphi[0, :] = 0
Bphi[-1, :] = 0
# Diagnostics
if step % 1000 == 0:
E_A = np.sum(A**2)
E_Bphi = np.sum(Bphi**2)
print(f"Step {step:5d}, t={t:.4f}, E_A={E_A:.4f}, E_Bphi={E_Bphi:.4f}")
if len(snapshots) < 5:
snapshots.append((t, A.copy(), Bphi.copy()))
t += dt
step += 1
print("Dynamo simulation complete.")
# Plot snapshots
fig, axes = plt.subplots(len(snapshots), 2, figsize=(12, 3*len(snapshots)))
for i, (t_snap, A_snap, Bphi_snap) in enumerate(snapshots):
ax_A = axes[i, 0] if len(snapshots) > 1 else axes[0]
ax_B = axes[i, 1] if len(snapshots) > 1 else axes[1]
# Poloidal field (contours of A)
im_A = ax_A.contourf(R*np.sin(Theta), R*np.cos(Theta), A_snap, levels=20, cmap='RdBu_r')
ax_A.set_xlabel('x')
ax_A.set_ylabel('z')
ax_A.set_title(f'Poloidal Field (A), t={t_snap:.3f}')
ax_A.set_aspect('equal')
plt.colorbar(im_A, ax=ax_A)
# Toroidal field
im_B = ax_B.contourf(R*np.sin(Theta), R*np.cos(Theta), Bphi_snap, levels=20, cmap='seismic')
ax_B.set_xlabel('x')
ax_B.set_ylabel('z')
ax_B.set_title(f'Toroidal Field $B_\\phi$, t={t_snap:.3f}')
ax_B.set_aspect('equal')
plt.colorbar(im_B, ax=ax_B)
plt.tight_layout()
plt.savefig('dynamo_evolution.png', dpi=150, bbox_inches='tight')
print("Dynamo evolution saved: dynamo_evolution.png")
plt.close()
# Butterfly diagram (Bphi vs time at mid-radius)
r_mid_idx = Nr // 2
butterfly_Bphi = []
butterfly_times = []
# Re-run to collect time series (or store during main loop)
# For demonstration, use final state
butterfly_Bphi.append(Bphi[r_mid_idx, :])
butterfly_times.append(t_end)
fig, ax = plt.subplots(figsize=(10, 6))
if len(butterfly_times) > 1:
ax.contourf(butterfly_times, theta, np.array(butterfly_Bphi).T, levels=30, cmap='RdBu_r')
else:
ax.plot(theta, Bphi[r_mid_idx, :], 'b-', linewidth=2)
ax.set_xlabel('$\\theta$ (rad)')
ax.set_ylabel('$B_\\phi$')
ax.set_title(f'Butterfly Diagram (r={r[r_mid_idx]:.2f})')
ax.grid(True, alpha=0.3)
plt.savefig('butterfly_diagram.png', dpi=150, bbox_inches='tight')
print("Butterfly diagram saved: butterfly_diagram.png")
plt.close()
3.4 ์์ ๊ฒฐ๊ณผ¶
- ์ง๋ ๋ค์ด๋๋ชจ: $A$์ $B_\phi$๊ฐ ์๊ฐ์ ๋ฐ๋ผ ์ง๋ (์๊ธฐ ์ฃผ๊ธฐ)
- ์ ๋ ๋ฐฉํฅ ์ด๋: ํํ ์ฅ์ด ๊ทน์์ ์ ๋๋ก ์ด๋ ($\alpha$-$\Omega$ ๋งค๊ฐ๋ณ์๊ฐ ์ ์ ํ๋ฉด)
- ๋๋น ๋ค์ด์ด๊ทธ๋จ: $B_\phi$์ ์๋-์๊ฐ ์งํ ํ์ ($C_\Omega$์ $\alpha$๊ฐ ์ ์ ํ๋๋ฉด ํ์ํ)
Parker ์ด๋ ๋ค์ด๋๋ชจ: ์ ์ ํ ๋งค๊ฐ๋ณ์๋ก ํ์ 22๋ ์ฃผ๊ธฐ ์ฌํ!
3.5 ํ์ฅ¶
- 3D ๋ค์ด๋๋ชจ: ์์ ํ ๊ตฌ ์ขํ๊ณ, ๋๋ฅ ํฌํจ
- ๋น์ ํ quenching: $\alpha \to \alpha(B)$ (Lorentz ํ ์ญ๋ฐ์)
- ๋ฐ์ : ํ๋ฅ ์ $\alpha$ ๋ณ๋ โ ๊ทน์ฑ ๋ฐ์ (์ง๊ตฌ ์ฅ)
- ๋ฒค์น๋งํฌ: Dedalus ์คํํธ๋ด ์ฝ๋์ ๋น๊ต
ํ๋ก์ ํธ ์์ฝ¶
| ํ๋ก์ ํธ | ๋ฌผ๋ฆฌ | ๋ฐฉ๋ฒ | ๋์ด๋ |
|---|---|---|---|
| ํ์ ํ๋ ์ด | ์๊ธฐ ์ฌ๊ฒฐํฉ, ํ๋ผ์ฆ๋ชจ์ด๋ | 2D ์ ํญ์ฑ MHD, HLL ์๋ฒ | โ โ โ โ |
| ํ ์นด๋ง ๋ถ๊ดด | MHD ์์ ์ฑ, ์์ ์ธ์ | 1D ํํ, ์์ ์ฑ ๊ธฐ์ค | โ โ โ |
| ๋ค์ด๋๋ชจ | ํ๊ท ์ฅ ์ด๋ก , $\alpha$-$\Omega$ | 2D ์ถ๋์นญ, ์๊ฐ ์งํ | โ โ โ โ |
์ฐ์ต๋ ๊ธฐ์ : - ๋ณด์กดํ MHD ์๋ฒ (Godunov ์ ํ) - ํํ ๋ถ์๊ณผ ์์ ์ฑ ์ด๋ก - ํ๊ท ์ฅ ๊ทผ์ฌ - ์์น ๊ฒฐ๊ณผ์ ๋ฌผ๋ฆฌ์ ํด์
ํ๋ก์ ํธ๋ฅผ ์ํ ์ผ๋ฐ ํ¶
- ๊ฐ๋จํ๊ฒ ์์: 2D ์ ์ 1D๋ฅผ ์๋์ํค๊ณ , ๊ณ ํด์๋ ์ ์ ์ ํด์๋
- ๊ฒ์ฆ: ์๋ ค์ง ํด์ ๋น๊ต (์: ํ๋ก์ ํธ 1์ ์ํ Brio-Wu)
- ์ง๋จ: ์๋์ง ๋ณด์กด, $\nabla \cdot \mathbf{B}$, CFL ํ์์คํ ๋ชจ๋ํฐ
- ์๊ฐํ: $\mathbf{B}$์ ๋ํด streamplot ์ฌ์ฉ, ์ค์นผ๋ผ ์ฅ์ ๋ํด ์ค๊ณฝ
- ๋งค๊ฐ๋ณ์ ์ค์บ: $\eta$, $Re$, ๊ทธ๋ฆฌ๋ ํด์๋ ๋ณํ โ ์๋ ด ์ฐ๊ตฌ
- ๋ฌผ๋ฆฌ์ ์ง๊ด: ๊ฒฐ๊ณผ๊ฐ ์๋ฏธ๊ฐ ์์ต๋๊น? (์: ์ฌ๊ฒฐํฉ์ ํ๋ผ์ฆ๋ง๋ฅผ ๊ฐ์ดํด์ผ ํจ)
๊ฒฐ๋ก ¶
์ด ์ธ ๊ฐ์ง ํ๋ก์ ํธ๋ ์ ์ฒด MHD ๊ณผ์ ์ ํตํฉํฉ๋๋ค:
- ๋ณด์กด ๋ฒ์น: ์ง๋, ์ด๋๋, ์๋์ง (ํ๋ก์ ํธ 1)
- ํ๋ ๋ฌผ๋ฆฌ: ๊ณ ์, ์ ์, Alfvรฉn (ํ๋ก์ ํธ 1)
- ์์ ์ฑ: Kruskal-Shafranov, Suydam, ํฐ์ด๋ง (ํ๋ก์ ํธ 2)
- ๋ค์ด๋๋ชจ ์ด๋ก : $\alpha$-ํจ๊ณผ, ๋ฏธ๋ถ ํ์ (ํ๋ก์ ํธ 3)
- ์์น ๋ฐฉ๋ฒ: ์ ํ ์ฒด์ , Riemann ์๋ฒ, ์๊ฐ ์ ๋ถ (๋ชจ๋ ํ๋ก์ ํธ)
์ด๊ฒ๋ค์ ์๋ฃํจ์ผ๋ก์จ, ๋ํ์ ์์ค ์๊ธฐ์ ์ฒด์ญํ์ ๋ง์คํฐํ์ต๋๋ค!
์ถ๊ฐ ์ฝ์๊ฑฐ๋ฆฌ¶
ํ์ ์ฌ๊ฒฐํฉ¶
- Zweibel & Yamada (2009), Magnetic Reconnection in Astrophysical and Laboratory Plasmas, ARA&A
- Loureiro et al. (2007), Instability of current sheets and formation of plasmoid chains, Physics of Plasmas
ํ ์นด๋ง ์์ ์ฑ¶
- Wesson & Campbell (2011), Tokamaks, 4th Ed., Oxford
- Freidberg (2014), Ideal MHD, Cambridge
๋ค์ด๋๋ชจ ์ด๋ก ¶
- Moffatt (1978), Magnetic Field Generation in Electrically Conducting Fluids, Cambridge
- Brandenburg & Subramanian (2005), Astrophysical magnetic fields and nonlinear dynamo theory, Physics Reports
์์น MHD¶
- Tรณth et al. (2012), Adaptive numerical algorithms in space weather modeling, JCP
- Stone et al. (2020), Athena++: A Fast, Portable, and Multi-Physics PDE Solver, ApJS
์ด์ : ์คํํธ๋ด ๋ฐ ๊ณ ๊ธ ๋ฐฉ๋ฒ | ๋ค์: ์์ (๋ง์ง๋ง ๋ ์จ)