16. FDTD ๊ตฌํ (FDTD Implementation)
16. FDTD ๊ตฌํ (FDTD Implementation)¶
ํ์ต ๋ชฉํ¶
- 1D FDTD์ ์์ ํ ๊ตฌํ
- ์์ค ์ฌ๊ธฐ ๋ฐฉ๋ฒ (๊ฐ์ฐ์์ ํ์ค, ์ ํํ)
- ํก์ ๊ฒฝ๊ณ์กฐ๊ฑด (Simple ABC, Mur ABC)
- 2D FDTD (TM, TE ๋ชจ๋)
- PML (Perfectly Matched Layer) ๊ฐ๋
1. 1D FDTD ์์ ๊ตฌํ¶
1.1 ๊ธฐ๋ณธ ๊ตฌ์กฐ¶
1D FDTD ์๊ณ ๋ฆฌ์ฆ:
์ด๊ธฐํ:
- ๊ฒฉ์ ์ค์ (Nx, dx, dt)
- ๋ฐฐ์ด ์ด๊ธฐํ (Ey, Hz)
- ๋ฌผ์ฑ์น ์ค์ (ฮต, ฮผ, ฯ)
์๊ฐ ๋ฃจํ:
for n = 1, 2, ..., Nt:
1. H ์
๋ฐ์ดํธ: Hz^(n+1/2) = f(Hz^(n-1/2), Ey^n)
2. ์์ค ์ฃผ์
(soft/hard)
3. E ์
๋ฐ์ดํธ: Ey^(n+1) = f(Ey^n, Hz^(n+1/2))
4. ๊ฒฝ๊ณ์กฐ๊ฑด ์ ์ฉ (ABC)
5. ๋ฐ์ดํฐ ๊ธฐ๋ก/์ถ๋ ฅ
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
# ๋ฌผ๋ฆฌ ์์
c0 = 299792458.0 # ๊ด์ [m/s]
eps0 = 8.854187817e-12 # ์ง๊ณต ์ ์ ์จ [F/m]
mu0 = 4 * np.pi * 1e-7 # ์ง๊ณต ํฌ์์จ [H/m]
eta0 = np.sqrt(mu0 / eps0) # ์ง๊ณต ์ํผ๋์ค [ฮฉ]
class FDTD_1D:
"""1D FDTD ์๋ฎฌ๋ ์ดํฐ"""
def __init__(self, Nx=200, dx=1e-3, courant=0.99):
"""
Parameters:
- Nx: ๊ฒฉ์์ ์
- dx: ๊ณต๊ฐ ๊ฐ๊ฒฉ [m]
- courant: Courant ์ (โค 1)
"""
self.Nx = Nx
self.dx = dx
self.dt = courant * dx / c0
# ํ๋ ๋ฐฐ์ด
self.Ey = np.zeros(Nx)
self.Hz = np.zeros(Nx)
# ๋ฌผ์ฑ์น ๋ฐฐ์ด (์๋๊ฐ)
self.eps_r = np.ones(Nx)
self.mu_r = np.ones(Nx)
self.sigma = np.zeros(Nx) # ์ ๊ธฐ ์ ๋๋
self.sigma_m = np.zeros(Nx) # ์๊ธฐ ์ ๋๋
# ABC์ฉ ์ด์ ํ๋๊ฐ
self.Ey_left_prev = [0, 0]
self.Ey_right_prev = [0, 0]
# ์๊ฐ
self.time_step = 0
print(f"1D FDTD ์ด๊ธฐํ:")
print(f" Nx = {Nx}, dx = {dx*1000:.2f} mm")
print(f" dt = {self.dt*1e12:.3f} ps")
print(f" Courant S = {courant}")
def set_material(self, start, end, eps_r=1, sigma=0):
"""์ฌ๋ฃ ์์ญ ์ค์ """
self.eps_r[start:end] = eps_r
self.sigma[start:end] = sigma
def compute_coefficients(self):
"""์
๋ฐ์ดํธ ๊ณ์ ๊ณ์ฐ"""
eps = eps0 * self.eps_r
mu = mu0 * self.mu_r
# E ์
๋ฐ์ดํธ ๊ณ์ (์์ค ํฌํจ)
self.Ca = (1 - self.sigma * self.dt / (2 * eps)) / \
(1 + self.sigma * self.dt / (2 * eps))
self.Cb = (self.dt / (eps * self.dx)) / \
(1 + self.sigma * self.dt / (2 * eps))
# H ์
๋ฐ์ดํธ ๊ณ์
self.Da = (1 - self.sigma_m * self.dt / (2 * mu)) / \
(1 + self.sigma_m * self.dt / (2 * mu))
self.Db = (self.dt / (mu * self.dx)) / \
(1 + self.sigma_m * self.dt / (2 * mu))
def update_H(self):
"""H ํ๋ ์
๋ฐ์ดํธ"""
self.Hz[:-1] = (self.Da[:-1] * self.Hz[:-1] -
self.Db[:-1] * (self.Ey[1:] - self.Ey[:-1]))
def update_E(self):
"""E ํ๋ ์
๋ฐ์ดํธ"""
self.Ey[1:-1] = (self.Ca[1:-1] * self.Ey[1:-1] -
self.Cb[1:-1] * (self.Hz[1:-1] - self.Hz[:-2]))
def add_source_soft(self, position, value):
"""์ํํธ ์์ค (์ด ํ๋/์ฐ๋ ํ๋ ๊ฒฝ๊ณ)"""
self.Ey[position] += value
def add_source_hard(self, position, value):
"""ํ๋ ์์ค (๊ฐ์ ์ฃผ์
)"""
self.Ey[position] = value
def apply_abc_simple(self):
"""๊ฐ๋จํ ํก์ ๊ฒฝ๊ณ์กฐ๊ฑด (1์ฐจ)"""
# ์ข์ธก ๊ฒฝ๊ณ
self.Ey[0] = self.Ey_left_prev[0]
self.Ey_left_prev[0] = self.Ey_left_prev[1]
self.Ey_left_prev[1] = self.Ey[1]
# ์ฐ์ธก ๊ฒฝ๊ณ
self.Ey[-1] = self.Ey_right_prev[0]
self.Ey_right_prev[0] = self.Ey_right_prev[1]
self.Ey_right_prev[1] = self.Ey[-2]
def apply_abc_mur(self):
"""Mur 1์ฐจ ํก์ ๊ฒฝ๊ณ์กฐ๊ฑด"""
coeff = (c0 * self.dt - self.dx) / (c0 * self.dt + self.dx)
# ์ข์ธก ๊ฒฝ๊ณ
self.Ey[0] = self.Ey_left_prev[1] + coeff * (self.Ey[1] - self.Ey_left_prev[0])
self.Ey_left_prev[0] = self.Ey[0]
self.Ey_left_prev[1] = self.Ey[1]
# ์ฐ์ธก ๊ฒฝ๊ณ
self.Ey[-1] = self.Ey_right_prev[1] + coeff * (self.Ey[-2] - self.Ey_right_prev[0])
self.Ey_right_prev[0] = self.Ey[-1]
self.Ey_right_prev[1] = self.Ey[-2]
def step(self, source_func=None, source_pos=None, abc_type='mur'):
"""ํ ์๊ฐ ๋จ๊ณ ์ ์ง"""
self.update_H()
if source_func is not None and source_pos is not None:
t = self.time_step * self.dt
self.add_source_soft(source_pos, source_func(t))
self.update_E()
if abc_type == 'simple':
self.apply_abc_simple()
elif abc_type == 'mur':
self.apply_abc_mur()
else: # PEC
self.Ey[0] = 0
self.Ey[-1] = 0
self.time_step += 1
def run(self, n_steps, source_func=None, source_pos=None, abc_type='mur',
record_interval=1):
"""์๋ฎฌ๋ ์ด์
์คํ"""
self.compute_coefficients()
Ey_history = []
Hz_history = []
for n in range(n_steps):
self.step(source_func, source_pos, abc_type)
if n % record_interval == 0:
Ey_history.append(self.Ey.copy())
Hz_history.append(self.Hz.copy())
return np.array(Ey_history), np.array(Hz_history)
def gaussian_pulse(t, t0=1e-10, tau=3e-11):
"""๊ฐ์ฐ์์ ํ์ค ์์ค"""
return np.exp(-((t - t0) / tau) ** 2)
def sinusoidal_source(t, freq=3e9, t0=5e-11, tau=2e-11):
"""๋ณ์กฐ๋ ์ ํํ ์์ค"""
envelope = 1 - np.exp(-((t - t0) / tau) ** 2) if t < t0 else 1
return envelope * np.sin(2 * np.pi * freq * t)
def demo_1d_fdtd_basic():
"""1D FDTD ๊ธฐ๋ณธ ์์ฐ"""
# ์๋ฎฌ๋ ์ดํฐ ์์ฑ
fdtd = FDTD_1D(Nx=300, dx=1e-3, courant=0.99)
# ์ ์ ์ฒด ์ฌ๋๋ธ ์ถ๊ฐ
fdtd.set_material(150, 200, eps_r=4.0)
# ์๋ฎฌ๋ ์ด์
์คํ
source_pos = 50
n_steps = 500
Ey_history, Hz_history = fdtd.run(
n_steps,
source_func=gaussian_pulse,
source_pos=source_pos,
abc_type='mur',
record_interval=5
)
# ๊ฒฐ๊ณผ ์๊ฐํ
x = np.arange(fdtd.Nx) * fdtd.dx * 1000 # mm
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
# ์ค๋
์ท
snapshot_indices = [10, 30, 50, 70, 90]
for idx, snap_idx in enumerate(snapshot_indices):
if idx < 5:
ax = axes[idx // 3, idx % 3]
ax.plot(x, Ey_history[snap_idx], 'b-', linewidth=1.5)
# ์ ์ ์ฒด ์์ญ ํ์
ax.axvspan(150 * fdtd.dx * 1000, 200 * fdtd.dx * 1000,
alpha=0.2, color='green', label=r'$\epsilon_r=4$')
ax.axvline(x=source_pos * fdtd.dx * 1000, color='red',
linestyle='--', alpha=0.5)
ax.set_xlabel('x [mm]')
ax.set_ylabel('Ey')
ax.set_title(f'Step {snap_idx * 5}')
ax.grid(True, alpha=0.3)
ax.set_ylim(-1.2, 1.2)
# ๋ง์ง๋ง subplot: ์๊ณต๊ฐ ๋ค์ด์ด๊ทธ๋จ
ax = axes[1, 2]
t = np.arange(len(Ey_history)) * 5 * fdtd.dt * 1e9 # ns
im = ax.pcolormesh(x, t, Ey_history, cmap='RdBu_r', shading='auto',
vmin=-0.5, vmax=0.5)
ax.axvline(x=150 * fdtd.dx * 1000, color='green', linestyle='-', linewidth=2)
ax.axvline(x=200 * fdtd.dx * 1000, color='green', linestyle='-', linewidth=2)
ax.set_xlabel('x [mm]')
ax.set_ylabel('t [ns]')
ax.set_title('์๊ณต๊ฐ ๋ค์ด์ด๊ทธ๋จ')
plt.colorbar(im, ax=ax, label='Ey')
plt.suptitle('1D FDTD: ์ ์ ์ฒด ์ฌ๋๋ธ์์์ ๋ฐ์ฌ์ ํฌ๊ณผ', fontsize=14)
plt.tight_layout()
plt.savefig('fdtd_1d_dielectric.png', dpi=150, bbox_inches='tight')
plt.show()
return fdtd, Ey_history
# fdtd, Ey_history = demo_1d_fdtd_basic()
2. ์์ค ์ฌ๊ธฐ ๋ฐฉ๋ฒ¶
2.1 ํ๋ ์์ค vs ์ํํธ ์์ค¶
์์ค ์ ํ:
1. ํ๋ ์์ค (Hard Source):
Ey[source_pos] = source_value
- ํด๋น ์ ์ ํ๋ ๊ฐ์ ์ค์
- ๋ฐ์ฌํ๊ฐ ์์ค์์ ๋ค์ ๋ฐ์ฌ๋จ
- ๊ฐ๋จํ์ง๋ง ๋น๋ฌผ๋ฆฌ์ ๋ฐ์ฌ ๋ฐ์
2. ์ํํธ ์์ค (Soft Source):
Ey[source_pos] += source_value
- ๊ธฐ์กด ํ๋์ ์์ค ์ถ๊ฐ
- ๋ฐ์ฌํ๊ฐ ์์ค๋ฅผ ํต๊ณผ
- TF/SF ๊ฒฝ๊ณ์ ํจ๊ป ์ฌ์ฉ
3. TF/SF (Total-Field/Scattered-Field):
- ์
์ฌํ์ ์ฐ๋ํ ๋ถ๋ฆฌ
- ์ ํํ ํ๋ฉดํ ์ฃผ์
- ์ถ๊ฐ ๋ณด์ ํญ ํ์
def source_comparison():
"""ํ๋ ์์ค์ ์ํํธ ์์ค ๋น๊ต"""
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
for row, source_type in enumerate(['hard', 'soft']):
# ์๋ฎฌ๋ ์ดํฐ ์์ฑ
fdtd = FDTD_1D(Nx=200, dx=1e-3, courant=0.99)
fdtd.compute_coefficients()
source_pos = 50
n_steps = 300
# ๋ฐ์ฌ์ฒด (PEC) ์ถ๊ฐ
fdtd.sigma[150:155] = 1e7
Ey_history = []
for n in range(n_steps):
fdtd.update_H()
t = n * fdtd.dt
source = gaussian_pulse(t, t0=5e-11, tau=2e-11)
if source_type == 'hard':
fdtd.Ey[source_pos] = source
else:
fdtd.Ey[source_pos] += source
fdtd.update_E()
fdtd.apply_abc_mur()
if n % 3 == 0:
Ey_history.append(fdtd.Ey.copy())
x = np.arange(fdtd.Nx) * fdtd.dx * 1000
# ์ค๋
์ท
for col, snap_idx in enumerate([20, 50, 80]):
ax = axes[row, col]
ax.plot(x, Ey_history[snap_idx], 'b-', linewidth=1.5)
ax.axvline(x=source_pos * fdtd.dx * 1000, color='red', linestyle='--',
label='Source')
ax.axvspan(150 * fdtd.dx * 1000, 155 * fdtd.dx * 1000,
alpha=0.3, color='gray', label='PEC')
ax.set_xlabel('x [mm]')
ax.set_ylabel('Ey')
ax.set_title(f'{source_type.capitalize()} Source, Step {snap_idx * 3}')
ax.grid(True, alpha=0.3)
ax.set_ylim(-1.5, 1.5)
if col == 0:
ax.legend()
plt.suptitle('ํ๋ ์์ค vs ์ํํธ ์์ค ๋น๊ต', fontsize=14)
plt.tight_layout()
plt.savefig('fdtd_source_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
# source_comparison()
2.2 ๋ค์ํ ์์ค ํํ¶
def source_waveforms():
"""๋ค์ํ ์์ค ํํ"""
t = np.linspace(0, 0.5e-9, 1000) # 0.5 ns
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# (1) ๊ฐ์ฐ์์ ํ์ค
ax1 = axes[0, 0]
t0, tau = 0.15e-9, 0.03e-9
pulse = np.exp(-((t - t0) / tau) ** 2)
ax1.plot(t * 1e9, pulse, 'b-', linewidth=2)
ax1.set_title('๊ฐ์ฐ์์ ํ์ค')
ax1.set_xlabel('t [ns]')
ax1.set_ylabel('Amplitude')
ax1.grid(True, alpha=0.3)
# (2) ๊ฐ์ฐ์์ ๋ฏธ๋ถ (Ricker wavelet)
ax2 = axes[0, 1]
ricker = -2 * (t - t0) / tau**2 * np.exp(-((t - t0) / tau) ** 2)
ax2.plot(t * 1e9, ricker, 'r-', linewidth=2)
ax2.set_title('๊ฐ์ฐ์์ ๋ฏธ๋ถ (Ricker Wavelet)')
ax2.set_xlabel('t [ns]')
ax2.set_ylabel('Amplitude')
ax2.grid(True, alpha=0.3)
# (3) ๋ณ์กฐ ์ ํํ
ax3 = axes[1, 0]
freq = 10e9 # 10 GHz
modulated = np.sin(2 * np.pi * freq * t) * np.exp(-((t - t0) / tau) ** 2)
ax3.plot(t * 1e9, modulated, 'g-', linewidth=1.5)
ax3.set_title('๋ณ์กฐ ์ ํํ (10 GHz)')
ax3.set_xlabel('t [ns]')
ax3.set_ylabel('Amplitude')
ax3.grid(True, alpha=0.3)
# (4) ์คํํธ๋ผ
ax4 = axes[1, 1]
from scipy.fft import fft, fftfreq
dt = t[1] - t[0]
freqs = fftfreq(len(t), dt)
positive = freqs > 0
for signal, label, color in [(pulse, 'Gaussian', 'b'),
(ricker, 'Ricker', 'r'),
(modulated, 'Modulated', 'g')]:
spectrum = np.abs(fft(signal))
ax4.plot(freqs[positive] * 1e-9, spectrum[positive] / max(spectrum[positive]),
linewidth=1.5, label=label, color=color)
ax4.set_xlim(0, 50)
ax4.set_xlabel('Frequency [GHz]')
ax4.set_ylabel('Normalized Amplitude')
ax4.set_title('์ฃผํ์ ์คํํธ๋ผ')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('fdtd_source_waveforms.png', dpi=150, bbox_inches='tight')
plt.show()
# source_waveforms()
3. ํก์ ๊ฒฝ๊ณ์กฐ๊ฑด (ABC)¶
3.1 Simple ABC¶
1์ฐจ Simple ABC:
1D ํ๋ ๋ฐฉ์ ์์ ํน์ฑ:
(โ/โt + c โ/โx) Ey = 0 (์ฐ์ธก ์งํํ)
(โ/โt - c โ/โx) Ey = 0 (์ข์ธก ์งํํ)
์ด์ฐํ (์ฐ์ธก ๊ฒฝ๊ณ, ์ข์ธก ์งํํ ํก์):
Ey^(n+1)[Nx-1] = Ey^n[Nx-2]
์ด๊ฒ์ S = cฮt/ฮx = 1 ์ผ ๋๋ง ์ ํ
S โ 1 ์ด๋ฉด ๋ฐ์ฌ ๋ฐ์
3.2 Mur ABC¶
Mur 1์ฐจ ABC (1981):
1D ํ๋ ๋ฐฉ์ ์์ ์ ํ์ฐจ๋ถ ๊ทผ์ฌ:
์ฐ์ธก ๊ฒฝ๊ณ (x = xmax):
(Ey^(n+1)[Nx-1] - Ey^n[Nx-2]) / (cฮt + ฮx) =
(Ey^n[Nx-1] - Ey^(n+1)[Nx-2]) / (cฮt - ฮx)
์ ๋ฆฌ:
Ey^(n+1)[Nx-1] = Ey^n[Nx-2] +
(cฮt - ฮx)/(cฮt + ฮx) * (Ey^(n+1)[Nx-2] - Ey^n[Nx-1])
์ฅ์ :
- Simple ABC๋ณด๋ค ๋์ ์
์ฌ๊ฐ์์ ์ ํจ
- ๊ตฌํ ๊ฐ๋จ
ํ๊ณ:
- ์์ง ์
์ฌ๋ง ์ ํํ ํก์
- ๋น์ค๋ฌํ ์
์ฌ์์ ๋ฐ์ฌ ๋ฐ์
def abc_comparison():
"""ํก์ ๊ฒฝ๊ณ์กฐ๊ฑด ๋น๊ต"""
abc_types = ['pec', 'simple', 'mur']
results = {}
for abc_type in abc_types:
fdtd = FDTD_1D(Nx=300, dx=1e-3, courant=0.99)
fdtd.compute_coefficients()
source_pos = 100
Ey_history = []
for n in range(400):
fdtd.update_H()
t = n * fdtd.dt
source = gaussian_pulse(t, t0=5e-11, tau=2e-11)
fdtd.Ey[source_pos] += source
fdtd.update_E()
if abc_type == 'pec':
fdtd.Ey[0] = 0
fdtd.Ey[-1] = 0
elif abc_type == 'simple':
fdtd.apply_abc_simple()
else:
fdtd.apply_abc_mur()
if n % 4 == 0:
Ey_history.append(fdtd.Ey.copy())
results[abc_type] = np.array(Ey_history)
# ์๊ฐํ
x = np.arange(300) * 1e-3 * 1000
fig, axes = plt.subplots(3, 3, figsize=(15, 12))
for row, abc_type in enumerate(abc_types):
Ey_history = results[abc_type]
for col, snap_idx in enumerate([20, 50, 80]):
ax = axes[row, col]
ax.plot(x, Ey_history[snap_idx], 'b-', linewidth=1.5)
ax.axvline(x=100, color='red', linestyle='--', alpha=0.5)
ax.set_ylim(-1.2, 1.2)
ax.grid(True, alpha=0.3)
if row == 2:
ax.set_xlabel('x [mm]')
if col == 0:
ax.set_ylabel(f'{abc_type.upper()}\nEy')
ax.set_title(f'Step {snap_idx * 4}')
plt.suptitle('ํก์ ๊ฒฝ๊ณ์กฐ๊ฑด ๋น๊ต: PEC vs Simple ABC vs Mur ABC', fontsize=14)
plt.tight_layout()
plt.savefig('fdtd_abc_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
# ๋ฐ์ฌ ๋ถ์
fig, ax = plt.subplots(figsize=(10, 5))
t = np.arange(len(results['pec'])) * 4 * 1e-3 / c0 * 1e9
# ํน์ ์์น์์์ ํ๋ ๊ธฐ๋ก (๋ฐ์ฌ ๊ฒ์ถ์ฉ)
probe_pos = 50
for abc_type, color in [('pec', 'red'), ('simple', 'blue'), ('mur', 'green')]:
field = results[abc_type][:, probe_pos]
ax.plot(t, field, color=color, linewidth=1.5, label=abc_type.upper())
ax.set_xlabel('t [ns]')
ax.set_ylabel('Ey at probe')
ax.set_title('๊ฒฝ๊ณ์์์ ๋ฐ์ฌ ๋น๊ต (probe at x = 50 mm)')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('fdtd_abc_reflection.png', dpi=150, bbox_inches='tight')
plt.show()
# abc_comparison()
4. 2D FDTD ๊ตฌํ¶
4.1 TM ๋ชจ๋ (Ez, Hx, Hy)¶
class FDTD_2D_TM:
"""2D FDTD TM ๋ชจ๋ ์๋ฎฌ๋ ์ดํฐ"""
def __init__(self, Nx=100, Ny=100, dx=1e-3, dy=1e-3, courant=0.99):
self.Nx = Nx
self.Ny = Ny
self.dx = dx
self.dy = dy
# Courant ์กฐ๊ฑด
self.dt = courant / (c0 * np.sqrt(1/dx**2 + 1/dy**2))
# ํ๋ ๋ฐฐ์ด
self.Ez = np.zeros((Ny, Nx))
self.Hx = np.zeros((Ny, Nx))
self.Hy = np.zeros((Ny, Nx))
# ๋ฌผ์ฑ์น ๋ฐฐ์ด
self.eps_r = np.ones((Ny, Nx))
self.mu_r = np.ones((Ny, Nx))
self.sigma = np.zeros((Ny, Nx))
# ๊ณ์
self.Ca = None
self.Cb = None
self.time_step = 0
print(f"2D FDTD TM ๋ชจ๋ ์ด๊ธฐํ:")
print(f" ๊ฒฉ์: {Nx} x {Ny}")
print(f" dx = {dx*1000:.2f} mm, dy = {dy*1000:.2f} mm")
print(f" dt = {self.dt*1e12:.3f} ps")
def set_material_region(self, x1, x2, y1, y2, eps_r=1, sigma=0):
"""์ฌ๋ฃ ์์ญ ์ค์ """
self.eps_r[y1:y2, x1:x2] = eps_r
self.sigma[y1:y2, x1:x2] = sigma
def add_pec_circle(self, cx, cy, radius):
"""์ํ PEC ์ถ๊ฐ"""
for j in range(self.Ny):
for i in range(self.Nx):
if (i - cx)**2 + (j - cy)**2 < radius**2:
self.sigma[j, i] = 1e7
def compute_coefficients(self):
"""์
๋ฐ์ดํธ ๊ณ์ ๊ณ์ฐ"""
eps = eps0 * self.eps_r
self.Ca = (1 - self.sigma * self.dt / (2 * eps)) / \
(1 + self.sigma * self.dt / (2 * eps))
self.Cb = (self.dt / eps) / (1 + self.sigma * self.dt / (2 * eps))
def update_H(self):
"""H ํ๋ ์
๋ฐ์ดํธ"""
# Hx ์
๋ฐ์ดํธ: Hx = Hx - dt/ฮผโ * dEz/dy
self.Hx[:, :-1] -= (self.dt / (mu0 * self.dy)) * \
(self.Ez[:, 1:] - self.Ez[:, :-1])
# Hy ์
๋ฐ์ดํธ: Hy = Hy + dt/ฮผโ * dEz/dx
self.Hy[:-1, :] += (self.dt / (mu0 * self.dx)) * \
(self.Ez[1:, :] - self.Ez[:-1, :])
def update_E(self):
"""E ํ๋ ์
๋ฐ์ดํธ"""
# Ez ์
๋ฐ์ดํธ: Ez = Ca*Ez + Cb*(dHy/dx - dHx/dy)
self.Ez[1:-1, 1:-1] = (
self.Ca[1:-1, 1:-1] * self.Ez[1:-1, 1:-1] +
self.Cb[1:-1, 1:-1] * (
(self.Hy[1:-1, 1:-1] - self.Hy[:-2, 1:-1]) / self.dx -
(self.Hx[1:-1, 1:-1] - self.Hx[1:-1, :-2]) / self.dy
)
)
def apply_pec_boundary(self):
"""PEC ๊ฒฝ๊ณ์กฐ๊ฑด"""
self.Ez[0, :] = 0
self.Ez[-1, :] = 0
self.Ez[:, 0] = 0
self.Ez[:, -1] = 0
def add_point_source(self, x, y, value):
"""์ ์์ค ์ถ๊ฐ"""
self.Ez[y, x] += value
def add_line_source(self, x, value):
"""์ ์์ค ์ถ๊ฐ (y ๋ฐฉํฅ ์ ์ฒด)"""
self.Ez[:, x] += value
def step(self, source_func=None, source_pos=None, source_type='point'):
"""ํ ์๊ฐ ๋จ๊ณ ์ ์ง"""
self.update_H()
if source_func is not None and source_pos is not None:
t = self.time_step * self.dt
value = source_func(t)
if source_type == 'point':
self.add_point_source(source_pos[0], source_pos[1], value)
elif source_type == 'line':
self.add_line_source(source_pos, value)
self.update_E()
self.apply_pec_boundary()
self.time_step += 1
def run(self, n_steps, source_func=None, source_pos=None,
source_type='point', record_interval=1):
"""์๋ฎฌ๋ ์ด์
์คํ"""
self.compute_coefficients()
Ez_history = []
for n in range(n_steps):
self.step(source_func, source_pos, source_type)
if n % record_interval == 0:
Ez_history.append(self.Ez.copy())
return np.array(Ez_history)
def demo_2d_fdtd_tm():
"""2D FDTD TM ๋ชจ๋ ์์ฐ"""
# ์๋ฎฌ๋ ์ดํฐ ์์ฑ
fdtd = FDTD_2D_TM(Nx=150, Ny=150, dx=1e-3, dy=1e-3, courant=0.9)
# ์ ์ ์ฒด ๋ธ๋ก ์ถ๊ฐ
fdtd.set_material_region(90, 120, 60, 90, eps_r=4)
# PEC ์ํ ์ค๋ฆฐ๋ ์ถ๊ฐ
fdtd.add_pec_circle(50, 75, 15)
# ์๋ฎฌ๋ ์ด์
์คํ
source_pos = (20, 75) # (x, y)
def source(t):
return gaussian_pulse(t, t0=1e-10, tau=3e-11)
Ez_history = fdtd.run(
n_steps=300,
source_func=source,
source_pos=source_pos,
source_type='point',
record_interval=5
)
# ์๊ฐํ
x = np.arange(fdtd.Nx) * fdtd.dx * 1000
y = np.arange(fdtd.Ny) * fdtd.dy * 1000
X, Y = np.meshgrid(x, y)
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
snapshot_indices = [5, 15, 25, 35, 45, 55]
for idx, snap_idx in enumerate(snapshot_indices):
ax = axes[idx // 3, idx % 3]
vmax = np.max(np.abs(Ez_history[snap_idx])) * 0.7
if vmax == 0:
vmax = 1
im = ax.pcolormesh(X, Y, Ez_history[snap_idx], cmap='RdBu_r',
shading='auto', vmin=-vmax, vmax=vmax)
# ์ฌ๋ฃ ๊ฒฝ๊ณ ํ์
ax.contour(X, Y, fdtd.eps_r, levels=[2], colors='green', linewidths=2)
ax.contour(X, Y, fdtd.sigma, levels=[1e6], colors='black', linewidths=2)
# ์์ค ์์น
ax.plot(source_pos[0] * fdtd.dx * 1000,
source_pos[1] * fdtd.dy * 1000, 'r*', markersize=10)
ax.set_xlabel('x [mm]')
ax.set_ylabel('y [mm]')
ax.set_title(f'Step {snap_idx * 5}')
ax.set_aspect('equal')
plt.colorbar(im, ax=ax, label='Ez')
plt.suptitle('2D FDTD TM ๋ชจ๋: ์ฐ๋ ์๋ฎฌ๋ ์ด์
', fontsize=14)
plt.tight_layout()
plt.savefig('fdtd_2d_tm_scattering.png', dpi=150, bbox_inches='tight')
plt.show()
return fdtd, Ez_history
# fdtd, Ez_history = demo_2d_fdtd_tm()
4.2 TE ๋ชจ๋ (Hz, Ex, Ey)¶
class FDTD_2D_TE:
"""2D FDTD TE ๋ชจ๋ ์๋ฎฌ๋ ์ดํฐ"""
def __init__(self, Nx=100, Ny=100, dx=1e-3, dy=1e-3, courant=0.99):
self.Nx = Nx
self.Ny = Ny
self.dx = dx
self.dy = dy
self.dt = courant / (c0 * np.sqrt(1/dx**2 + 1/dy**2))
# ํ๋ ๋ฐฐ์ด
self.Hz = np.zeros((Ny, Nx))
self.Ex = np.zeros((Ny, Nx))
self.Ey = np.zeros((Ny, Nx))
# ๋ฌผ์ฑ์น
self.eps_r = np.ones((Ny, Nx))
self.sigma = np.zeros((Ny, Nx))
self.time_step = 0
def compute_coefficients(self):
eps = eps0 * self.eps_r
self.Ca = (1 - self.sigma * self.dt / (2 * eps)) / \
(1 + self.sigma * self.dt / (2 * eps))
self.Cb = (self.dt / eps) / (1 + self.sigma * self.dt / (2 * eps))
def update_E(self):
"""E ํ๋ ์
๋ฐ์ดํธ"""
# Ex = Ca*Ex + Cb * dHz/dy
self.Ex[1:-1, :] = (
self.Ca[1:-1, :] * self.Ex[1:-1, :] +
self.Cb[1:-1, :] * (self.Hz[1:, :] - self.Hz[:-2, :]) / (2 * self.dy)
)
# Ey = Ca*Ey - Cb * dHz/dx
self.Ey[:, 1:-1] = (
self.Ca[:, 1:-1] * self.Ey[:, 1:-1] -
self.Cb[:, 1:-1] * (self.Hz[:, 2:] - self.Hz[:, :-2]) / (2 * self.dx)
)
def update_H(self):
"""H ํ๋ ์
๋ฐ์ดํธ"""
# Hz = Hz + dt/ฮผโ * (dEx/dy - dEy/dx)
self.Hz[1:-1, 1:-1] += (self.dt / mu0) * (
(self.Ex[2:, 1:-1] - self.Ex[:-2, 1:-1]) / (2 * self.dy) -
(self.Ey[1:-1, 2:] - self.Ey[1:-1, :-2]) / (2 * self.dx)
)
def step(self, source_func=None, source_pos=None):
self.update_E()
if source_func is not None and source_pos is not None:
t = self.time_step * self.dt
self.Hz[source_pos[1], source_pos[0]] += source_func(t)
self.update_H()
# PEC ๊ฒฝ๊ณ
self.Ex[0, :] = self.Ex[-1, :] = 0
self.Ey[:, 0] = self.Ey[:, -1] = 0
self.time_step += 1
5. PML (Perfectly Matched Layer)¶
5.1 PML ๊ฐ๋ ¶
PML (Berenger, 1994):
ํต์ฌ ์์ด๋์ด:
- ํ๋์ ํก์ํ๋ ์ธ๊ณต ๋งค์ง์ธต
- ๋งค์ง ๊ฒฝ๊ณ์์ ๋ฐ์ฌ ์์ (์ํผ๋์ค ์ ํฉ)
- ์ธต ๋ด๋ถ์์ ์ง์์ ๊ฐ์
๊ตฌํ ๋ฐฉ์:
1. Split-field PML: ํ๋๋ฅผ ๋ ์ฑ๋ถ์ผ๋ก ๋ถํ
2. UPML (Uniaxial PML): ์ด๋ฐฉ์ฑ ๋งค์ง ํํ
3. CPML (Convolutional PML): ์ปจ๋ณผ๋ฃจ์
ํํ
PML ๋งค๊ฐ๋ณ์:
- ์ธต ๋๊ป: ๋ณดํต 8-20 ์
- ๊ฐ์ ํ๋กํ์ผ: ๋คํญ์ ๋๋ ์ง์ํจ์
- ์ต๋ ์ ๋๋: ์ต์ ๊ฐ ์กด์ฌ
class FDTD_2D_TM_PML:
"""2D FDTD TM ๋ชจ๋ with UPML"""
def __init__(self, Nx=100, Ny=100, dx=1e-3, dy=1e-3, pml_layers=10, courant=0.9):
self.Nx = Nx
self.Ny = Ny
self.dx = dx
self.dy = dy
self.pml_layers = pml_layers
self.dt = courant / (c0 * np.sqrt(1/dx**2 + 1/dy**2))
# ํ๋ ๋ฐฐ์ด
self.Ez = np.zeros((Ny, Nx))
self.Hx = np.zeros((Ny, Nx))
self.Hy = np.zeros((Ny, Nx))
# PML ๋ณด์กฐ ํ๋
self.Psi_Ez_x = np.zeros((Ny, Nx))
self.Psi_Ez_y = np.zeros((Ny, Nx))
self.Psi_Hx_y = np.zeros((Ny, Nx))
self.Psi_Hy_x = np.zeros((Ny, Nx))
# ๋ฌผ์ฑ์น
self.eps_r = np.ones((Ny, Nx))
self.sigma = np.zeros((Ny, Nx))
# PML ๊ณ์ ์ด๊ธฐํ
self._setup_pml()
self.time_step = 0
print(f"2D FDTD TM + PML ์ด๊ธฐํ:")
print(f" ๊ฒฉ์: {Nx} x {Ny}, PML: {pml_layers} layers")
def _pml_profile(self, d, d_max, sigma_max, order=3):
"""PML ์ ๋๋ ํ๋กํ์ผ"""
return sigma_max * (d / d_max) ** order
def _setup_pml(self):
"""PML ๊ณ์ ์ค์ """
# ์ต์ ์ ๋๋
sigma_max = 0.8 * (order + 1) / (eta0 * self.dx) if hasattr(self, 'order') else \
0.8 * 4 / (eta0 * self.dx)
order = 3
# x ๋ฐฉํฅ PML ๊ณ์
self.sigma_x = np.zeros(self.Nx)
self.sigma_x_star = np.zeros(self.Nx) # dual grid
for i in range(self.pml_layers):
# ์ข์ธก PML
d = self.pml_layers - i
self.sigma_x[i] = self._pml_profile(d, self.pml_layers, sigma_max, order)
self.sigma_x_star[i] = self._pml_profile(d - 0.5, self.pml_layers, sigma_max, order)
# ์ฐ์ธก PML
d = i + 1
self.sigma_x[-(i+1)] = self._pml_profile(d, self.pml_layers, sigma_max, order)
self.sigma_x_star[-(i+1)] = self._pml_profile(d - 0.5, self.pml_layers, sigma_max, order)
# y ๋ฐฉํฅ PML ๊ณ์
self.sigma_y = np.zeros(self.Ny)
self.sigma_y_star = np.zeros(self.Ny)
for j in range(self.pml_layers):
# ํ๋จ PML
d = self.pml_layers - j
self.sigma_y[j] = self._pml_profile(d, self.pml_layers, sigma_max, order)
self.sigma_y_star[j] = self._pml_profile(d - 0.5, self.pml_layers, sigma_max, order)
# ์๋จ PML
d = j + 1
self.sigma_y[-(j+1)] = self._pml_profile(d, self.pml_layers, sigma_max, order)
self.sigma_y_star[-(j+1)] = self._pml_profile(d - 0.5, self.pml_layers, sigma_max, order)
# ๊ณ์ ๊ณ์ฐ
self.b_x = np.exp(-self.sigma_x * self.dt / eps0)
self.c_x = (1 - self.b_x) / (self.sigma_x * self.dx + 1e-10)
self.b_x_star = np.exp(-self.sigma_x_star * self.dt / eps0)
self.c_x_star = (1 - self.b_x_star) / (self.sigma_x_star * self.dx + 1e-10)
self.b_y = np.exp(-self.sigma_y * self.dt / eps0)
self.c_y = (1 - self.b_y) / (self.sigma_y * self.dy + 1e-10)
self.b_y_star = np.exp(-self.sigma_y_star * self.dt / eps0)
self.c_y_star = (1 - self.b_y_star) / (self.sigma_y_star * self.dy + 1e-10)
def update_H(self):
"""H ํ๋ ์
๋ฐ์ดํธ (PML ํฌํจ)"""
# Hx ์
๋ฐ์ดํธ
dEz_dy = (self.Ez[:, 1:] - self.Ez[:, :-1]) / self.dy
self.Psi_Hx_y[:, :-1] = (self.b_y[:, np.newaxis] * self.Psi_Hx_y[:, :-1] +
self.c_y[:, np.newaxis] * dEz_dy)
self.Hx[:, :-1] -= self.dt / mu0 * (dEz_dy + self.Psi_Hx_y[:, :-1])
# Hy ์
๋ฐ์ดํธ
dEz_dx = (self.Ez[1:, :] - self.Ez[:-1, :]) / self.dx
self.Psi_Hy_x[:-1, :] = (self.b_x[np.newaxis, :] * self.Psi_Hy_x[:-1, :] +
self.c_x[np.newaxis, :] * dEz_dx)
self.Hy[:-1, :] += self.dt / mu0 * (dEz_dx + self.Psi_Hy_x[:-1, :])
def update_E(self):
"""E ํ๋ ์
๋ฐ์ดํธ (PML ํฌํจ)"""
eps = eps0 * self.eps_r
dHy_dx = (self.Hy[1:-1, 1:-1] - self.Hy[:-2, 1:-1]) / self.dx
dHx_dy = (self.Hx[1:-1, 1:-1] - self.Hx[1:-1, :-2]) / self.dy
self.Psi_Ez_x[1:-1, 1:-1] = (self.b_x_star[np.newaxis, 1:-1] * self.Psi_Ez_x[1:-1, 1:-1] +
self.c_x_star[np.newaxis, 1:-1] * dHy_dx)
self.Psi_Ez_y[1:-1, 1:-1] = (self.b_y_star[1:-1, np.newaxis] * self.Psi_Ez_y[1:-1, 1:-1] +
self.c_y_star[1:-1, np.newaxis] * dHx_dy)
self.Ez[1:-1, 1:-1] += self.dt / eps[1:-1, 1:-1] * (
dHy_dx + self.Psi_Ez_x[1:-1, 1:-1] -
dHx_dy - self.Psi_Ez_y[1:-1, 1:-1]
)
def step(self, source_func=None, source_pos=None):
self.update_H()
if source_func is not None and source_pos is not None:
t = self.time_step * self.dt
self.Ez[source_pos[1], source_pos[0]] += source_func(t)
self.update_E()
# PEC ์ธ๋ถ ๊ฒฝ๊ณ
self.Ez[0, :] = self.Ez[-1, :] = 0
self.Ez[:, 0] = self.Ez[:, -1] = 0
self.time_step += 1
def run(self, n_steps, source_func=None, source_pos=None, record_interval=1):
Ez_history = []
for n in range(n_steps):
self.step(source_func, source_pos)
if n % record_interval == 0:
Ez_history.append(self.Ez.copy())
return np.array(Ez_history)
def demo_pml():
"""PML ์์ฐ"""
# PML ์๋ ๊ฒฝ์ฐ
fdtd_no_pml = FDTD_2D_TM(Nx=100, Ny=100, dx=1e-3, dy=1e-3, courant=0.9)
fdtd_no_pml.compute_coefficients()
# PML ์๋ ๊ฒฝ์ฐ
fdtd_pml = FDTD_2D_TM_PML(Nx=100, Ny=100, dx=1e-3, dy=1e-3, pml_layers=10, courant=0.9)
source_pos = (50, 50)
def source(t):
return gaussian_pulse(t, t0=0.15e-9, tau=0.05e-9)
n_steps = 200
# ์คํ
Ez_no_pml = fdtd_no_pml.run(n_steps, source, source_pos, record_interval=10)
Ez_pml = fdtd_pml.run(n_steps, source, source_pos, record_interval=10)
# ๋น๊ต ์๊ฐํ
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
x = np.arange(100) * 1e-3 * 1000
y = np.arange(100) * 1e-3 * 1000
X, Y = np.meshgrid(x, y)
for col, snap_idx in enumerate([5, 10, 15, 19]):
# PEC ๊ฒฝ๊ณ (๋ฐ์ฌ)
ax = axes[0, col]
vmax = 0.3
ax.pcolormesh(X, Y, Ez_no_pml[snap_idx], cmap='RdBu_r',
shading='auto', vmin=-vmax, vmax=vmax)
ax.set_title(f'PEC ๊ฒฝ๊ณ, Step {snap_idx * 10}')
ax.set_aspect('equal')
if col == 0:
ax.set_ylabel('Without PML\ny [mm]')
# PML ๊ฒฝ๊ณ (ํก์)
ax = axes[1, col]
ax.pcolormesh(X, Y, Ez_pml[snap_idx], cmap='RdBu_r',
shading='auto', vmin=-vmax, vmax=vmax)
# PML ์์ญ ํ์
pml = 10
ax.axvline(x=pml, color='green', linestyle='--', alpha=0.5)
ax.axvline(x=100-pml, color='green', linestyle='--', alpha=0.5)
ax.axhline(y=pml, color='green', linestyle='--', alpha=0.5)
ax.axhline(y=100-pml, color='green', linestyle='--', alpha=0.5)
ax.set_title(f'PML ๊ฒฝ๊ณ, Step {snap_idx * 10}')
ax.set_aspect('equal')
if col == 0:
ax.set_ylabel('With PML\ny [mm]')
ax.set_xlabel('x [mm]')
plt.suptitle('PEC vs PML ๊ฒฝ๊ณ์กฐ๊ฑด ๋น๊ต', fontsize=14)
plt.tight_layout()
plt.savefig('fdtd_pml_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
# demo_pml()
6. ์์ฉ ์์ : ๋ํ๊ด¶
6.1 ์ง์ฌ๊ฐํ ๋ํ๊ด ์๋ฎฌ๋ ์ด์ ¶
def rectangular_waveguide():
"""์ง์ฌ๊ฐํ ๋ํ๊ด ์๋ฎฌ๋ ์ด์
"""
# ๋ํ๊ด ์น์ (WR-90: a=22.86mm, b=10.16mm)
# ๊ฐ์ํ๋ ์น์ ์ฌ์ฉ
a = 30 # ๋ํ๊ด ํญ (์
์)
b = 15 # ๋ํ๊ด ๋์ด (์
์)
Nx = 150
Ny = 40
dx = dy = 1e-3 # 1 mm
fdtd = FDTD_2D_TM(Nx=Nx, Ny=Ny, dx=dx, dy=dy, courant=0.9)
# ๋ํ๊ด ๋ฒฝ (PEC)
wall_y1 = (Ny - b) // 2
wall_y2 = wall_y1 + b
# ์๋จ/ํ๋จ PEC ๋ฒฝ
fdtd.sigma[:wall_y1, :] = 1e7
fdtd.sigma[wall_y2:, :] = 1e7
fdtd.compute_coefficients()
# TE10 ๋ชจ๋ ์ฌ๊ธฐ ์ฃผํ์
f_c = c0 / (2 * a * dx) # ์ฐจ๋จ ์ฃผํ์
f_op = 1.5 * f_c # ์ด์ฉ ์ฃผํ์
print(f"์ฐจ๋จ ์ฃผํ์: {f_c/1e9:.2f} GHz")
print(f"์ด์ฉ ์ฃผํ์: {f_op/1e9:.2f} GHz")
# ์์ค (๋ํ๊ด ๋ด๋ถ)
source_x = 10
source_y = Ny // 2
def source(t):
t0 = 0.2e-9
tau = 0.05e-9
return np.sin(2 * np.pi * f_op * t) * (1 - np.exp(-((t - t0)/tau)**2) if t < t0 else 1)
# ์๋ฎฌ๋ ์ด์
n_steps = 500
Ez_history = []
for n in range(n_steps):
fdtd.step(source, (source_x, source_y))
if n % 5 == 0:
Ez_history.append(fdtd.Ez.copy())
Ez_history = np.array(Ez_history)
# ์๊ฐํ
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
x = np.arange(Nx) * dx * 1000
y = np.arange(Ny) * dy * 1000
X, Y = np.meshgrid(x, y)
for idx, snap_idx in enumerate([10, 30, 50, 70, 90]):
ax = axes[idx // 3, idx % 3]
vmax = np.max(np.abs(Ez_history[snap_idx])) * 0.5
if vmax == 0:
vmax = 1
im = ax.pcolormesh(X, Y, Ez_history[snap_idx], cmap='RdBu_r',
shading='auto', vmin=-vmax, vmax=vmax)
# ๋ํ๊ด ๋ฒฝ ํ์
ax.axhline(y=wall_y1 * dy * 1000, color='black', linewidth=2)
ax.axhline(y=wall_y2 * dy * 1000, color='black', linewidth=2)
ax.set_xlabel('x [mm]')
ax.set_ylabel('y [mm]')
ax.set_title(f'Step {snap_idx * 5}')
plt.colorbar(im, ax=ax)
# ํํ ๋ถ์
ax = axes[1, 2]
probe_y = Ny // 2
probe_x_list = [30, 60, 90, 120]
for px in probe_x_list:
signal = Ez_history[:, probe_y, px]
t = np.arange(len(signal)) * 5 * fdtd.dt * 1e9
ax.plot(t, signal, label=f'x={px*dx*1000:.0f}mm')
ax.set_xlabel('t [ns]')
ax.set_ylabel('Ez')
ax.set_title('๋ค์ํ ์์น์์์ ํ๋')
ax.legend()
ax.grid(True, alpha=0.3)
plt.suptitle(f'์ง์ฌ๊ฐํ ๋ํ๊ด (f_op = {f_op/1e9:.1f} GHz > f_c = {f_c/1e9:.1f} GHz)', fontsize=14)
plt.tight_layout()
plt.savefig('fdtd_waveguide.png', dpi=150, bbox_inches='tight')
plt.show()
# rectangular_waveguide()
7. ์ฐ์ต ๋ฌธ์ ¶
์ฐ์ต 1: ์์ค ๋น๊ต¶
๊ฐ์ฐ์์ ํ์ค์ Ricker wavelet์ ์์ค๋ก ์ฌ์ฉํ ๋ 1D FDTD ๊ฒฐ๊ณผ๋ฅผ ๋น๊ตํ์์ค. ์ฃผํ์ ์๋ต ํน์ฑ์ ๋ถ์ํ์์ค.
์ฐ์ต 2: ABC ์ฑ๋ฅ¶
1์ฐจ Mur ABC์ 2์ฐจ Mur ABC์ ๋ฐ์ฌ ๊ณ์๋ฅผ ๋น๊ตํ์์ค. ์ ์ฌ๊ฐ์ ๋ฐ๋ฅธ ์ฑ๋ฅ์ ๋ถ์ํ์์ค.
์ฐ์ต 3: PML ์ต์ ํ¶
PML ์ธต ๋๊ป(5, 10, 15, 20)์ ๋คํญ์ ์ฐจ์(2, 3, 4)์ ๋ฐ๋ฅธ ํก์ ์ฑ๋ฅ์ ๋น๊ตํ์์ค.
์ฐ์ต 4: ๋ํ๊ด ๋ชจ๋¶
TE10 ์ฐจ๋จ ์ฃผํ์ ์ดํ์ ์ด์์์์ ๋ํ๊ด ์ ํ๋ฅผ ์๋ฎฌ๋ ์ด์ ํ๊ณ ์ฐจ์ด๋ฅผ ๋ถ์ํ์์ค.
8. ์ฐธ๊ณ ์๋ฃ¶
ํต์ฌ ๋ ผ๋ฌธ¶
- Yee (1966) "Numerical Solution of Initial Boundary Value Problems..."
- Mur (1981) "Absorbing Boundary Conditions for the Finite-Difference Approximation..."
- Berenger (1994) "A Perfectly Matched Layer for the Absorption of Electromagnetic Waves"
๊ต์ฌ¶
- Taflove & Hagness, "Computational Electrodynamics: The FDTD Method"
- Sullivan, "Electromagnetic Simulation Using the FDTD Method"
์คํ์์ค¶
- MEEP (MIT): Python/C++ FDTD
- gprMax: Ground Penetrating Radar FDTD
- OpenEMS: FDTD + ํ๋ก ์๋ฎฌ๋ ์ด์
์์ฝ¶
FDTD ๊ตฌํ ํต์ฌ:
1. ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐ:
- H ์
๋ฐ์ดํธ โ ์์ค ์ฃผ์
โ E ์
๋ฐ์ดํธ โ ABC
2. ์์ค ์ ํ:
- Hard: ๊ฐ์ ์ค์ (๋ฐ์ฌ ๋ฐ์)
- Soft: ์ถ๊ฐ (+= ) (๋ฐ์ฌํ ํต๊ณผ)
- TF/SF: ์ ํํ ํ๋ฉดํ
3. ํก์ ๊ฒฝ๊ณ์กฐ๊ฑด:
- Simple ABC: ๊ฐ์ฅ ๋จ์, S=1์์๋ง ์ ํ
- Mur ABC: ๊ฐ์ ๋ ์ฑ๋ฅ, ์์ง ์
์ฌ์ ํจ๊ณผ์
- PML: ์ต๊ณ ์ฑ๋ฅ, ๊ตฌํ ๋ณต์ก
4. 2D ๋ชจ๋:
- TM: Ez, Hx, Hy (z ํธ๊ด)
- TE: Hz, Ex, Ey (z ํธ๊ด)
5. PML ์์:
- ์ธต ๋๊ป: 8-20 ์
- ๋คํญ์ ํ๋กํ์ผ (order 3-4)
- CPML์ด ๊ฐ์ฅ ํจ๊ณผ์
6. ์์น์ ๊ณ ๋ ค:
- Courant ์กฐ๊ฑด ์ค์
- ํ์ฅ๋น 10-20 ์
- ์์น ๋ถ์ฐ ์ต์ํ
๋ค์ ๋ ์จ์์๋ MHD (์๊ธฐ์ ์ฒด์ญํ)์ ๊ธฐ์ด ์ด๋ก ์ ๋ค๋ฃน๋๋ค.