BPM / bpm /mode_solver.py
jwt625's picture
init
fa34304
import numpy as np
import warnings
def slab_mode_source(x, w, n_WG, n0, wavelength, ind_m=0, x0=0):
"""
Returns the normalized TE mode profile for a symmetric slab waveguide with a lateral shift x0.
"""
k0 = 2 * np.pi / wavelength
def f_even(beta):
if beta < n0*k0 or beta > n_WG*k0:
return None
inside = n_WG**2 * k0**2 - beta**2
outside = beta**2 - n0**2 * k0**2
if inside <= 0 or outside <= 0:
return None
kx = np.sqrt(inside)
kappa = np.sqrt(outside)
return kx * np.tan(kx * w / 2) - kappa
def f_odd(beta):
if beta < n0*k0 or beta > n_WG*k0:
return None
inside = n_WG**2 * k0**2 - beta**2
outside = beta**2 - n0**2 * k0**2
if inside <= 0 or outside <= 0:
return None
kx = np.sqrt(inside)
kappa = np.sqrt(outside)
sin_term = np.sin(kx * w / 2)
if abs(sin_term) < 1e-12:
return None
return - kx * (np.cos(kx * w / 2) / sin_term) - kappa
def valid_even(beta):
inside = n_WG**2 * k0**2 - beta**2
if inside <= 0:
return False
kx = np.sqrt(inside)
theta = kx * w / 2
m = int(np.floor(2 * theta / np.pi))
if m % 2 == 0:
if m == 0 and theta > (np.pi/2 - 0.1):
return False
return True
return False
def valid_odd(beta):
inside = n_WG**2 * k0**2 - beta**2
if inside <= 0:
return False
kx = np.sqrt(inside)
theta = kx * w / 2
m = int(np.floor(2 * theta / np.pi))
return (m % 2 == 1)
N = 2000
beta_scan = np.linspace(n0*k0, n_WG*k0, N)
even_intervals = []
odd_intervals = []
f_even_vals = [f_even(b) for b in beta_scan]
f_odd_vals = [f_odd(b) for b in beta_scan]
for i in range(N-1):
if (f_even_vals[i] is not None) and (f_even_vals[i+1] is not None):
if f_even_vals[i] * f_even_vals[i+1] < 0:
even_intervals.append((beta_scan[i], beta_scan[i+1]))
if (f_odd_vals[i] is not None) and (f_odd_vals[i+1] is not None):
if f_odd_vals[i] * f_odd_vals[i+1] < 0:
odd_intervals.append((beta_scan[i], beta_scan[i+1]))
def refine_root(f, b_left, b_right):
for _ in range(50):
b_mid = 0.5*(b_left+b_right)
val_mid = f(b_mid)
if val_mid is None:
b_right = b_mid
continue
if abs(val_mid) < 1e-9:
return b_mid
val_left = f(b_left)
if val_left is None or val_left*val_mid > 0:
b_left = b_mid
else:
b_right = b_mid
return b_mid
even_roots = []
for (b_left, b_right) in even_intervals:
root = refine_root(f_even, b_left, b_right)
if valid_even(root):
even_roots.append(root)
odd_roots = []
for (b_left, b_right) in odd_intervals:
root = refine_root(f_odd, b_left, b_right)
if valid_odd(root):
odd_roots.append(root)
modes = [("even", r) for r in even_roots] + [("odd", r) for r in odd_roots]
modes_sorted = sorted(modes, key=lambda tup: tup[1], reverse=True)
if len(modes_sorted) == 0:
raise ValueError("No guided slab modes found in [n0*k0, n_WG*k0].")
if ind_m >= len(modes_sorted):
warnings.warn(
f"Requested mode index {ind_m} >= found modes ({len(modes_sorted)}). Using highest mode index {len(modes_sorted)-1}.",
UserWarning
)
ind_m = len(modes_sorted) - 1
parity, beta_chosen = modes_sorted[ind_m]
inside = n_WG**2 * k0**2 - beta_chosen**2
outside = beta_chosen**2 - n0**2 * k0**2
kx = np.sqrt(inside)
kappa = np.sqrt(outside)
E = np.zeros_like(x, dtype=np.complex128)
if parity == "even":
for i, xi in enumerate(x):
xp = xi - x0
if abs(xp) <= w/2:
E[i] = np.cos(kx * xp)
else:
E[i] = np.cos(kx * (w/2)) * np.exp(-kappa * (abs(xp)-w/2))
else:
for i, xi in enumerate(x):
xp = xi - x0
if abs(xp) <= w/2:
E[i] = np.sin(kx * xp)
else:
E[i] = np.sign(xp) * np.sin(kx * (w/2)) * np.exp(-kappa * (abs(xp)-w/2))
norm = np.sqrt(np.trapz(np.abs(E)**2, x))
E /= norm
return E