| 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 | |