├── README.md ├── class ├── fdfd_blochX_modes.m ├── fdfd_main.m ├── fdfd_mf_solve.m ├── fdfd_modes.m ├── fdfd_solve.m └── fdfd_wg_modes.m ├── examples ├── MIM_slot_waveguide.m ├── SOI_slot_waveguide.m ├── circ_scatterer.m ├── guided_resonance.m ├── heart_resonator.m ├── photonic_transition_TE.m ├── photonic_transition_TM.m ├── ring_resonator.m └── triangle_resonator.m ├── flux ├── poyntingTE.m └── poyntingTM.m ├── helper ├── S_create.m ├── assign_blocks.m ├── assign_val.m ├── bwdmean_w.m ├── createDws.m └── create_sfactor.m ├── solver ├── solveTE.m ├── solveTE_BlochX.m ├── solveTE_mf.m ├── solveTE_modes.m ├── solveTM.m ├── solveTM_BlochX.m ├── solveTM_mf.m ├── solveTM_modes.m ├── solve_wg_modes.m └── solve_wg_modes_1D.m └── vis ├── b2r.m ├── movie_real.m ├── vis_abs.m └── vis_real.m /README.md: -------------------------------------------------------------------------------- 1 | # fdfd_suite 2 | 3 | 4 | 5 | # Contents 6 | This package contains a comprehensive list of two-dimensional finite-difference frequency-domain (FDFD) programs used to simulate how electromagnetic waves interact with various dielectric/metal geometries. 7 | 8 | It can efficiently calculate the field patterns in both TE and TM polarizations in the following simulations: 9 | * **Scattering:** Compute how electromagnetic waves scatter off of a dielectric/metal object 10 | * **Resonator mode:** Compute the natural resonating modes and frequencies of a 2D dielectric geometry 11 | * **Photonic crystal:** Compute the modes supported by a periodic structure 12 | * **Modulator:** Compute the steady-state field profile of an active device whose refractive index is modulated in time 13 | * **Waveguide mode:** Compute the modal profile of a waveguide given its 2D cross section geometry 14 | 15 | This repository contains the following folders: 16 | 17 | * **class:** This folder contains the class definitions for each FDFD simulation. These classes are used to instantiate a particular simulation. 18 | 19 | * **examples:** This folder contains a list of examples that set up various types of FDFD simulations. The files can be used as a template for starting your own simulation. 20 | 21 | * **flux:** This folder contains the programs for calculating the Poynting flux given a field distribution 22 | 23 | * **helper:** This folder contains the helper functions in the FDFD solvers, including assigning permittivity distribution, calculating various finite-difference operators, and so on. 24 | 25 | * **solver:** This folder contains the implementations of the FDFD algorithm 26 | 27 | * **vis:** This folder contains the visualization programs used for plotting the permittivity distribution, field patterns, and making a movie out of the field patterns. 28 | 29 | # How to set up a basic simulation 30 | The easiest way to learn how to use this package is to take a look at the sample programs in the **example** folder. Below is a short tutorial on what you can do with the fdfd_suite. 31 | 32 | ## Add folders to path 33 | First, add all folders to path. 34 | 35 | ``` 36 | clear, close all; clc; 37 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 38 | ``` 39 | 40 | ## Instantiate a particular solver 41 | * Direct solve of an electromagnetic simulation with a source: fdfd = fdfd_solve(); 42 | * Resonator mode solver: fdfd = fdfd_modes(); 43 | * Photonic crystal geometry: fdfd = fdfd_blochX_modes(); 44 | * Spatiotemporal modulation of permittivity: fdfd = fdfd_mf_solve(); 45 | * Waveguide cross section modes: fdfd = fdfd_wg_modes(); 46 | 47 | ## Initialize simulation properties 48 | While most solvers have their unique properties, the properties below are common to almost all solvers and should be initialized first. 49 | 50 | * **fdfd.L0**: Length scale. e.g. 1e-6 = microns 51 | * **fdfd.wvlen0**: Operating wavelength in units of L0 52 | * **fdfd.xrange**: Simulation domain limit in the x direction in units of L0, [xmin, xmax] 53 | * **fdfd.yrange**: Simulation domain limit in the y direction in units of L0, [ymin, ymax] 54 | * **fdfd.N**: Number of cells, [Nx, Ny] 55 | * **fdfd.pol**: Polarization, either "TE" or "TM" 56 | * **fdfd.Npml**: Number of absorbing layers at the boundaries, i.e. PMLs, [Npmlx, Npmly] 57 | 58 | ## Add permittivity blocks 59 | To add various dielectric or metal structures, use the function 60 | 61 | ``` fdfd.add_eps(block_type, block_loc, block_val); ``` 62 | 63 | * **block_val**: value of the permittivity. 64 | * **block_type**: choose from 'rect', 'circ', 'poly', 'custom' 65 | * 'rect': Rectangular block. **block_loc** = [x_min, y_min, x_length, y_length]. 66 | * 'circ': Circular block. **block_loc** = [x_center, y_center, radius]. 67 | * 'poly': Polygonal block. **block_loc** = [vertex1; vertex2; ... vertexN]. 68 | * 'custom': Custom block. **block_loc** = @(x, y) boolean function of coordinates x, y. 69 | 70 | ## Set up the permittivity distribution 71 | Simply call 72 | 73 | ``` fdfd.eps_setup(); ``` 74 | 75 | To visualize the permittivity distribution, run 76 | 77 | ``` fdfd.vis_structure(); ``` 78 | 79 | ## Set up the source 80 | To add a source to a simulation, use the function 81 | ``` fdfd.source_setup(src_type, src_loc, src_params) ``` 82 | 83 | **src_type**: choose from 'point', 'tfsf', 'modal' 84 | 85 | * src_type = 'point': Point source 86 | * src_loc = [x_coord, y_coord] 87 | * src_params = src_amplitude 88 | 89 | * src_type = 'tfsf': Total field scattered field source 90 | * src_loc = [x_min, y_min, x_length, y_length]; 91 | * src_params = [src_amplitude, src_angle]. Note: src_angle (in degrees) is measured from the lower left corner with respect to the x-axis. 92 | 93 | * src_type: 'modal': Modal line source for a waveguide 94 | * src_loc = [x_center, y_center]; 95 | * src_params = {orientation, mode, num_points}; 96 | - orientation = 'v' for vertical, 'h' for horizontal 97 | - mode = order of the waveguide mode. e.g. mode = 0 is for either TE0 or TM0 mode 98 | - num_points = number of points above and below the center of the source. 99 | 100 | ## Run the simulation 101 | Call the function 102 | 103 | ``` fdfd.simulate(); ``` 104 | 105 | Upon finishing, the appropriate Ex, Ey, Ez, Hx, Hy, and Hz fields are stored in the *fdfd* object 106 | 107 | ## Visualize the field patterns 108 | There are three functions for visualization. 109 | * **fdfd.visreal(field)**: Visualize the real part of the field 110 | * **fdfd.visabs(field)**: Visualize the amplitude of the field 111 | * **fdfd.moviereal(field, Ncycle, Nfpc)**: Create a movie of with the field. 112 | * Ncycle: (Optional) number of cycles of the movie 113 | * Nfpc: (Optional) number of frames per cycle 114 | 115 | ## Poynting vector 116 | The "poynting(obj)" function can be used to calculate the Poynting vector density of the simulation domain. After a simulation is completed, you can call the following function to compute the Poynting vector of the simulation domain. 117 | 118 | ``` [Sx, Sy] = poynting(fdfd); ``` 119 | 120 | 121 | # Specialized simulations 122 | For more specialized simulations (spatiotemporal modulation, photonic crystal band structure, etc), please refer to the **examples** folder to see how the simulations are set up. 123 | 124 | Please let me know if you have questions! 125 | 126 | Also, if you would like to have additional functionalities for your simulations, I'd be happy to discuss with you to see what I can do. 127 | 128 | -Jerry, 2/19/2018 129 | -------------------------------------------------------------------------------- /class/fdfd_blochX_modes.m: -------------------------------------------------------------------------------- 1 | classdef fdfd_blochX_modes < fdfd_main 2 | %FDFD_BLOCHX_SOLVE: Specializes in 1D periodic photonic crystal modes 3 | 4 | properties 5 | Kx % Bloch wave vector in x 6 | n_modes % Number of modes solved 7 | omega_eigs % Eigen-frequencies of the Bloch modes 8 | lambda_eigs % The corresponding wavelengths of the Eigen-frequencies 9 | end 10 | 11 | methods 12 | %% Run the simulation 13 | function obj = simulate(obj) 14 | switch obj.pol 15 | case 'TE' 16 | [obj.Hz, obj.Ex, obj.Ey, obj.omega_eigs] = solveTE_BlochX(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, obj.Kx, obj.Npml, obj.n_modes); 17 | case 'TM' 18 | [obj.Ez, obj.Hx, obj.Hy, obj.omega_eigs] = solveTM_BlochX(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, obj.Kx, obj.Npml, obj.n_modes); 19 | end 20 | 21 | obj.lambda_eigs = 2*pi*obj.c0/obj.L0 ./ obj.omega_eigs; 22 | end 23 | 24 | %% Filter out unwanted modes of the simulation domain 25 | function obj = filtering(obj, y_planes, thresh) 26 | % thresh is a value between 0 and 1 27 | 28 | % Get the indices of the box 29 | y_ind1 = floor((y_planes(1)-obj.yrange(1))/obj.dL(2)) + 1; 30 | y_ind2 = floor((y_planes(2)-obj.yrange(1))/obj.dL(2)) + 1; 31 | 32 | % Filtering to keep the desired modes 33 | omega_eigs_temp = []; 34 | 35 | switch obj.pol 36 | case 'TE' 37 | Hz_temp = {}; 38 | Ex_temp = {}; 39 | Ey_temp = {}; 40 | 41 | % Apply the filter for each Hz 42 | counter = 1; 43 | for i = 1:length(obj.Hz) 44 | intensity1 = sum(sum(abs(obj.Hz{i}(:, y_ind1:y_ind2)))); 45 | intensity2 = sum(sum(abs(obj.Hz{i}))); 46 | 47 | if intensity1 > thresh*intensity2 48 | Hz_temp{counter, 1} = obj.Hz{i}; 49 | Ex_temp{counter, 1} = obj.Ex{i}; 50 | Ey_temp{counter, 1} = obj.Ey{i}; 51 | 52 | omega_eigs_temp = [omega_eigs_temp; obj.omega_eigs(i)]; 53 | counter = counter + 1; 54 | end 55 | 56 | end 57 | 58 | % Override Hz, Ex, Ey, omega_eigs after filtering 59 | obj.Hz = Hz_temp; 60 | obj.Ex = Ex_temp; 61 | obj.Ey = Ey_temp; 62 | obj.omega_eigs = omega_eigs_temp; 63 | 64 | case 'TM' 65 | Ez_temp = {}; 66 | Hx_temp = {}; 67 | Hy_temp = {}; 68 | 69 | % Apply the filter for each Ez 70 | counter = 1; 71 | for i = 1:length(obj.Ez) 72 | intensity1 = sum(sum(abs(obj.Ez{i}(:, y_ind1:y_ind2)))); 73 | intensity2 = sum(sum(abs(obj.Ez{i}))); 74 | 75 | if intensity1 > thresh*intensity2 76 | Ez_temp{counter, 1} = obj.Ez{i}; 77 | Hx_temp{counter, 1} = obj.Hx{i}; 78 | Hy_temp{counter, 1} = obj.Hy{i}; 79 | 80 | omega_eigs_temp = [omega_eigs_temp; obj.omega_eigs(i)]; 81 | counter = counter + 1; 82 | end 83 | end 84 | 85 | % Override Ez, Hx, Hy, omega_eigs after filtering 86 | obj.Ez = Ez_temp; 87 | obj.Hx = Hx_temp; 88 | obj.Hy = Hy_temp; 89 | obj.omega_eigs = omega_eigs_temp; 90 | end 91 | 92 | obj.lambda_eigs = 2*pi*obj.c0 ./ obj.omega_eigs; 93 | end 94 | 95 | end 96 | 97 | end 98 | 99 | -------------------------------------------------------------------------------- /class/fdfd_main.m: -------------------------------------------------------------------------------- 1 | classdef fdfd_main < handle 2 | %fdfd_main class is a general fdfd class that contains parameters that are 3 | %used in more specific FDFD simulations. All other FDFD simulations 4 | %inherit their properties from the fdfd class 5 | 6 | 7 | properties (Constant) 8 | eps0 = 8.854e-12; % vacuum permittivity in farad/m 9 | mu0 = pi * 4e-7; % vacuum permeability in henry/m 10 | c0 = 1/sqrt(8.854e-12 * pi * 4e-7); % speed of light in vacuum in m/sec 11 | end 12 | 13 | properties 14 | %% Simlulation properties 15 | pol % Polarization: 'TE' or 'TM' 16 | L0 % Length scale. 1e-6 corresponds to microns 17 | wvlen0 % Input wavelength in L0 18 | omega0 % Input frequency 19 | xrange % Simulation domain boundaries in L0: [xmin, xmax] 20 | yrange % Simulation domain boundaries in L0: [ymin, ymax] 21 | N % Number of cells in x and y: [Nx, Ny] 22 | dL % Resolution in x and y: [dx, dy] 23 | Npml % Number of PMLs in x and y: [Npmlx, Npmly] 24 | blocks % Storage of various permittivity blocks that are added 25 | eps_r % Relative permittivity distribution 26 | src % Source distribution of the simulation 27 | 28 | %% Output properties 29 | % Electric fields 30 | Ex 31 | Ey 32 | Ez 33 | 34 | % Magnetic fields 35 | Hx 36 | Hy 37 | Hz 38 | end 39 | 40 | 41 | methods 42 | %% Add dielectric blocks to the simulation 43 | function obj = add_eps(obj, block_name, block_loc, block_val) 44 | N_blocks = length(obj.blocks); 45 | 46 | obj.blocks{N_blocks+1} = {block_name, block_loc, block_val}; 47 | end 48 | 49 | %% Visualize the permittivity distribution 50 | function [] = vis_structure(obj) 51 | vis_abs(obj.eps_r, obj.xrange, obj.yrange); 52 | end 53 | 54 | %% Visualization of the real part of the field 55 | function [] = visreal(obj, field) 56 | vis_real(field, obj.xrange, obj.yrange); 57 | end 58 | 59 | %% Visualization of the amplitude of the field 60 | function [] = visabs(obj, field) 61 | vis_abs(field, obj.xrange, obj.yrange); 62 | end 63 | 64 | %% Make a movie out of the field 65 | function [] = moviereal(obj, field, Ncycle, Nfpc) 66 | if nargin < 4 % no Ncycle 67 | Ncycle = 1; % play movie for 1 cycle 68 | end 69 | 70 | if nargin < 5 % no Nframe 71 | Nfpc = 30; % take 30 frames per cycle 72 | end 73 | movie_real(field, obj.xrange, obj.yrange, Ncycle, Nfpc); 74 | end 75 | 76 | %% Compute the Poynting flux 77 | function [Sx, Sy] = poynting(obj) 78 | switch obj.pol 79 | case 'TE' 80 | [Sx, Sy] = poyntingTE(obj.Hz, obj.Ex, obj.Ey); 81 | 82 | case 'TM' 83 | [Sx, Sy] = poyntingTM(obj.Ez, obj.Hx, obj.Hy); 84 | 85 | end 86 | end 87 | 88 | %% Set up the permittivity distribution 89 | function obj = eps_setup(obj) 90 | obj.dL = [diff(obj.xrange) diff(obj.yrange)]./obj.N; 91 | 92 | obj.omega0 = 2*pi*obj.c0 / (obj.wvlen0 * obj.L0); 93 | 94 | 95 | obj.eps_r = ones(obj.N); 96 | 97 | N_blocks = length(obj.blocks); 98 | for i = 1 : N_blocks 99 | obj.eps_r = assign_blocks(obj.eps_r, obj.blocks{i}{1}, obj.blocks{i}{2}, obj.blocks{i}{3}, obj.xrange, obj.yrange, obj.N); 100 | end 101 | 102 | end 103 | 104 | %% Set up the source 105 | function [beta, obj] = source_setup(obj, src_type, src_loc, src_params) 106 | %% Output parameter: 107 | % beta: for a modal source, this outputs the propagation constant 108 | 109 | %% 110 | obj.src = zeros(obj.N); 111 | beta = []; 112 | 113 | src_type = lower(src_type); 114 | switch src_type 115 | %% Point source 116 | case 'point' 117 | % src_loc is the location of the point source 118 | % src_params contains the amplitude of the source 119 | src_ind_x = round((src_loc(1) - obj.xrange(1)) / diff(obj.xrange) * obj.N(1)) + 1; 120 | src_ind_y = round((src_loc(2) - obj.yrange(1)) / diff(obj.yrange) * obj.N(2)) + 1; 121 | 122 | obj.src(src_ind_x, src_ind_y) = src_params; 123 | 124 | %% Total field scattered field 125 | case 'tfsf' 126 | % src_loc contains [x_start, y_start, x_length, y_length] 127 | % src_params contains [amplitude, angle] of the source 128 | 129 | hx = obj.dL(1); 130 | hy = obj.dL(2); 131 | 132 | % Q = mask function. It is 1 in SF region, 0 in TF region 133 | M = prod(obj.N); 134 | Q = ones(obj.N); 135 | within_TF = @(x, y) x > src_loc(1) & y > src_loc(2) & x < src_loc(1)+src_loc(3) & y < src_loc(2)+src_loc(4); 136 | Q = assign_val(Q, obj.xrange, obj.yrange, within_TF, 0); 137 | Q_sparse = spdiags(Q(:), 0, M, M); 138 | 139 | % Calculate A matrix without the scatterer 140 | switch obj.pol 141 | case 'TM' 142 | [~, ~, ~, A] = solveTM(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, zeros(obj.N), obj.Npml); 143 | case 'TE' 144 | [~, ~, ~, A] = solveTE(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, zeros(obj.N), obj.Npml); 145 | end 146 | 147 | % Calculate the TFSF plane wave 148 | theta = src_params(2) * pi/180; % Angle of incidence from X-AXIS 149 | x_vec = linspace(obj.xrange(1)+hx/2, obj.xrange(2)-hx/2, obj.N(1)); 150 | y_vec = linspace(obj.yrange(1)+hy/2, obj.yrange(2)-hy/2, obj.N(2)); 151 | 152 | [X, Y] = meshgrid(x_vec, y_vec); 153 | f_src = src_params(1) * exp(-1i * 2*pi/obj.wvlen0 * (X'*cos(theta) + Y'*sin(theta))); 154 | f_src_vec = f_src(:)/obj.omega0; 155 | 156 | % Calculate the TFSF source 157 | b = (Q_sparse*A - A*Q_sparse) * f_src_vec; 158 | obj.src = reshape(b, obj.N); 159 | 160 | %% Waveguide modal source 161 | case 'modal' 162 | % src_loc is the center coordinate of the modal source 163 | % src_params is a cell that contains: 164 | % {orientation, mode, num_points} 165 | 166 | src_ind_x = round((src_loc(1) - obj.xrange(1)) / diff(obj.xrange) * obj.N(1)) + 1; 167 | src_ind_y = round((src_loc(2) - obj.yrange(1)) / diff(obj.yrange) * obj.N(2)) + 1; 168 | 169 | orientation = src_params{1}; 170 | mode = src_params{2}+1; 171 | n_pts = src_params{3}; 172 | 173 | %% orientation = 'v' for vertical, 'h' for horizontal 174 | orientation = lower(orientation); 175 | switch orientation 176 | case 'v' 177 | % Get the permittivity cross section 178 | src_line_inds = src_ind_y-n_pts : src_ind_y+n_pts; 179 | eps_src = obj.eps_r(src_ind_x, src_line_inds); 180 | 181 | src_xrange = [src_line_inds(1)*obj.dL(2), src_line_inds(end)*obj.dL(2)]; 182 | % Solve for the modes 183 | eps_src = transpose(eps_src); 184 | [temp_src, beta] = solve_wg_modes_1D(obj.pol, obj.L0, obj.wvlen0, src_xrange, [-1 1], eps_src, mode); 185 | 186 | % Assign the source profile 187 | obj.src(src_ind_x, src_line_inds) = transpose(temp_src); 188 | 189 | case 'h' 190 | % Get the permittivity cross section 191 | src_line_inds = src_ind_x-n_pts : src_ind_x+n_pts; 192 | eps_src = obj.eps_r(src_line_inds, src_ind_y); 193 | 194 | src_xrange = [src_line_inds(1)*obj.dL(1), src_line_inds(end)*obj.dL(1)]; 195 | 196 | % Solve for the modes 197 | [temp_src, beta] = solve_wg_modes_1D(obj.pol, obj.L0, obj.wvlen0, src_xrange, [-1 1], eps_src, mode); 198 | 199 | % Assign the source profile 200 | obj.src(src_line_inds, src_ind_y) = temp_src; 201 | end 202 | end 203 | end 204 | end 205 | 206 | end 207 | 208 | -------------------------------------------------------------------------------- /class/fdfd_mf_solve.m: -------------------------------------------------------------------------------- 1 | classdef fdfd_mf_solve < fdfd_main 2 | %FDFD_MODE_SOLVE: Specializes in solving field profiles in 3 | %time-modulated photonic structures 4 | % Details of the implementation: Y. Shi, et al. Optica 3, pp.1256 (2016) 5 | 6 | properties 7 | Nsb % Number of sideband frequencies 8 | mod_amp_blocks % Cells containing modulation amplitude geometries 9 | mod_phi_blocks % Cells containing modulation phase geometries 10 | mod_amp % Modulation amplitude distribution 11 | mod_phi % Modulation phase distribution 12 | Omega % Modulation frequency 13 | omega % List of sideband frequencies 14 | end 15 | 16 | methods 17 | %% Visualize the modulation amplitude and phase 18 | function obj = vis_mod(obj) 19 | subplot(2, 1, 1); 20 | vis_abs(obj.mod_amp, obj.xrange, obj.yrange); 21 | title('Mod amplitude'); 22 | 23 | subplot(2, 1, 2); 24 | vis_real(obj.mod_phi, obj.xrange, obj.yrange); 25 | title('Mod phase'); 26 | end 27 | 28 | %% Run the simulation 29 | function obj = simulate(obj) 30 | switch obj.pol 31 | case 'TE' 32 | [obj.Hz, obj.Ex, obj.Ey, obj.omega] = solveTE_mf(obj.L0, obj.wvlen0, obj.Omega, obj.Nsb, obj.xrange, obj.yrange, obj.eps_r, obj.mod_amp, obj.mod_phi, obj.src, obj.Npml); 33 | case 'TM' 34 | [obj.Ez, obj.Hx, obj.Hy, obj.omega] = solveTM_mf(obj.L0, obj.wvlen0, obj.Omega, obj.Nsb, obj.xrange, obj.yrange, obj.eps_r, obj.mod_amp, obj.mod_phi, obj.src, obj.Npml); 35 | end 36 | end 37 | 38 | 39 | %% Add a modulation amplitude region 40 | function obj = add_mod_amp(obj, block_name, block_loc, block_val) 41 | N_blocks = length(obj.mod_amp_blocks); 42 | 43 | obj.mod_amp_blocks{N_blocks+1} = {block_name, block_loc, block_val}; 44 | end 45 | 46 | %% Add a modulation phase region 47 | function obj = add_mod_phi(obj, block_name, block_loc, block_val) 48 | N_blocks = length(obj.mod_phi_blocks); 49 | 50 | obj.mod_phi_blocks{N_blocks+1} = {block_name, block_loc, block_val}; 51 | end 52 | 53 | %% Set up the modulation region 54 | function obj = mod_setup(obj) 55 | obj.mod_amp = zeros(obj.N); 56 | obj.mod_phi = zeros(obj.N); 57 | 58 | % Assign the modulation amplitude matrix 59 | N_amp = length(obj.mod_amp_blocks); 60 | for i = 1 : N_amp 61 | obj.mod_amp = assign_blocks(obj.mod_amp, obj.mod_amp_blocks{i}{1}, obj.mod_amp_blocks{i}{2}, obj.mod_amp_blocks{i}{3}, obj.xrange, obj.yrange); 62 | end 63 | 64 | % Assign the modulation phase matrix 65 | N_phi = length(obj.mod_phi_blocks); 66 | for i = 1 : N_phi 67 | obj.mod_phi = assign_blocks(obj.mod_phi, obj.mod_phi_blocks{i}{1}, obj.mod_phi_blocks{i}{2}, obj.mod_phi_blocks{i}{3}, obj.xrange, obj.yrange); 68 | end 69 | end 70 | 71 | end 72 | 73 | end 74 | 75 | -------------------------------------------------------------------------------- /class/fdfd_modes.m: -------------------------------------------------------------------------------- 1 | classdef fdfd_modes < fdfd_main 2 | %FDFD_MODES: Specializes in solving for the eigenmodes of 2D 3 | %photonic structures 4 | 5 | properties 6 | n_modes % Number of modes 7 | omega_eigs % Eigen-frequencies of the modes 8 | lambda_eigs % Wavelengths corresponding to the eigen-frequencies 9 | A % Sparse system matrix used for eigenmode solution 10 | end 11 | 12 | methods 13 | %% Run the simulation 14 | function obj = simulate(obj) 15 | switch obj.pol 16 | case 'TE' 17 | [obj.Hz, obj.Ex, obj.Ey, obj.A, obj.omega_eigs] = solveTE_modes(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, obj.Npml, obj.n_modes); 18 | case 'TM' 19 | [obj.Ez, obj.Hx, obj.Hy, obj.A, obj.omega_eigs] = solveTM_modes(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, obj.Npml, obj.n_modes); 20 | end 21 | 22 | obj.lambda_eigs = 2*pi*obj.c0/obj.L0 ./ obj.omega_eigs; 23 | end 24 | 25 | end 26 | 27 | end 28 | 29 | -------------------------------------------------------------------------------- /class/fdfd_solve.m: -------------------------------------------------------------------------------- 1 | classdef fdfd_solve < fdfd_main 2 | %FDFD_SOLVE: specializes in a direct solution for a photonic structure 3 | %under the excitation of a source 4 | 5 | properties 6 | A % Sparse system matrix of the solver 7 | end 8 | 9 | methods 10 | %% Run the simulation 11 | function obj = simulate(obj) 12 | switch obj.pol 13 | case 'TE' 14 | [obj.Hz, obj.Ex, obj.Ey, obj.A] = solveTE(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, obj.src, obj.Npml); 15 | case 'TM' 16 | [obj.Ez, obj.Hx, obj.Hy, obj.A] = solveTM(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, obj.src, obj.Npml); 17 | 18 | end 19 | end 20 | 21 | end 22 | 23 | end 24 | 25 | -------------------------------------------------------------------------------- /class/fdfd_wg_modes.m: -------------------------------------------------------------------------------- 1 | classdef fdfd_wg_modes < fdfd_main 2 | %FDFD_WG_MODES: Specializes in solving for the guided modes of a 3 | %optical waveguide given its x-y cross section 4 | 5 | properties 6 | beta % Propagation constants for each waveguide mode in 1/L0 7 | n_modes % Number of waveguide modes 8 | beta_scale % The scaling of the guess on beta. Default value is 2 9 | A % System matrix 10 | end 11 | 12 | methods 13 | %% Run the simulation 14 | function obj = simulate(obj) 15 | [obj.Hx, obj.Hy, obj.Hz, obj.Ex, obj.Ey, obj.Ez, obj.beta, obj.A] = solve_wg_modes(obj.L0, obj.wvlen0, obj.xrange, obj.yrange, obj.eps_r, obj.n_modes, obj.beta_scale); 16 | end 17 | 18 | %% Visualize the real part of the fields 19 | function [] = visreal_wg(obj, mode) 20 | subplot(2, 3, 1); 21 | vis_real(obj.Hx{mode}, obj.xrange, obj.yrange); 22 | title('Hx'); 23 | 24 | subplot(2, 3, 2); 25 | vis_real(obj.Hy{mode}, obj.xrange, obj.yrange); 26 | title('Hy'); 27 | 28 | subplot(2, 3, 3); 29 | vis_real(obj.Hz{mode}, obj.xrange, obj.yrange); 30 | title('Hz'); 31 | 32 | subplot(2, 3, 4); 33 | vis_real(obj.Ex{mode}, obj.xrange, obj.yrange); 34 | title('Ex'); 35 | 36 | subplot(2, 3, 5); 37 | vis_real(obj.Ey{mode}, obj.xrange, obj.yrange); 38 | title('Ey'); 39 | 40 | subplot(2, 3, 6); 41 | vis_real(obj.Ez{mode}, obj.xrange, obj.yrange); 42 | title('Ez'); 43 | end 44 | 45 | %% Visualize the amplitude of the fields 46 | function [] = visabs_wg(obj, mode) 47 | subplot(2, 3, 1); 48 | vis_abs(obj.Hx{mode}, obj.xrange, obj.yrange); 49 | title('Hx'); 50 | 51 | subplot(2, 3, 2); 52 | vis_abs(obj.Hy{mode}, obj.xrange, obj.yrange); 53 | title('Hy'); 54 | 55 | subplot(2, 3, 3); 56 | vis_abs(obj.Hz{mode}, obj.xrange, obj.yrange); 57 | title('Hz'); 58 | 59 | subplot(2, 3, 4); 60 | vis_abs(obj.Ex{mode}, obj.xrange, obj.yrange); 61 | title('Ex'); 62 | 63 | subplot(2, 3, 5); 64 | vis_abs(obj.Ey{mode}, obj.xrange, obj.yrange); 65 | title('Ey'); 66 | 67 | subplot(2, 3, 6); 68 | vis_abs(obj.Ez{mode}, obj.xrange, obj.yrange); 69 | title('Ez'); 70 | end 71 | 72 | %% Draw quiver plots 73 | function [] = quiver_wg(obj, mode) 74 | x_total = 1:obj.N(1); 75 | y_total = 1:obj.N(2); 76 | 77 | resolution = 6; 78 | indx = round(resolution/2):resolution:obj.N(1); 79 | indy = round(resolution/2):resolution:obj.N(2); 80 | 81 | x = x_total(indx); 82 | y = y_total(indy); 83 | 84 | [X_temp, Y_temp] = meshgrid(x, y); 85 | X = X_temp/obj.N(1) * diff(obj.xrange) + obj.xrange(1); 86 | Y = Y_temp/obj.N(2) * diff(obj.yrange) + obj.yrange(1); 87 | 88 | Ex_qui = real(obj.Ex{mode}(indx, indy)); 89 | Ey_qui = real(obj.Ey{mode}(indx, indy)); 90 | 91 | Hx_qui = real(obj.Hx{mode}(indx, indy)); 92 | Hy_qui = real(obj.Hy{mode}(indx, indy)); 93 | 94 | subplot(1, 2, 1); 95 | quiver(X, Y, transpose(Ex_qui), transpose(Ey_qui), 'AlignVertexCenters', 'on'); 96 | axis equal 97 | axis([obj.xrange obj.yrange]); 98 | title('E field'); 99 | 100 | subplot(1, 2, 2); 101 | 102 | quiver(X, Y, transpose(Hx_qui), transpose(Hy_qui), 'AlignVertexCenters', 'on'); 103 | 104 | axis equal 105 | axis([obj.xrange obj.yrange]); 106 | title('H field'); 107 | end 108 | 109 | end 110 | 111 | end 112 | 113 | -------------------------------------------------------------------------------- /examples/MIM_slot_waveguide.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% 5 | fdfd = fdfd_wg_modes(); % Instantiate the solver 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.5; % Set wavelength 10 | fdfd.xrange = [-0.75 0.75]; % [xmin, xmax] 11 | fdfd.yrange = [-0.75 0.75]; % [ymin, ymax] 12 | fdfd.N = [200 200]; % Number of cells, [Nx, Ny] 13 | 14 | fdfd.n_modes = 1; % Number of modes 15 | fdfd.beta_scale = 2.5; % Scales the initial beta_est 16 | 17 | %% Add permittivity blocks 18 | eps_metal = -100 - 1i*10; 19 | fdfd.add_eps('rect', [-0.3, -0.1, 0.27, 0.2], eps_metal); % Left metal waveguide 20 | fdfd.add_eps('rect', [0.03, -0.1, 0.27, 0.2], eps_metal); % Right metal waveguide 21 | 22 | %% Set up permittivity 23 | fdfd.eps_setup(); 24 | 25 | %% Visualize the permittivity distribution 26 | figure; 27 | fdfd.vis_structure(); 28 | 29 | %% Solve the fields 30 | fdfd.simulate(); 31 | 32 | %% Visualize fields 33 | mode = 1; 34 | figure; 35 | fdfd.visreal_wg(mode); 36 | 37 | %% Quiver plot of the cross-section fields 38 | figure; 39 | fdfd.quiver_wg(mode); 40 | -------------------------------------------------------------------------------- /examples/SOI_slot_waveguide.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% 5 | fdfd = fdfd_wg_modes(); % Instantiate the solver 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.55; % Set wavelength 10 | fdfd.xrange = [-1 1]; % [xmin, xmax] 11 | fdfd.yrange = [-1 1]; % [ymin, ymax] 12 | fdfd.N = [200 200]; % Number of cells, [Nx, Ny] 13 | 14 | fdfd.n_modes = 4; 15 | 16 | %% Add permittivity blocks 17 | fdfd.add_eps('rect', [-0.5, -0.2, 0.47, 0.4], 12); % Left Si waveguide 18 | fdfd.add_eps('rect', [0.03, -0.2, 0.47, 0.4], 12); % Right Si waveguide 19 | fdfd.add_eps('custom', @(x, y) y < -0.2, 2.25); % SiO2 substrate 20 | 21 | %% Set up permittivity 22 | fdfd.eps_setup(); 23 | 24 | %% Visualize the permittivity distribution 25 | figure; 26 | fdfd.vis_structure(); 27 | 28 | %% Solve the fields 29 | fdfd.simulate(); 30 | 31 | %% Visualize fields 32 | mode = 1; 33 | figure; 34 | fdfd.visreal_wg(mode); 35 | 36 | %% Quiver plot of the cross-section fields 37 | figure; 38 | fdfd.quiver_wg(mode); 39 | -------------------------------------------------------------------------------- /examples/circ_scatterer.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% 5 | fdfd = fdfd_solve(); % Instantiate the solver 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.55; % Set wavelength 10 | fdfd.xrange = [-2 2]; % [xmin, xmax] 11 | fdfd.yrange = [-2 2]; % [ymin, ymax] 12 | fdfd.N = [200 200]; % Number of cells, [Nx, Ny] 13 | 14 | fdfd.Npml = [15 15]; % Number of PMLs [Npmlx, Npmly] 15 | fdfd.pol = 'TM'; % Set the polarization 16 | 17 | %% Add permittivity blocks 18 | % fdfd.add_eps('rect', [-1.5, 1.1, 2, 0.1], 20); 19 | % fdfd.add_eps('rect', [-0.1, 0.5, 2, 0.1], 12); 20 | fdfd.add_eps('circ', [0, 0, 0.3], 7); % Add a circular scatterer 21 | 22 | %% Set up the simulation 23 | fdfd.eps_setup(); 24 | 25 | %% Set up the sources 26 | % fdfd.source_setup('point', [-0.4, 0.2], 0.5); % Point source 27 | % [beta] = fdfd.source_setup('modal', [-1.4, -0.05], {'v', 0, 30}); % Modal source 28 | 29 | fdfd.source_setup('tfsf', [-1.2, -1, 2.5, 2], [0.5, 40]); % Total field scattered field 30 | 31 | 32 | %% Visualize the permittivity distribution 33 | figure; 34 | fdfd.vis_structure(); 35 | 36 | %% Perform FDFD simulation 37 | fdfd.simulate(); 38 | 39 | %% Visualize a particular field 40 | figure; 41 | fdfd.visreal(fdfd.Ez); 42 | 43 | %% Make a movie of the propagating field 44 | figure; 45 | fdfd.moviereal(fdfd.Ez); 46 | 47 | -------------------------------------------------------------------------------- /examples/guided_resonance.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% 5 | fdfd = fdfd_blochX_modes(); % Instantiate the Bloch solver 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.55; % Set wavelength 10 | fdfd.xrange = [-0.25 0.25]; % [xmin, xmax] 11 | fdfd.yrange = [-2 2]; % [ymin, ymax] 12 | fdfd.N = [25 200]; % Number of cells, [Nx, Ny] 13 | fdfd.Kx = 0.0; % Bloch wavevector in 1/L0 14 | 15 | fdfd.n_modes = 20; % Number of eigenmodes 16 | fdfd.pol = 'TM'; % Polarization 17 | 18 | fdfd.Npml = [0 15]; % Number of PMLs [Npmlx, Npmly] 19 | 20 | %% Add permittivity blocks 21 | fdfd.add_eps('rect', [-0.5, -0.2, 1, 0.4], 12); % Add a waveguide 22 | fdfd.add_eps('circ', [0, 0.2, 0.10], 1); % Add a hole on top of the wg 23 | 24 | %% Set up permittivity 25 | fdfd.eps_setup(); 26 | 27 | %% Visualize the permittivity distribution 28 | figure; 29 | fdfd.vis_structure(); 30 | 31 | %% Solve the fields 32 | fdfd.simulate(); 33 | 34 | %% Filter the modes 35 | %% This is so that you can keep only the meaningful guided resonance modes 36 | y_locs = [-0.2 0.2]; % Filter boundary 37 | thresh = 0.15; % Energy density threshold within boundary to keep the mode 38 | 39 | fdfd.filtering(y_locs, thresh); 40 | 41 | %% Get the eigen-frequencies 42 | fdfd.omega_eigs 43 | 44 | %% Visualize fields 45 | mode = 2; 46 | figure; 47 | fdfd.visreal(fdfd.Ez{mode}) 48 | 49 | -------------------------------------------------------------------------------- /examples/heart_resonator.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% Instantiate the eigenmode solver 5 | fdfd = fdfd_modes(); 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 3; % Set wavelength 10 | fdfd.xrange = [-1.5 1.5]; % [xmin, xmax] 11 | fdfd.yrange = [-1.5 1.5]; % [ymin, ymax] 12 | fdfd.N = [200 200]; % Number of cells, [Nx, Ny] 13 | 14 | fdfd.n_modes = 2; % Number of eigenmodes 15 | fdfd.pol = 'TE'; % Polarization 16 | 17 | fdfd.Npml = [15 15]; % Number of PMLs [Npmlx, Npmly] 18 | 19 | %% Add permittivity blocks 20 | fdfd.add_eps('circ', [-0.25, 0.1, 0.25], 12); 21 | fdfd.add_eps('circ', [0.25, 0.1, 0.25], 12); 22 | fdfd.add_eps('rect', [-0.5, -1, 1, 1.1], 1); 23 | 24 | 25 | vertices = [-0.5 0.1; 0.5 0.1; 0 -0.5]; % Triangle vertices 26 | fdfd.add_eps('poly', vertices, 12); % Add a trangular resonator 27 | 28 | %% Set up permittivity 29 | fdfd.eps_setup(); 30 | 31 | %% Visualize the permittivity distribution 32 | figure; 33 | fdfd.vis_structure(); 34 | 35 | %% Solve the fields 36 | fdfd.simulate(); 37 | 38 | %% Get the eigen-frequencies 39 | fdfd.omega_eigs 40 | fdfd.lambda_eigs 41 | 42 | %% Visualize fields 43 | mode = 1; 44 | figure; 45 | fdfd.visabs(fdfd.Hz{mode}) 46 | 47 | %% Make a movie of the fields 48 | figure; 49 | fdfd.moviereal(fdfd.Hz{mode}) 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/photonic_transition_TE.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% Instantiate the MF-FDFD solver 5 | fdfd = fdfd_mf_solve(); 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.55; % Set wavelength 10 | fdfd.xrange = [0 15]; % [xmin, xmax] 11 | fdfd.yrange = [-1 1]; % [ymin, ymax] 12 | fdfd.N = [600 150]; % Number of cells, [Nx, Ny] 13 | fdfd.Nsb = 1; % Number of sideband frequencies 14 | 15 | fdfd.Npml = [15 10]; % Number of PMLs [Npmlx, Npmly] 16 | 17 | fdfd.pol = 'TE'; % Polarization 18 | 19 | %% Modulation frequency 20 | wvlen0 = 1.55; % 1/0.6468 21 | wvlen1 = 1.30; % 1/0.8879 22 | fdfd.Omega = 2*pi*fdfd.c0/fdfd.L0* (1/wvlen1 - 1/wvlen0); 23 | 24 | 25 | %% Linear waveguide 26 | a = 0.4002; 27 | wg_lower = -a/2; 28 | fdfd.add_eps('rect', [-1, wg_lower, 20, a], 12.25); 29 | 30 | %% Modulation region 31 | delta = 1; % Modulation amplitude 32 | mod_x = 1.5; % Start location of the modulation 33 | mod_L = 13.2; % range of modulation region in x 34 | q = 3.8124; % Modulation momentum 35 | 36 | phi_fun = @(x,y) q*x; % Modulation phase distribution as a function handle 37 | 38 | fdfd.add_mod_amp('rect', [mod_x, -a/2, mod_L, a/2], delta); 39 | fdfd.add_mod_phi('rect', [mod_x, -a/2, mod_L, a/2], phi_fun); 40 | 41 | %% Set up permittivity and modulation profile 42 | fdfd.eps_setup(); 43 | fdfd.mod_setup(); 44 | 45 | %% Visualize the permittivity distribution and the modulation profile 46 | figure; 47 | fdfd.vis_structure(); 48 | 49 | figure; 50 | fdfd.vis_mod(); 51 | 52 | %% Sources 53 | % myfdfd.source_setup('point', [-0.4, 0.2], 0.5); 54 | % myfdfd.source_setup('tfsf', [-1.2, -1, 2.5, 2], [0.5, 40]); 55 | [beta] = fdfd.source_setup('modal', [0.8, 0.00], {'v', 0, 40}); % Set up a modal source 56 | 57 | %% 58 | fdfd.simulate(); 59 | 60 | %% Inspect various sideband frequencies 61 | figure; 62 | subplot(3, 1, 1); 63 | fdfd.visreal(fdfd.Ey{1}); 64 | subplot(3, 1, 2); 65 | fdfd.visreal(fdfd.Ey{2}); 66 | subplot(3, 1, 3); 67 | fdfd.visreal(fdfd.Ey{3}); 68 | 69 | %% Total fields 70 | Ey_tot = (fdfd.Ey{1} + fdfd.Ey{2} + fdfd.Ey{3}); 71 | figure; 72 | vis_real(Ey_tot, fdfd.xrange, fdfd.yrange) 73 | -------------------------------------------------------------------------------- /examples/photonic_transition_TM.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% Instantiate the MF-FDFD solver 5 | fdfd = fdfd_mf_solve(); 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.5461; % Set wavelength 10 | fdfd.xrange = [0 15]; % [xmin, xmax] 11 | fdfd.yrange = [-1 1]; % [ymin, ymax] 12 | fdfd.N = [600 200]; % Number of cells, [Nx, Ny] 13 | fdfd.Nsb = 1; % Number of sideband frequencies 14 | 15 | fdfd.Npml = [15 10]; % Number of PMLs [Npmlx, Npmly] 16 | 17 | fdfd.pol = 'TM'; % Polarization 18 | 19 | %% Modulation frequency 20 | wvlen0 = 1.5461; % 1/0.6468 21 | wvlen1 = 1.1263; % 1/0.8879 22 | fdfd.Omega = 2*pi*fdfd.c0/fdfd.L0* (1/wvlen1 - 1/wvlen0); 23 | 24 | 25 | %% Linear waveguide 26 | a = 0.2202; 27 | wg_lower = -a/2; 28 | fdfd.add_eps('rect', [-1, wg_lower, 20, a], 12.25); 29 | 30 | %% Modulation region 31 | delta = 1; % Modulation amplitude 32 | mod_x = 1.5; % Start location of the modulation 33 | mod_L = 10.2; % range of modulation region in x 34 | q = 2.9263; % Modulation momentum 35 | 36 | phi_fun = @(x,y) q*x; % Modulation phase distribution as a function handle 37 | 38 | fdfd.add_mod_amp('rect', [mod_x, -a/2, mod_L, a/2], delta); 39 | fdfd.add_mod_phi('rect', [mod_x, -a/2, mod_L, a/2], phi_fun); 40 | 41 | %% Set up permittivity and modulation profile 42 | fdfd.eps_setup(); 43 | fdfd.mod_setup(); 44 | 45 | %% Visualize the permittivity distribution 46 | figure; 47 | fdfd.vis_structure(); 48 | 49 | %% Sources 50 | % myfdfd.source_setup('point', [-0.4, 0.2], 0.5); 51 | % myfdfd.source_setup('tfsf', [-1.2, -1, 2.5, 2], [0.5, 40]); 52 | [beta] = fdfd.source_setup('modal', [0.8, 0.00], {'v', 0, 30}); % Set up a modal source 53 | 54 | %% 55 | fdfd.simulate(); 56 | 57 | %% Inspect various sideband frequencies 58 | figure; 59 | subplot(3, 1, 1); 60 | fdfd.visreal(fdfd.Ez{1}); 61 | subplot(3, 1, 2); 62 | fdfd.visreal(fdfd.Ez{2}); 63 | subplot(3, 1, 3); 64 | fdfd.visreal(fdfd.Ez{3}); 65 | 66 | %% Total fields 67 | Ez_tot = (fdfd.Ez{1} + fdfd.Ez{2} + fdfd.Ez{3}); 68 | figure; 69 | vis_real(Ez_tot, fdfd.xrange, fdfd.yrange) 70 | -------------------------------------------------------------------------------- /examples/ring_resonator.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% 5 | fdfd = fdfd_solve(); % Instantiate the solver 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.508465; % Set wavelength 10 | fdfd.xrange = [0 6]; % [xmin, xmax] 11 | fdfd.yrange = [0 6]; % [ymin, ymax] 12 | fdfd.N = [300 300]; % Number of cells, [Nx, Ny] 13 | 14 | fdfd.Npml = [15 15]; % Number of PMLs [Npmlx, Npmly] 15 | fdfd.pol = 'TM'; % Set the polarization 16 | 17 | %% Add permittivity blocks 18 | r1 = 1.5; % Inner radius 19 | r2 = 1.8; % Outer radius 20 | 21 | wg_c = 0.81; % Waveguide center 22 | wg_d = 0.201; % Waveguide width 23 | 24 | fdfd.add_eps('circ', [3, 3, r2], 12); 25 | fdfd.add_eps('circ', [3, 3, r1], 1); 26 | fdfd.add_eps('rect', [fdfd.xrange(1), wg_c-wg_d/2, diff(fdfd.xrange), wg_d], 12); 27 | 28 | %% Set up the simulation 29 | fdfd.eps_setup(); 30 | 31 | %% Set up the sources 32 | [beta] = fdfd.source_setup('modal', [1.01, wg_c], {'v', 0, 30}); % Modal source 33 | 34 | %% Visualize the permittivity distribution 35 | figure; 36 | fdfd.vis_structure(); 37 | 38 | %% Perform FDFD simulation 39 | fdfd.simulate(); 40 | 41 | %% Visualize a particular field 42 | figure; 43 | fdfd.visabs(fdfd.Ez); 44 | 45 | %% Make a movie of the propagating field 46 | figure; 47 | fdfd.moviereal(fdfd.Ez); 48 | 49 | -------------------------------------------------------------------------------- /examples/triangle_resonator.m: -------------------------------------------------------------------------------- 1 | clear; close all; clc; 2 | addpath('../class', '../flux', '../helper', '../solver', '../vis'); 3 | 4 | %% Instantiate the eigenmode solver 5 | fdfd = fdfd_modes(); 6 | 7 | %% Initialize simulation properties 8 | fdfd.L0 = 1e-6; % Set length scale to micron 9 | fdfd.wvlen0 = 1.55; % Set wavelength 10 | fdfd.xrange = [-2 2]; % [xmin, xmax] 11 | fdfd.yrange = [-2 2]; % [ymin, ymax] 12 | fdfd.N = [200 200]; % Number of cells, [Nx, Ny] 13 | 14 | fdfd.n_modes = 4; % Number of eigenmodes 15 | fdfd.pol = 'TE'; % Polarization 16 | 17 | fdfd.Npml = [15 15]; % Number of PMLs [Npmlx, Npmly] 18 | 19 | %% Add permittivity blocks 20 | vertices = [-1 -1; 0 1; 1 -1]; % Triangle vertices 21 | fdfd.add_eps('poly', vertices, 12); % Add a trangular resonator 22 | 23 | %% Set up permittivity 24 | fdfd.eps_setup(); 25 | 26 | %% Visualize the permittivity distribution 27 | figure; 28 | fdfd.vis_structure(); 29 | 30 | %% Solve the fields 31 | fdfd.simulate(); 32 | 33 | %% Get the eigen-frequencies 34 | fdfd.omega_eigs 35 | 36 | %% Visualize fields 37 | mode = 4; 38 | figure; 39 | fdfd.visabs(fdfd.Hz{mode}) 40 | 41 | %% Make a movie of the fields 42 | figure; 43 | fdfd.moviereal(fdfd.Hz{mode}) 44 | 45 | 46 | -------------------------------------------------------------------------------- /flux/poyntingTE.m: -------------------------------------------------------------------------------- 1 | function [Sx, Sy] = poyntingTE(Hz, Ex, Ey) 2 | %% Input Parameters 3 | % Hz, Ex, Ey: 2D arrays of H- and E-field components 4 | 5 | 6 | %% Output Parameters 7 | % Sx, Sy: 2D array of x- and y-components of Poynting vector 8 | 9 | Hz_x = bwdmean_w(Hz, 'x'); 10 | Sx = 1/2 * real(Ey .* conj(Hz_x)); 11 | 12 | Hz_av_y = bwdmean_w(Hz, 'y'); 13 | Sy = -1/2 * real(Ex .* conj(Hz_av_y)); 14 | 15 | end -------------------------------------------------------------------------------- /flux/poyntingTM.m: -------------------------------------------------------------------------------- 1 | function [Sx, Sy] = poyntingTM(Ez, Hx, Hy) 2 | %% Input Parameters 3 | % Ez, Hx, Hy: 2D arrays of E- and H-field components 4 | 5 | 6 | %% Output Parameters 7 | % Sx, Sy: 2D array of x- and y-components of Poynting vector 8 | 9 | Ez_x = bwdmean_w(Ez, 'x'); 10 | Sx = -1/2 * real(Ez_x .* conj(Hy)); 11 | 12 | Ez_y = bwdmean_w(Ez, 'y'); 13 | Sy = 1/2 * real(Ez_y .* conj(Hx)); 14 | 15 | end -------------------------------------------------------------------------------- /helper/S_create.m: -------------------------------------------------------------------------------- 1 | function [Sxf, Sxb, Syf, Syb] = S_create(L0, wvlen, xrange, yrange, N, Npml) 2 | %S_CREATE: creates the stretched-coordinate PML operators in FDFD 3 | % Detailed explanation goes here 4 | 5 | 6 | %% Set up the domain parameters. 7 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 8 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 9 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 10 | 11 | M = prod(N); 12 | 13 | omega = 2*pi*c0/wvlen; % angular frequency in rad/sec 14 | 15 | %% Deal with the s_factor 16 | % Create the sfactor in each direction and for 'f' and 'b' 17 | s_vector_x_f = create_sfactor(xrange, 'f', omega, eps0, mu0, N(1), Npml(1)); 18 | s_vector_x_b = create_sfactor(xrange, 'b', omega, eps0, mu0, N(1), Npml(1)); 19 | s_vector_y_f = create_sfactor(yrange, 'f', omega, eps0, mu0, N(2), Npml(2)); 20 | s_vector_y_b = create_sfactor(yrange, 'b', omega, eps0, mu0, N(2), Npml(2)); 21 | 22 | 23 | % Fill the 2D space with layers of appropriate s-factors 24 | Sx_f_2D = zeros(N); 25 | Sx_b_2D = zeros(N); 26 | Sy_f_2D = zeros(N); 27 | Sy_b_2D = zeros(N); 28 | 29 | for j = 1:N(2) 30 | Sx_f_2D(:, j) = s_vector_x_f .^-1; 31 | Sx_b_2D(:, j) = s_vector_x_b .^-1; 32 | end 33 | 34 | for i = 1:N(1) 35 | Sy_f_2D(i, :) = s_vector_y_f .^-1; 36 | Sy_b_2D(i, :) = s_vector_y_b .^-1; 37 | end 38 | 39 | % surf(abs(Sy_f_2D)); pause 40 | 41 | % Reshape the 2D s-factors into a 1D s-array 42 | Sx_f_vec = reshape(Sx_f_2D, M, 1); 43 | Sx_b_vec = reshape(Sx_b_2D, M, 1); 44 | Sy_f_vec = reshape(Sy_f_2D, M, 1); 45 | Sy_b_vec = reshape(Sy_b_2D, M, 1); 46 | 47 | % Construct the 1D total s-array into a diagonal matrix 48 | Sxf = spdiags(Sx_f_vec, 0, M, M); 49 | Sxb = spdiags(Sx_b_vec, 0, M, M); 50 | Syf = spdiags(Sy_f_vec, 0, M, M); 51 | Syb = spdiags(Sy_b_vec, 0, M, M); 52 | 53 | 54 | end 55 | 56 | -------------------------------------------------------------------------------- /helper/assign_blocks.m: -------------------------------------------------------------------------------- 1 | function [array_new] = assign_blocks(array_old, input_name, loc, val, xrange, yrange, N) 2 | %ASSIGN_BLOCKS This function adds various permittivity geometries to the 3 | % permittivity array of the simulation domain 4 | %% Input parameters 5 | % array_old: Original permittivity array 6 | % input_name: String with the name of the geometry 7 | % choices: 'custom', 'rect', 'circ', 'poly' 8 | % loc: location of the geometry 9 | % val: value of the permittivity 10 | % xrange: [xmin, xmax] of the simulation domain 11 | % yrange: [ymin, ymax] of the simulation domain 12 | % N: [Nx, Ny] number of cells in the x and y directions 13 | % 14 | %% Output parameters 15 | % array_new: Updated permittivity array 16 | 17 | 18 | %% Main program 19 | input_name = lower(input_name); 20 | switch input_name 21 | %% For 'custom', the loc = boolean function handle of x and y 22 | case 'custom' 23 | array_new = assign_val(array_old, xrange, yrange, loc, val); 24 | 25 | %% For 'rect', loc = [x_start, y_start, x_length, y_length] 26 | case 'rect' 27 | within_rect = @(x, y) x > loc(1) & x < loc(1)+loc(3) & y > loc(2) & y < loc(2)+loc(4); 28 | array_new = assign_val(array_old, xrange, yrange, within_rect, val); 29 | 30 | %% For 'circ', loc = [x_center, y_center, radius] 31 | case 'circ' 32 | r = @(x, y) sqrt((x - loc(1)).^2 + (y - loc(2)).^2); 33 | within_circ = @(x, y) r(x, y) < loc(3); 34 | array_new = assign_val(array_old, xrange, yrange, within_circ, val); 35 | 36 | %% For 'poly', loc = [vertex1; vertex2; ... vertexN]; 37 | case 'poly' 38 | array_new = array_old; 39 | 40 | % Polygon vertex points 41 | xv = loc(:, 1); 42 | yv = loc(:, 2); 43 | 44 | hx = diff(xrange)/N(1); 45 | hy = diff(yrange)/N(2); 46 | 47 | xs = linspace(xrange(1), xrange(2), N(1)); 48 | ys = linspace(yrange(1), yrange(2), N(2)); 49 | 50 | % Bound the search location 51 | xmin_ind = floor((min(xv) - xrange(1))/hx); 52 | xmax_ind = floor((max(xv) - xrange(1))/hx)+2; 53 | ymin_ind = floor((min(yv) - yrange(1))/hy); 54 | ymax_ind = floor((max(yv) - yrange(1))/hy)+2; 55 | 56 | xmin_ind = max(xmin_ind, 1); 57 | xmax_ind = max(xmax_ind, N(1)); 58 | ymin_ind = max(ymin_ind, 1); 59 | ymax_ind = max(ymax_ind, N(2)); 60 | 61 | % Check each grid point within bound and see if it is in polygon 62 | for i = xmin_ind : xmax_ind 63 | for j = ymin_ind : ymax_ind 64 | in_flag = inpolygon(xs(i), ys(j), xv, yv); 65 | 66 | if in_flag == 1 67 | array_new(i, j) = val; 68 | end 69 | end 70 | end 71 | 72 | end 73 | 74 | end 75 | 76 | -------------------------------------------------------------------------------- /helper/assign_val.m: -------------------------------------------------------------------------------- 1 | function val_array = assign_val(val_array, xrange, yrange, region_cond, val_fun) 2 | %% Credits to Wonseok Shin 3 | %% Input parameters 4 | % val_array: 2D array of values to be updated 5 | % xrange: [xmin xmax], range of domain in x-direction including PML 6 | % yrange: [xmin xmax], range of domain in y-direction including PML 7 | % region_cond: function handle with parameters (x, y) that returns true if val_array is to be update at (x, y) 8 | % val_fun: scalar, or function handle with parameters (x, y) that returns new value of val_array at (x,y) 9 | 10 | %% Output Parameters 11 | N = size(val_array); 12 | 13 | xs = linspace(xrange(1), xrange(2), N(1) + 1); % locations of cell edges in x 14 | ys = linspace(yrange(1), yrange(2), N(2) + 1); % locations of cell edges in y 15 | 16 | xs = (xs(1:end-1) + xs(2:end)) / 2; % locations of cell centers in x 17 | ys = (ys(1:end-1) + ys(2:end)) / 2; % locations of cell centers in y 18 | 19 | [X, Y] = ndgrid(xs, ys); % X, Y are Nx-by-Ny arrays 20 | 21 | ind = region_cond(X, Y); % logical indices of cells satisfying condition 22 | if isa(val_fun, 'function_handle') % val_fun is function handle 23 | val = val_fun(X, Y); % val is Nx-by-Ny array 24 | val_array(ind) = val(ind); % update val_array at locations satisfying condition 25 | else % val_fun is scalar 26 | assert(isscalar(val_fun)); 27 | val = val_fun; 28 | val_array(ind) = val; % update val_array to val at locations satisfying condition 29 | end 30 | -------------------------------------------------------------------------------- /helper/bwdmean_w.m: -------------------------------------------------------------------------------- 1 | function avg_array = bwdmean_w(center_array, w) 2 | %% Credits to Wonseok Shin 3 | %% Input Parameters 4 | % center_array: 2D array of values defined at cell centers 5 | % w: 'x' or 'y', direction in which average is taken 6 | 7 | %% Out Parameter 8 | % avg_array: 2D array of averaged values 9 | 10 | center_shifted = circshift(center_array, 1*('xy'==w)); 11 | avg_array = (center_shifted + center_array) / 2; 12 | -------------------------------------------------------------------------------- /helper/createDws.m: -------------------------------------------------------------------------------- 1 | function Dws = createDws(w, s, dL, N) 2 | %% This function creates the sparse derivative operators in FDFD 3 | %% Input parameters 4 | % w: one of 'x', 'y' 5 | % s: one of 'f' and 'b' 6 | % dL: [dx dy] for 2D 7 | % N: [Nx Ny] for 2D 8 | % 9 | %% Output parameters 10 | % Dws: derivative operators 11 | 12 | %% Compute Nx, Ny 13 | Nx = N(1); 14 | Ny = N(2); 15 | 16 | %% Sparse identity matrices 17 | Ix = speye(Nx); 18 | Iy = speye(Ny); 19 | 20 | %% Create derivative operators 21 | switch w 22 | case 'x' 23 | if s == 'f' 24 | dxf = -Ix + circshift(Ix, [0 1]); 25 | Dws = 1/dL(1) * kron(Iy, dxf); 26 | else 27 | dxb = Ix - circshift(Ix, [0 -1]); 28 | Dws = 1/dL(1) * kron(Iy, dxb); 29 | end 30 | 31 | 32 | case 'y' 33 | if s == 'f' 34 | dyf = -Iy + circshift(Iy, [0 1]); 35 | Dws = 1/dL(2) * kron(dyf, Ix); 36 | else 37 | dyb = Iy - circshift(Iy, [0 -1]); 38 | Dws = 1/dL(2) * kron(dyb, Ix); 39 | end 40 | end 41 | 42 | end 43 | 44 | -------------------------------------------------------------------------------- /helper/create_sfactor.m: -------------------------------------------------------------------------------- 1 | function sfactor_array = create_sfactor(wrange, s, omega, eps0, mu0, Nw, Nw_pml) 2 | %% Input Parameters 3 | % wrange: [wmin wmax], range of domain in w-direction including PML 4 | % s: 'b' or 'f', indicating whether s-factor is for Dwb or Dwf 5 | % omega: angular frequency 6 | % eps0: vacuum permittivity 7 | % mu0: vacuum permeability 8 | % Nw: number of cells in w-direction 9 | % Nw_pml: number of cells in PML 10 | 11 | %% Output Parameter 12 | % sfactor_array: 1D array with Nw elements containing PML s-factors for Dws 13 | 14 | eta0 = sqrt(mu0/eps0); % vacuum impedance 15 | m = 3.5; % degree of polynomial grading 16 | lnR = -12; % R: target reflection coefficient for normal incidence 17 | 18 | % find dw 19 | hw = diff(wrange) / (Nw); 20 | dw = Nw_pml * hw; 21 | 22 | % Sigma function 23 | sig_max = -(m+1) * lnR / (2*eta0*dw); 24 | sig_w = @(l) sig_max *(l/dw)^m; 25 | 26 | S = @(l) 1 - 1i*sig_w(l) / (omega*eps0); 27 | 28 | %% PML vector 29 | % initialize sfactor_array 30 | sfactor_array = ones(Nw, 1); 31 | for i = 1:Nw 32 | switch s 33 | case 'f' 34 | if i <= Nw_pml 35 | sfactor_array(i) = S(hw * (Nw_pml - i + 0.5)); 36 | 37 | elseif i > Nw - Nw_pml 38 | sfactor_array(i) = S(hw * (i - (Nw - Nw_pml) - 0.5)); 39 | end 40 | 41 | case 'b' 42 | if i <= Nw_pml 43 | sfactor_array(i) = S(hw * (Nw_pml - i + 1)); 44 | 45 | elseif i > Nw - Nw_pml 46 | sfactor_array(i) = S(hw * (i - (Nw - Nw_pml) - 1)); 47 | end 48 | end 49 | 50 | end 51 | 52 | -------------------------------------------------------------------------------- /solver/solveTE.m: -------------------------------------------------------------------------------- 1 | function [Hz, Ex, Ey, A, omega] = solveTE(L0, wvlen, xrange, yrange, eps_r, Mz, Npml) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen: wavelength in L0 5 | % xrange: [xmin xmax], range of domain in x-direction including PML 6 | % yrange: [ymin ymax], range of domain in y-direction including PML 7 | % eps_r: Nx-by-Ny array of relative permittivity 8 | % Mz: Nx-by-Ny array of magnetic current source density 9 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 10 | 11 | %% Output Parameters 12 | % Ez, Hx, Hy: Nx-by-Ny arrays of H- and E-field components 13 | % dL: [dx dy] in L0 14 | % A: system matrix of A x = b 15 | % omega: angular frequency for given wvlen 16 | 17 | %% Set up the domain parameters. 18 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 19 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 20 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 21 | 22 | N = size(eps_r); % [Nx Ny] 23 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 24 | dL = L./N; % [dx dy] 25 | 26 | M = prod(N); 27 | 28 | omega = 2*pi*c0/wvlen; % angular frequency in rad/sec 29 | 30 | %% Deal with the s_factor 31 | [Sxf, Sxb, Syf, Syb] = S_create(L0, wvlen, xrange, yrange, N, Npml); 32 | 33 | %% Set up the permittivity and permeability in the domain. 34 | eps_x = bwdmean_w(eps0*eps_r, 'x'); 35 | eps_y = bwdmean_w(eps0*eps_r, 'y'); 36 | 37 | % Setup the Teps_x, Teps_y, and Tmu_z matrices 38 | T_eps_x = spdiags(eps_x(:), 0, M, M); 39 | T_eps_y = spdiags(eps_y(:), 0, M, M); 40 | 41 | 42 | %% Construct derivate matrices 43 | Dyb = Syb*createDws('y', 'b', dL, N); 44 | Dxb = Sxb*createDws('x', 'b', dL, N); 45 | Dxf = Sxf*createDws('x', 'f', dL, N); 46 | Dyf = Syf*createDws('y', 'f', dL, N); 47 | 48 | %% Reshape Mz into a vector 49 | mz = reshape(Mz, M, 1); 50 | 51 | 52 | 53 | %% Construct A matrix and b vector 54 | % A = Sx_f *Dxf* T_eps_y^-1 *Sx_b *Dxb + Sy_f *Dyf* T_eps_x^-1* Sy_b*Dyb + omega^2*T_mu_z; 55 | % A = Sx_f*Dxf* T_eps_y^-1 *Sx_b*Dxb + Sy_f*Dyf* T_eps_x^-1* Sy_b*Dyb + omega^2*T_mu_z; 56 | 57 | 58 | I = speye(M); 59 | 60 | A = Dxf*(T_eps_x^-1)*Dxb + Dyf*(T_eps_y^-1)*Dyb + omega^2*mu0*I; 61 | % % A = Dxf* T_eps_y^-1 *Dxb + Dyf* T_eps_x^-1* Dyb + omega^2*T_mu_z; 62 | b = 1i * omega * mz; 63 | 64 | 65 | %% Solve the equation. 66 | if all(b==0) 67 | hz = zeros(size(b)); 68 | else 69 | hz = A\b; 70 | end 71 | Hz = reshape(hz, N); 72 | 73 | ex = 1/(1i*omega) * T_eps_y^-1 * Dyb * hz; 74 | ey = 1/(1i*omega) * T_eps_x^-1 * (-Dxb * hz); 75 | 76 | Ex = reshape(ex, N); 77 | Ey = reshape(ey, N); 78 | 79 | end 80 | -------------------------------------------------------------------------------- /solver/solveTE_BlochX.m: -------------------------------------------------------------------------------- 1 | function [Hz, Ex, Ey, omega_eigs] = solveTE_BlochX(L0, wvlen_center, xrange, yrange, eps_r, Kx, Npml, n) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen_center: wavelength in L0 5 | % xrange: [xmin xmax], range of domain in x-direction including PML 6 | % yrange: [ymin ymax], range of domain in y-direction including PML 7 | % eps_r: Nx-by-Ny array of relative permittivity 8 | % Kx: Bloch wavevector in 1/L0 9 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 10 | % n: number of modes 11 | 12 | %% Output Parameters 13 | % Hz, Ex, Ey: n-by-1 cells of the field profile of each mode 14 | % omega_eigs: n-by-1 array of eigen-frequency for each mode 15 | 16 | %% Set up the domain parameters. 17 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 18 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 19 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 20 | 21 | N = size(eps_r); % [Nx Ny] 22 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 23 | dL = L./(N); % [dx dy] 24 | 25 | M = prod(N); 26 | 27 | omega_center = 2*pi*c0/wvlen_center; % angular frequency in rad/sec 28 | 29 | %% Deal with the s_factor 30 | [Sxf, Sxb, Syf, Syb] = S_create(L0, wvlen_center, xrange, yrange, N, Npml); 31 | 32 | %% Set up the permittivity and permeability in the domain. 33 | eps_x = eps0 * bwdmean_w(eps_r, 'x'); 34 | eps_y = eps0 * bwdmean_w(eps_r, 'y'); 35 | eps_z = eps0 * eps_r; 36 | % mu_z = mu0 .* ones(N); 37 | 38 | % Reshape epsilon into 1D array 39 | vector_eps_x = reshape(eps_x, M, 1); 40 | vector_eps_y = reshape(eps_y, M, 1); 41 | vector_eps_z = reshape(eps_z, M, 1); 42 | % vector_mu_z = reshape(mu_z, M, 1); 43 | 44 | % Setup the Teps_x, Teps_y, and Tmu_z matrices 45 | T_eps_x = spdiags(vector_eps_x, 0, M, M); 46 | T_eps_y = spdiags(vector_eps_y, 0, M, M); 47 | T_eps_z = spdiags(vector_eps_z, 0, M, M); 48 | % T_mu_z = spdiags(vector_mu_z, 0, M, M); 49 | 50 | %% Construct derivate matrices 51 | Dyb = Syb * createDws('y', 'b', dL, N); 52 | Dxb = Sxb * createDws('x', 'b', dL, N); 53 | Dxf = Sxf * createDws('x', 'f', dL, N); 54 | Dyf = Syf * createDws('y', 'f', dL, N); 55 | 56 | %% Construct the averaging matrices 57 | Vxb = abs(createDws('x', 'b', [2 2], N)); 58 | Vxf = abs(createDws('x', 'f', [2 2], N)); 59 | Vyb = abs(createDws('y', 'b', [2 2], N)); 60 | Vyf = abs(createDws('y', 'f', [2 2], N)); 61 | 62 | %% Construct A matrix 63 | size(Dxf*T_eps_x^-1*Dxb) 64 | size(Kx) 65 | 66 | A = -mu0^-1 * (Dxf*T_eps_x^-1*Dxb - 2*1i*Kx * Vxf*T_eps_x^-1*Dxb - Kx^2*T_eps_z^-1 + Dyf*T_eps_y^-1*Dyb); 67 | 68 | %% Solve the eigenvalue equation. 69 | omega_est = omega_center; 70 | 71 | % [vz_temp, omega_sqr] = eigs(A, n, (omega_est)^2, 'StartVector', eps_r(:)-1); 72 | [vz_temp, omega_sqr] = eigs(A, n, (omega_est)^2); 73 | 74 | omega_eigs = sqrt(diag(omega_sqr)); 75 | 76 | %% 77 | Hz = cell(n, 1); 78 | Ex = cell(n, 1); 79 | Ey = cell(n, 1); 80 | 81 | x_vec = transpose(linspace(xrange(1), xrange(2), N(1))); 82 | x_space = repmat(x_vec, [N(2), 1]); 83 | 84 | for i = 1:n 85 | hz_temp = vz_temp(:, i) .* exp(-1i * Kx * x_space); 86 | ex_temp = 1/(1i*omega_eigs(i)) * T_eps_y^-1 * Dyb * hz_temp; 87 | ey_temp = 1/(1i*omega_eigs(i)) * T_eps_x^-1 * (-Dxb * hz_temp); 88 | 89 | Hz{i} = reshape(hz_temp, N); 90 | Ex{i} = reshape(ex_temp, N); 91 | Ey{i} = reshape(ey_temp, N); 92 | end 93 | 94 | %% 95 | % 96 | % 97 | % hx = -1/(1i*omega) * mu0^-1 * Dyf * ez; 98 | % hy = 1/(1i*omega) * mu0^-1 * Dxf * ez; 99 | % 100 | % Hx = reshape(hx, N); 101 | % Hy = reshape(hy, N); 102 | 103 | end -------------------------------------------------------------------------------- /solver/solveTE_mf.m: -------------------------------------------------------------------------------- 1 | function [Hz, Ex, Ey, omega] = solveTE_mf(L0, wvlen0, Omega, Nsb, xrange, yrange, eps_r, mod_reg, mod_phi, Mz0, Npml) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen0: input wavelength in L0 5 | % Omega: modulation frequency in rad/s 6 | % Nsb: number of sideband components 7 | % xrange: [xmin xmax], range of domain in x-direction including PML 8 | % yrange: [ymin ymax], range of domain in y-direction including PML 9 | % eps_r: Nx-by-Ny array of relative permittivity 10 | % Mz: Nx-by-Ny array of magnetic current source density 11 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 12 | 13 | %% Output Parameters 14 | % Hz, Ex, Ey: (2*Nsb+1)-by-1 cell of H- and E-field components for each 15 | % frequency sideband 16 | % omega: (2*Nsb+1)-by-1 array of sideband frequencies 17 | 18 | %% Set up the domain parameters. 19 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 20 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 21 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 22 | 23 | N = size(eps_r); % [Nx Ny] 24 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 25 | dL = L./N; % [dx dy] 26 | 27 | M = prod(N); 28 | 29 | omega0 = 2*pi*c0/wvlen0; % angular frequency in rad/sec 30 | n_sb = -Nsb : 1 : Nsb; 31 | 32 | omega = omega0 + Omega*n_sb; 33 | wvlen = 2*pi*c0./omega; 34 | 35 | %% Deal with the s_factor 36 | Sxf = cell(2*Nsb+1, 1); 37 | Sxb = cell(2*Nsb+1, 1); 38 | Syf = cell(2*Nsb+1, 1); 39 | Syb = cell(2*Nsb+1, 1); 40 | 41 | for i = 1 : (2*Nsb + 1) 42 | [Sxf{i}, Sxb{i}, Syf{i}, Syb{i}] = S_create(L0, wvlen(i), xrange, yrange, N, Npml); 43 | end 44 | 45 | %% Set up the permittivity and permeability in the domain. 46 | 47 | eps_x = eps0 * bwdmean_w(eps_r, 'x'); 48 | eps_y = eps0 * bwdmean_w(eps_r, 'y'); 49 | 50 | T_eps_x = spdiags(eps_x(:), 0, M, M); 51 | T_eps_y = spdiags(eps_y(:), 0, M, M); 52 | 53 | 54 | %% Construct derivate matrices 55 | Dyb = createDws('y', 'b', dL, N); 56 | Dxb = createDws('x', 'b', dL, N); 57 | Dxf = createDws('x', 'f', dL, N); 58 | Dyf = createDws('y', 'f', dL, N); 59 | 60 | %% Reshape Mz into a vector, and convert Mz to Jx, Jy 61 | mz0 = reshape(Mz0, M, 1); 62 | 63 | jx0 = Dyb * mz0; 64 | jy0 = -Dxb * mz0; 65 | 66 | 67 | %% Construct A matrix and b vector: TO DO AFTER LUNCH, 2/16/18 68 | A_i = cell(4*Nsb+2, 1); 69 | 70 | for i = 1 : (2*Nsb + 1) % WRITE THIS PART!! 71 | A11 = -Syb{i}*Dyb * Syf{i}*Dyf - omega(i)^2*mu0*T_eps_y; 72 | A12 = Syb{i}*Dyb * Sxf{i}*Dxf; 73 | A21 = Sxb{i}*Dxb * Syf{i}*Dyf; 74 | A22 = -Sxb{i}*Dxb * Sxf{i}*Dxf - omega(i)^2*mu0*T_eps_x; 75 | 76 | A_i{i} = [A11 A12; 77 | A21 A22]; 78 | end 79 | 80 | b0 = [jx0; jy0]; 81 | 82 | b = zeros(2*M * (2*Nsb+1), 1); 83 | 84 | b((Nsb*2*M)+1 : (Nsb+1)*2*M, 1) = b0; 85 | 86 | %% Account for coupling 87 | tic 88 | if (Nsb > 0) 89 | mod_profile_p = eps0 * mod_reg .* exp(-1i*mod_phi); 90 | mod_profile_m = eps0 * mod_reg .* exp(1i*mod_phi); 91 | 92 | 93 | mod_p_x = bwdmean_w(mod_profile_p, 'x'); 94 | mod_p_y = bwdmean_w(mod_profile_p, 'y'); 95 | mod_m_x = bwdmean_w(mod_profile_m, 'x'); 96 | mod_m_y = bwdmean_w(mod_profile_m, 'y'); 97 | 98 | 99 | T_mod_p_x = spdiags(mod_p_x(:), 0, M, M); 100 | T_mod_p_y = spdiags(mod_p_y(:), 0, M, M); 101 | T_mod_m_x = spdiags(mod_m_x(:), 0, M, M); 102 | T_mod_m_y = spdiags(mod_m_y(:), 0, M, M); 103 | 104 | T_mod_p = blkdiag(T_mod_p_y, T_mod_p_x); 105 | T_mod_m = blkdiag(T_mod_m_y, T_mod_m_x); 106 | 107 | C_p = kron(spdiags([0,omega(1:end-1).^2/2]', 1, 2*Nsb+1, 2*Nsb+1), mu0*T_mod_p); 108 | C_m = kron(spdiags([omega(2:end).^2/2,0]', -1, 2*Nsb+1, 2*Nsb+1), mu0*T_mod_m); 109 | A = blkdiag(A_i{:})+C_p+C_m; 110 | 111 | else 112 | A = A_i{1}; 113 | end 114 | 115 | 116 | time_setup = toc 117 | 118 | % figure; spy(A); pause; 119 | 120 | %% Solve the equation. 121 | tic 122 | if all(b==0) 123 | e_vec = zeros(size(b)); 124 | else 125 | e_vec = A\b; 126 | end 127 | time_solve = toc 128 | 129 | %% 130 | Ex = cell(2*Nsb+1, 1); 131 | Ey = cell(2*Nsb+1, 1); 132 | Hz = cell(2*Nsb+1, 1); 133 | 134 | 135 | for i = 1 : (2*Nsb+1) 136 | start_ind = (i-1)*2*M; 137 | ex_temp = e_vec(start_ind+1 : start_ind+M, 1); 138 | ey_temp = e_vec(start_ind+M+1 : start_ind+2*M, 1); 139 | 140 | hz_temp = -1/(1i*omega(i)*mu0) * (Sxf{i}*Dxf * ey_temp - Syf{i}*Dyf * ex_temp); 141 | 142 | Ex{i} = reshape(ex_temp, N); 143 | Ey{i} = reshape(ey_temp, N); 144 | Hz{i} = reshape(hz_temp, N); 145 | end 146 | 147 | end 148 | -------------------------------------------------------------------------------- /solver/solveTE_modes.m: -------------------------------------------------------------------------------- 1 | function [Hz, Ex, Ey, A, omega] = solveTE_modes(L0, wvlen, xrange, yrange, eps_r, Npml, n) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen: wavelength in L0 5 | % xrange: [xmin xmax], range of domain in x-direction including PML 6 | % yrange: [ymin ymax], range of domain in y-direction including PML 7 | % eps_r: Nx-by-Ny array of relative permittivity 8 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 9 | % n: number of eigen-modes 10 | 11 | %% Output Parameters 12 | % Hz, Ex, Ey: Nx-by-Ny arrays of H- and E-field components 13 | % A: system matrix of A x = lambda x 14 | % omega: n-by-1 array of eigen-frequency for each mode 15 | 16 | %% Set up the domain parameters. 17 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 18 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 19 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 20 | 21 | N = size(eps_r); % [Nx Ny] 22 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 23 | dL = L./N; % [dx dy] 24 | 25 | M = prod(N); 26 | 27 | omega0 = 2*pi*c0/wvlen; % angular frequency in rad/sec 28 | 29 | %% Deal with the s_factor 30 | [Sxf, Sxb, Syf, Syb] = S_create(L0, wvlen, xrange, yrange, N, Npml); 31 | 32 | %% Set up the permittivity and permeability in the domain. 33 | eps_x = bwdmean_w(eps0*eps_r, 'x'); 34 | eps_y = bwdmean_w(eps0*eps_r, 'y'); 35 | 36 | % Setup the Teps_x, Teps_y, and Tmu_z matrices 37 | T_eps_x = spdiags(eps_x(:), 0, M, M); 38 | T_eps_y = spdiags(eps_y(:), 0, M, M); 39 | 40 | 41 | %% Construct derivate matrices 42 | Dyb = Syb*createDws('y', 'b', dL, N); 43 | Dxb = Sxb*createDws('x', 'b', dL, N); 44 | Dxf = Sxf*createDws('x', 'f', dL, N); 45 | Dyf = Syf*createDws('y', 'f', dL, N); 46 | 47 | %% Construct A matrix and b vector 48 | A = Dxf*(T_eps_x^-1)*Dxb + Dyf*(T_eps_y^-1)*Dyb; 49 | 50 | %% Solve the eigenvalue equation. 51 | [hz, eig_vals] = eigs(A, n, -omega0^2*mu0); 52 | 53 | omega = diag(sqrt(-eig_vals/mu0)); 54 | 55 | %% Obtain the actual fields 56 | Hz = cell(n, 1); 57 | Ex = cell(n, 1); 58 | Ey = cell(n, 1); 59 | 60 | for i = 1:n 61 | hz_temp = hz(1:M, i); 62 | ex_temp = 1/(1i*omega(i)) * T_eps_y^-1 * Dyb * hz_temp; 63 | ey_temp = 1/(1i*omega(i)) * T_eps_x^-1 * (-Dxb * hz_temp); 64 | 65 | Hz{i} = reshape(hz_temp, N); 66 | Ex{i} = reshape(ex_temp, N); 67 | Ey{i} = reshape(ey_temp, N); 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /solver/solveTM.m: -------------------------------------------------------------------------------- 1 | function [Ez, Hx, Hy, A, omega] = solveTM(L0, wvlen, xrange, yrange, eps_r, Jz, Npml) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen: wavelength in L0 5 | % xrange: [xmin xmax], range of domain in x-direction including PML 6 | % yrange: [ymin ymax], range of domain in y-direction including PML 7 | % eps_r: Nx-by-Ny array of relative permittivity 8 | % Jz: Nx-by-Ny array of electric current source density 9 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 10 | 11 | %% Output Parameters 12 | % Ez, Hx, Hy: Nx-by-Ny arrays of E- and H-field components 13 | % A: system matrix of A x = b 14 | % omega: angular frequency 15 | 16 | %% Set up the domain parameters. 17 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 18 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 19 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 20 | 21 | N = size(eps_r); % [Nx Ny] 22 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 23 | dL = L./N; % [dx dy] 24 | 25 | M = prod(N); 26 | 27 | omega = 2*pi*c0/wvlen; % angular frequency in rad/sec 28 | 29 | %% Deal with the s_factor 30 | [Sxf, Sxb, Syf, Syb] = S_create(L0, wvlen, xrange, yrange, N, Npml); 31 | 32 | %% Set up the permittivity and permeability in the domain. 33 | 34 | 35 | % Setup the Teps_x, Teps_y, and Tmu_z matrices 36 | T_eps_z = spdiags(eps0*eps_r(:), 0, M, M); 37 | 38 | %% Construct derivate matrices 39 | Dyb = Syb*createDws('y', 'b', dL, N); 40 | Dxb = Sxb*createDws('x', 'b', dL, N); 41 | Dxf = Sxf*createDws('x', 'f', dL, N); 42 | Dyf = Syf*createDws('y', 'f', dL, N); 43 | 44 | %% Reshape Mz into a vector 45 | jz = reshape(Jz, M, 1); 46 | 47 | %% Construct A matrix and b vector 48 | A = Dxf * mu0^-1 * Dxb + Dyf * mu0^-1 * Dyb + omega^2*T_eps_z; 49 | b = 1i * omega * jz; 50 | 51 | 52 | %% Solve the equation. 53 | if all(b==0) 54 | ez = zeros(size(b)); 55 | else 56 | ez = A\b; 57 | end 58 | Ez = reshape(ez, N); 59 | 60 | hx = -1/(1i*omega) * mu0^-1 * Dyb * ez; 61 | hy = 1/(1i*omega) * mu0^-1 * Dxb * ez; 62 | 63 | Hx = reshape(hx, N); 64 | Hy = reshape(hy, N); 65 | 66 | end 67 | -------------------------------------------------------------------------------- /solver/solveTM_BlochX.m: -------------------------------------------------------------------------------- 1 | function [Ez, Hx, Hy, omega_eigs] = solveTM_BlochX(L0, wvlen_min, xrange, yrange, eps_r, Kx, Npml, n) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen_center: wavelength in L0 5 | % xrange: [xmin xmax], range of domain in x-direction including PML 6 | % yrange: [ymin ymax], range of domain in y-direction including PML 7 | % eps_r: Nx-by-Ny array of relative permittivity 8 | % Kx: Bloch wavevector in 1/L0 9 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 10 | % n: number of modes 11 | 12 | %% Output Parameters 13 | % Ez, Hx, Hy: n-by-1 cells of the field profile of each mode 14 | % omega_eigs: n-by-1 array of eigen-frequency for each mode 15 | 16 | %% Set up the domain parameters. 17 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 18 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 19 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 20 | 21 | N = size(eps_r); % [Nx Ny] 22 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 23 | dL = L./(N); % [dx dy] 24 | 25 | M = prod(N); 26 | 27 | omega_max = 2*pi*c0/wvlen_min; % angular frequency in rad/sec 28 | 29 | %% Deal with the s_factor 30 | [Sx_f, Sx_b, Sy_f, Sy_b] = S_create(L0, wvlen_min, xrange, yrange, N, Npml); 31 | 32 | %% Set up the permittivity and permeability in the domain. 33 | 34 | eps_z = eps0 * eps_r; 35 | 36 | % Reshape epsilon into 1D array 37 | vector_eps_z = reshape(eps_z, M, 1); 38 | 39 | % Setup the Teps_x, Teps_y, and Tmu_z matrices 40 | T_eps_z = spdiags(vector_eps_z, 0, M, M); 41 | 42 | %% Construct derivate matrices 43 | Dyb = Sy_b * createDws('y', 'b', dL, N); 44 | Dxb = Sx_b * createDws('x', 'b', dL, N); 45 | Dxf = Sx_f * createDws('x', 'f', dL, N); 46 | Dyf = Sy_f * createDws('y', 'f', dL, N); 47 | 48 | %% Construct A matrix 49 | A = -mu0^-1 * T_eps_z^-1 * (Dxb*Dxf - 1i*Kx*Dxb - 1i*Kx*Dxf - Kx^2*speye(M) + Dyb*Dyf); 50 | 51 | %% Solve the eigenvalue equation. 52 | omega_est = omega_max; 53 | 54 | % [uz_temp, omega_sqr] = eigs(A, n, (omega_est)^2, 'StartVector', eps_r(:)-1); 55 | [uz_temp, omega_sqr] = eigs(A, n, (omega_est)^2); 56 | 57 | omega_eigs = sqrt(diag(omega_sqr)); 58 | 59 | %% 60 | Ez = cell(n, 1); 61 | Hx = cell(n, 1); 62 | Hy = cell(n, 1); 63 | 64 | x_vec = transpose(linspace(xrange(1), xrange(2), N(1))); 65 | x_space = repmat(x_vec, [N(2), 1]); 66 | 67 | for i = 1:n 68 | ez_temp = uz_temp(:, i) .* exp(-1i * Kx * x_space); 69 | hx_temp = -1/(1i*omega_eigs(i)) * mu0^-1 * Dyf * ez_temp; 70 | hy_temp = 1/(1i*omega_eigs(i)) * mu0^-1 * Dxf * ez_temp; 71 | 72 | Ez{i} = reshape(ez_temp, N); 73 | Hx{i} = reshape(hx_temp, N); 74 | Hy{i} = reshape(hy_temp, N); 75 | end 76 | 77 | %% 78 | 79 | 80 | end -------------------------------------------------------------------------------- /solver/solveTM_mf.m: -------------------------------------------------------------------------------- 1 | function [Ez, Hx, Hy, omega] = solveTM_mf(L0, wvlen0, Omega, Nsb, xrange, yrange, eps_r, mod_reg, mod_phi, Jz0, Npml) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen0: input wavelength in L0 5 | % Omega: modulation frequency in rad/s 6 | % Nsb: number of sideband components 7 | % xrange: [xmin xmax], range of domain in x-direction including PML 8 | % yrange: [ymin ymax], range of domain in y-direction including PML 9 | % eps_r: Nx-by-Ny array of relative permittivity 10 | % Jz: Nx-by-Ny array of electric current source density 11 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 12 | 13 | %% Output Parameters 14 | % Ez, Hx, Hy: (2*Nsb+1)-by-1 cell of H- and E-field components for each 15 | % frequency sideband 16 | % omega: (2*Nsb+1)-by-1 array of sideband frequencies 17 | 18 | %% Set up the domain parameters. 19 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 20 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 21 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 22 | 23 | N = size(eps_r); % [Nx Ny] 24 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 25 | dL = L./N; % [dx dy] 26 | 27 | M = prod(N); 28 | 29 | omega0 = 2*pi*c0/wvlen0; % angular frequency in rad/sec 30 | n_sb = -Nsb : 1 : Nsb; 31 | 32 | omega = omega0 + Omega*n_sb; 33 | wvlen = 2*pi*c0./omega; 34 | 35 | %% Deal with the s_factor 36 | Sxf = cell(2*Nsb+1, 1); 37 | Sxb = cell(2*Nsb+1, 1); 38 | Syf = cell(2*Nsb+1, 1); 39 | Syb = cell(2*Nsb+1, 1); 40 | 41 | for i = 1 : (2*Nsb + 1) 42 | [Sxf{i}, Sxb{i}, Syf{i}, Syb{i}] = S_create(L0, wvlen(i), xrange, yrange, N, Npml); 43 | end 44 | 45 | %% Set up the permittivity and permeability in the domain. 46 | 47 | vector_eps = reshape(eps0 * eps_r, M, 1); 48 | 49 | vec_phi = reshape(mod_phi, M, 1); 50 | 51 | T_eps = spdiags(vector_eps, 0, M, M); 52 | 53 | T_phi = spdiags(exp(1i*vec_phi), 0, M, M); 54 | 55 | %% Construct derivate matrices 56 | Dyb = createDws('y', 'b', dL, N); 57 | Dxb = createDws('x', 'b', dL, N); 58 | Dxf = createDws('x', 'f', dL, N); 59 | Dyf = createDws('y', 'f', dL, N); 60 | 61 | %% Reshape Mz into a vector 62 | jz0 = reshape(Jz0, M, 1); 63 | 64 | %% Construct A matrix and b vector 65 | 66 | A_i = cell(2*Nsb+1, 1); 67 | 68 | for i = 1 : (2*Nsb + 1) 69 | A_i{i} = Sxb{i}*Dxb * mu0^-1 * Sxf{i}*Dxf + Syb{i}*Dyb * mu0^-1 * Syf{i}*Dyf + omega(i)^2*T_eps; 70 | end 71 | 72 | size(omega(Nsb+1)) 73 | size(jz0) 74 | b0 = 1i * omega(Nsb+1) * jz0; 75 | 76 | b = zeros(M * (2*Nsb+1), 1); 77 | 78 | b((Nsb*M)+1 : (Nsb+1)*M, 1) = b0; 79 | 80 | %% Account for coupling 81 | tic 82 | if (Nsb > 0) 83 | delta_vec = reshape(eps0*mod_reg, M, 1); 84 | T_delta = spdiags(delta_vec, 0, M, M); 85 | 86 | C_p = kron(spdiags([0,omega(1:end-1).^2/2]', 1, 2*Nsb+1, 2*Nsb+1), T_delta*conj(T_phi)); 87 | C_m = kron(spdiags([omega(2:end).^2/2,0]', -1, 2*Nsb+1, 2*Nsb+1), T_delta * T_phi); 88 | A = blkdiag(A_i{:})+C_p+C_m; 89 | 90 | else 91 | A(1:M, 1:M) = A_i{1}; 92 | end 93 | time_setup = toc 94 | 95 | 96 | %% Solve the equation. 97 | tic 98 | if all(b==0) 99 | ez = zeros(size(b)); 100 | else 101 | ez = A\b; 102 | end 103 | time_solve = toc 104 | 105 | %% 106 | ez_i = cell(2*Nsb+1, 1); 107 | hx_i = cell(2*Nsb+1, 1); 108 | hy_i = cell(2*Nsb+1, 1); 109 | 110 | Ez = cell(2*Nsb+1, 1); 111 | Hx = cell(2*Nsb+1, 1); 112 | Hy = cell(2*Nsb+1, 1); 113 | 114 | for i = 1 : (2*Nsb+1) 115 | ez_i{i} = ez((i-1)*M + 1 : i*M, 1); 116 | 117 | hx_i{i} = -1/(1i*omega(i)) * mu0^-1 * Syf{i}*Dyf * ez_i{i}; 118 | hy_i{i} = 1/(1i*omega(i)) * mu0^-1 * Sxf{i}*Dxf * ez_i{i}; 119 | 120 | 121 | Ez{i} = reshape(ez_i{i}, N); 122 | Hx{i} = reshape(hx_i{i}, N); 123 | Hy{i} = reshape(hy_i{i}, N); 124 | end 125 | 126 | end 127 | -------------------------------------------------------------------------------- /solver/solveTM_modes.m: -------------------------------------------------------------------------------- 1 | function [Ez, Hx, Hy, omega, A] = solveTM_modes(L0, wvlen0, xrange, yrange, eps_r, Npml, n) 2 | %% Input Parameters 3 | % L0: length unit (e.g., L0 = 1e-9 for nm) 4 | % wvlen: wavelength in L0 5 | % xrange: [xmin xmax], range of domain in x-direction including PML 6 | % yrange: [ymin ymax], range of domain in y-direction including PML 7 | % eps_r: Nx-by-Ny array of relative permittivity 8 | % Npml: [Nx_pml Ny_pml], number of cells in x- and y-normal PML 9 | % n: number of eigen-modes 10 | 11 | %% Output Parameters 12 | % Ez, Hx, Hy: Nx-by-Ny arrays of H- and E-field components 13 | % A: system matrix of A x = lambda x 14 | % omega: n-by-1 array of eigen-frequency for each mode 15 | 16 | %% Set up the domain parameters. 17 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 18 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 19 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 20 | 21 | N = size(eps_r); % [Nx Ny] 22 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 23 | dL = L./(N); % [dx dy] 24 | 25 | M = prod(N); 26 | 27 | omega0 = 2*pi*c0/wvlen0; % angular frequency in rad/sec 28 | 29 | 30 | %% Deal with the s_factor 31 | [Sxf, Sxb, Syf, Syb] = S_create(L0, wvlen0, xrange, yrange, N, Npml); 32 | 33 | 34 | %% Set up the permittivity and permeability in the domain. 35 | % Reshape epsilon into 1D array 36 | vector_eps_r = reshape(eps_r, M, 1); 37 | 38 | % Setup the Teps_x, Teps_y, and Tmu_z matrices 39 | T_eps_r = spdiags(vector_eps_r, 0, M, M); 40 | 41 | 42 | %% Construct derivate matrices 43 | Dyb = Syb * createDws('y', 'b', dL, N); 44 | Dxb = Sxb * createDws('x', 'b', dL, N); 45 | Dxf = Sxf * createDws('x', 'f', dL, N); 46 | Dyf = Syf * createDws('y', 'f', dL, N); 47 | 48 | 49 | %% Construct A matrix and b vector 50 | A = T_eps_r^-1 * (Dxb * Dxf + Dyb * Dyf); 51 | 52 | %% Solve for modes 53 | 54 | [ez, eig_vals] = eigs(A, n, -omega0^2*eps0*mu0); 55 | 56 | omega = diag(sqrt(-eig_vals/(eps0*mu0))); 57 | 58 | %% Obtain the actual fields 59 | Ez = cell(n, 1); 60 | Hx = cell(n, 1); 61 | Hy = cell(n, 1); 62 | 63 | for i = 1:n 64 | ez_temp = ez(1:M, i); 65 | hx_temp = -1/(1i*omega(i)) * mu0^-1 * Dyf * ez_temp; 66 | hy_temp = 1/(1i*omega(i)) * mu0^-1 * Dxf * ez_temp; 67 | 68 | Ez{i} = reshape(ez_temp, N); 69 | Hx{i} = reshape(hx_temp, N); 70 | Hy{i} = reshape(hy_temp, N); 71 | end 72 | 73 | end 74 | 75 | -------------------------------------------------------------------------------- /solver/solve_wg_modes.m: -------------------------------------------------------------------------------- 1 | function [Hx, Hy, Hz, Ex, Ey, Ez, beta, A] = solve_wg_modes(L0, wvlen, xrange, yrange, eps_r, n, beta_scale) 2 | %SOLVE_WG_MODE: Solves the waveguide mode given its x-y cross section 3 | 4 | %% Input Parameters 5 | % L0: length unit (e.g., L0 = 1e-6 for microns) 6 | % wvlen: wavelength in L0 7 | % xrange: [xmin xmax], range of domain in x-direction including PML 8 | % yrange: [ymin ymax], range of domain in y-direction including PML 9 | % eps_r: Nx-by-Ny array of relative permittivity 10 | % n: number of modes 11 | 12 | %% Output Parameters 13 | % Hx, Hy, Hz, Ex, Ey, Ez: n-by-1 cells containing of H- and E-field 14 | % components of each mode 15 | % beta: propagation constant of each mode in 1/L0 16 | 17 | %% Set the default value for beta_est 18 | if isempty(beta_scale) 19 | beta_scale = 2; 20 | end 21 | 22 | %% Set up the domain parameters. 23 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 24 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 25 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 26 | omega = 2*pi*c0/wvlen; % angular frequency in rad/sec 27 | 28 | N = size(eps_r); % [Nx Ny] 29 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 30 | dL = L./(N); % [dx dy] 31 | 32 | M = prod(N); 33 | 34 | %% Set up the permittivity and permeability in the domain. 35 | eps_x = bwdmean_w(eps0 * eps_r, 'y'); % average eps for eps_x 36 | eps_y = bwdmean_w(eps0 * eps_r, 'x'); % average eps for eps_y 37 | eps_z = bwdmean_w(eps0 * eps_r, 'x'); 38 | eps_z = bwdmean_w(eps_z, 'y'); % average eps for eps_z 39 | 40 | % Reshape epsilon into 1D array 41 | vector_eps_x = reshape(eps_x, M, 1); 42 | vector_eps_y = reshape(eps_y, M, 1); 43 | vector_eps_z = reshape(eps_z, M, 1); 44 | 45 | % Setup the Teps_x, Teps_y, and Tmu_z matrices 46 | T_eps_x = spdiags(vector_eps_x, 0, M, M); 47 | T_eps_y = spdiags(vector_eps_y, 0, M, M); 48 | T_eps_z = spdiags(vector_eps_z, 0, M, M); 49 | 50 | T_eps_z_inv = spdiags(vector_eps_z.^(-1), 0, M, M); 51 | 52 | %% Construct derivate matrices 53 | Dyb = createDws('y', 'b', dL, N); 54 | Dxb = createDws('x', 'b', dL, N); 55 | Dxf = createDws('x', 'f', dL, N); 56 | Dyf = createDws('y', 'f', dL, N); 57 | 58 | %% Construct A matrix 59 | A = omega^2 * mu0 * blkdiag(T_eps_y, T_eps_x) + ... 60 | + blkdiag(T_eps_y, T_eps_x)* [-Dyf; Dxf] * T_eps_z_inv * [-Dyb Dxb] + ... 61 | + [Dxb; Dyb]*[Dxf Dyf]; 62 | 63 | 64 | %% Solve the equation. 65 | n_diel = sqrt(max(max(real(eps_r)))); 66 | beta_est = abs(2*pi*n_diel / wvlen); 67 | 68 | [h_temp, beta_sqr] = eigs(A, n, (beta_est*beta_scale)^2); 69 | 70 | 71 | %% Rearrange all the fields back into an array 72 | Hx = cell(n, 1); 73 | Hy = cell(n, 1); 74 | Hz = cell(n, 1); 75 | 76 | Ex = cell(n, 1); 77 | Ey = cell(n, 1); 78 | Ez = cell(n, 1); 79 | 80 | beta = zeros(n, 1); 81 | 82 | for i = 1:n 83 | beta(i) = sqrt(beta_sqr(i, i)); 84 | gamma = 1i*beta(i); 85 | 86 | hx = h_temp(1:M, i); 87 | hy = h_temp(M+1:2*M, i); 88 | hz = 1./(gamma) * (Dxf * hx + Dyf * hy); 89 | 90 | ex = 1./(1i*omega) * T_eps_x^-1 * (Dyb * hz + gamma * hy); 91 | ey = 1./(1i*omega) * T_eps_y^-1 * (-gamma * hx - Dxb * hz); 92 | ez = 1./(1i*omega) * T_eps_z^-1 * (Dxb * hy - Dyb * hx); 93 | 94 | %% Reshape all vectors 95 | Hx{i} = reshape(hx, N); 96 | Hy{i} = reshape(hy, N); 97 | Hz{i} = reshape(hz, N); 98 | Ex{i} = reshape(ex, N); 99 | Ey{i} = reshape(ey, N); 100 | Ez{i} = reshape(ez, N); 101 | 102 | end 103 | 104 | 105 | end 106 | 107 | -------------------------------------------------------------------------------- /solver/solve_wg_modes_1D.m: -------------------------------------------------------------------------------- 1 | function [src_profile, beta] = solve_wg_modes_1D(pol, L0, wvlen, xrange, yrange, eps_r, n) 2 | %SOLVE_MODE Summary of this function goes here 3 | % Detailed explanation goes here 4 | 5 | 6 | %% Input Parameters 7 | % pol: polarization, either 'TE' or 'TM'); 8 | % L0: length unit (e.g., L0 = 1e-9 for nm) 9 | % wvlen: wavelength in L0 10 | % xrange: [xmin xmax], range of domain in x-direction including PML 11 | % yrange: [ymin ymax], range of domain in y-direction including PML 12 | % eps_r: Nx-by-Ny array of relative permittivity 13 | % n: order of the mode needed 14 | 15 | %% Output Parameters 16 | % src_profile: current density distribution for the modal source 17 | % beta: propagation constant of the n-th mode in 1/L0 18 | 19 | %% Set up the domain parameters. 20 | eps0 = 8.854e-12 * L0; % vacuum permittivity in farad/L0 21 | mu0 = pi * 4e-7 * L0; % vacuum permeability in henry/L0 22 | c0 = 1/sqrt(eps0*mu0); % speed of light in vacuum in L0/sec 23 | omega = 2*pi*c0/wvlen; % angular frequency in rad/sec 24 | 25 | N = size(eps_r); % [Nx Ny] 26 | L = [diff(xrange) diff(yrange)]; % [Lx Ly] 27 | dL = L./(N); % [dx dy] 28 | 29 | M = prod(N); 30 | 31 | 32 | %% Set up the permittivity and permeability in the domain. 33 | 34 | eps_x = bwdmean_w(eps0*eps_r, 'x'); 35 | 36 | 37 | T_eps = spdiags(eps0*eps_r(:), 0, M, M); 38 | T_eps_x = spdiags(eps_x(:), 0, M, M); 39 | 40 | 41 | 42 | %% Construct derivate matrices 43 | Dxb = createDws('x', 'b', dL, N); 44 | Dxf = createDws('x', 'f', dL, N); 45 | 46 | %% Construct the guesses 47 | 48 | %% Construct A matrix 49 | switch pol 50 | case 'TM' 51 | A = omega^2*mu0*T_eps + Dxf * Dxb; 52 | 53 | case 'TE' 54 | % A = omega^2*mu0*T_eps + T_eps*Dxf*T_eps^-1*Dxb; 55 | A = omega^2*mu0*T_eps + T_eps*Dxf*T_eps_x^-1*Dxb; 56 | end 57 | 58 | %% Solve the equation. 59 | alpha = 1; 60 | n_diel = sqrt(max(max(real(eps_r)))); 61 | beta_est = abs(2*pi*n_diel / wvlen); 62 | 63 | [h_temp, beta_sqr] = eigs(A, n, (beta_est)^2 * alpha); 64 | beta = sqrt(beta_sqr(n, n)); 65 | 66 | %% Output the modal pattern 67 | switch pol 68 | case 'TM' 69 | 70 | ey = h_temp(:, n); 71 | 72 | src_profile = reshape(ey, N); 73 | 74 | 75 | case 'TE' 76 | hy = h_temp(:, n); 77 | src_profile = reshape(hy, N); 78 | end 79 | 80 | 81 | end 82 | 83 | -------------------------------------------------------------------------------- /vis/b2r.m: -------------------------------------------------------------------------------- 1 | function c = b2r(m) 2 | %% Credits to Wonseok Shin 3 | % Color map from blue to white to red. 4 | 5 | if nargin < 1, m = size(get(gcf,'colormap'),1); end 6 | 7 | % From [0 0 1] to [1 1 1] to [1 0 0]; 8 | if mod(m,2) == 0 % color bar depth is even 9 | m1 = m*0.5; 10 | r = (0:m1-1)'/max(m1-1,1); 11 | g = r; 12 | r = [r; ones(m1,1)]; 13 | g = [g; flipud(g)]; 14 | b = flipud(r); 15 | else % color bar depth is odd 16 | m1 = floor(m*0.5); 17 | r = (0:m1-1)'/max(m1,1); 18 | g = r; 19 | r = [r; 1; ones(m1,1)]; 20 | g = [g; 1; flipud(g)]; 21 | b = flipud(r); 22 | end 23 | 24 | c = [r g b]; 25 | -------------------------------------------------------------------------------- /vis/movie_real.m: -------------------------------------------------------------------------------- 1 | function movie_real(array2d, xrange, yrange, Ncycle, Nfpc) 2 | 3 | if nargin < 4 % no Ncycle 4 | Ncycle = 1; % play movie for 1 cycle 5 | end 6 | 7 | if nargin < 5 % no Nframe 8 | Nfpc = 30; % take 30 frames per cycle 9 | end 10 | 11 | % Multiply exp(i*phase) to array2d, and visualize the real part, where phase 12 | % changes from 0 to 2*pi*Ncycle. 13 | Nframe = Ncycle * Nfpc; % total # of frames 14 | for n = 0:Nframe 15 | gcf; 16 | vis_real(array2d * exp(1i*2*pi*n/Nfpc), xrange, yrange) 17 | drawnow; 18 | end 19 | -------------------------------------------------------------------------------- /vis/vis_abs.m: -------------------------------------------------------------------------------- 1 | function vis_abs(array2d, xrange, yrange) 2 | 3 | %% Attach a row and column at the ends. 4 | % The extra row and column are not visualized, but required by pcolor(). 5 | array2d = abs(array2d); 6 | [Nx, Ny] = size(array2d); 7 | array2d = [array2d, array2d(:,1)]; 8 | array2d = [array2d; array2d(1,:)]; 9 | 10 | %% Create the matrices for x-locations (X), y-locations (Y), and color (C). 11 | xs = linspace(xrange(1), xrange(2), Nx+1); 12 | ys = linspace(yrange(1), yrange(2), Ny+1); 13 | [X, Y] = meshgrid(xs, ys); 14 | C = permute(array2d, [2 1]); 15 | 16 | %% Draw with pcolor(). 17 | h = pcolor(X, Y, C); 18 | 19 | %% Make the figure look better. 20 | set(h, 'EdgeColor', 'none'); 21 | set(gca, 'TickDir', 'out'); 22 | axis image; 23 | 24 | colormap('hot') 25 | colorbar; 26 | -------------------------------------------------------------------------------- /vis/vis_real.m: -------------------------------------------------------------------------------- 1 | function vis_real(array2d, xrange, yrange) 2 | 3 | %% Get the maximum magnitude before taking the real part. 4 | cmax = max(abs(array2d(:))); 5 | % cmax = max(real(array2d(:))); 6 | 7 | 8 | %% Attach a row and column at the ends. (Not visualized, but required by pcolor().) 9 | array2d = real(array2d); 10 | [Nx, Ny] = size(array2d); 11 | array2d = [array2d, array2d(:,1)]; 12 | array2d = [array2d; array2d(1,:)]; 13 | 14 | %% Create the matrices for x-locations (X), y-locations (Y), and color (C). 15 | xs = linspace(xrange(1), xrange(2), Nx+1); 16 | ys = linspace(yrange(1), yrange(2), Ny+1); 17 | [X, Y] = meshgrid(xs, ys); 18 | C = permute(array2d, [2 1]); 19 | 20 | %% Draw with pcolor(). 21 | h = pcolor(X, Y, C); 22 | 23 | %% Make the figure look better. 24 | set(h, 'EdgeColor', 'none'); 25 | set(gca, 'TickDir', 'out'); 26 | axis image; 27 | 28 | caxis([-cmax, cmax]); 29 | colormap('b2r') 30 | colorbar; 31 | --------------------------------------------------------------------------------