├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── _config.yml
├── docs
├── Makefile
├── rtfd-requirements.txt
└── source
│ ├── api.rst
│ ├── conf.py
│ ├── images
│ └── slabs.svg
│ ├── index.rst
│ └── intro.rst
├── examples
├── dc-gap-sweep.py
├── dc-sweep-soi-3db-index-profile-200nm-gap.png
├── dc-sweep-soi-3db.png
├── example1.py
├── example2.py
├── gc-sweep-soi.png
├── gc-sweep-soi.py
├── modes_full_vec_ex2
│ ├── material_index
│ │ ├── material_index_xx.png
│ │ ├── material_index_xy.png
│ │ ├── material_index_yx.png
│ │ ├── material_index_yy.png
│ │ └── material_index_zz.png
│ ├── mode_0
│ │ ├── mode_Ex_0.png
│ │ ├── mode_Ey_0.png
│ │ ├── mode_Ez_0.png
│ │ ├── mode_Hx_0.png
│ │ ├── mode_Hy_0.png
│ │ └── mode_Hz_0.png
│ ├── mode_1
│ │ ├── mode_Ex_1.png
│ │ ├── mode_Ey_1.png
│ │ ├── mode_Ez_1.png
│ │ ├── mode_Hx_1.png
│ │ ├── mode_Hy_1.png
│ │ └── mode_Hz_1.png
│ ├── mode_2
│ │ ├── mode_Ex_2.png
│ │ ├── mode_Ey_2.png
│ │ ├── mode_Ez_2.png
│ │ ├── mode_Hx_2.png
│ │ ├── mode_Hy_2.png
│ │ └── mode_Hz_2.png
│ ├── mode_3
│ │ ├── mode_Ex_3.png
│ │ ├── mode_Ey_3.png
│ │ ├── mode_Ez_3.png
│ │ ├── mode_Hx_3.png
│ │ ├── mode_Hy_3.png
│ │ └── mode_Hz_3.png
│ ├── mode_4
│ │ ├── mode_Ex_4.png
│ │ ├── mode_Ey_4.png
│ │ ├── mode_Ez_4.png
│ │ ├── mode_Hx_4.png
│ │ ├── mode_Hy_4.png
│ │ └── mode_Hz_4.png
│ ├── mode_5
│ │ ├── mode_Ex_5.png
│ │ ├── mode_Ey_5.png
│ │ ├── mode_Ez_5.png
│ │ ├── mode_Hx_5.png
│ │ ├── mode_Hy_5.png
│ │ └── mode_Hz_5.png
│ ├── mode_6
│ │ ├── mode_Ex_6.png
│ │ ├── mode_Ey_6.png
│ │ ├── mode_Ez_6.png
│ │ ├── mode_Hx_6.png
│ │ ├── mode_Hy_6.png
│ │ └── mode_Hz_6.png
│ ├── mode_7
│ │ ├── mode_Ex_7.png
│ │ ├── mode_Ey_7.png
│ │ ├── mode_Ez_7.png
│ │ ├── mode_Hx_7.png
│ │ ├── mode_Hy_7.png
│ │ └── mode_Hz_7.png
│ ├── mode_info
│ └── wavelength_n_effs.png
├── modes_semi_vec_ex1
│ ├── example_modes_1_Ey_0.png
│ ├── example_modes_1_Ey_1.png
│ └── example_structure_1.png
├── width-sweep-soi.py
└── width-sweep
│ ├── width-sweep-end-n-profile.png
│ ├── width-sweep-soi.png
│ └── width-sweep-start-n-profile.png
├── modesolverpy
├── __init__.py
├── _analyse.py
├── _mode_solver_lib.py
├── coupling_efficiency.py
├── design.py
├── fractions.gpi
├── mode.gpi
├── mode_solver.py
├── n_effs.gpi
├── structure.gpi
├── structure.py
└── structure_base.py
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | Pipfile.lock
2 | .idea/
3 | notebooks/
4 | scripts/maketags
5 |
6 | examples/example_structure_1.png
7 | examples/modes_semi_vec/
8 |
9 | *.cprof
10 | *.ipynb
11 | .ipynb_checkpoints/
12 |
13 | # Sphinx documentation
14 | doc/_build/
15 | doc/tutorial_ipkiss/_build/
16 | doc/tutorial_psi/_build/
17 | doc/tutorial_picazzo/_build/
18 | tutorial/notebooks/.ipynb_checkpoints/
19 | tutorial/notebooks/lumerical/3_crossing_optimization/.ipynb_checkpoints/
20 |
21 | Icon?
22 | *.prf
23 | *.h5
24 | *.log
25 | *.DS_Store
26 | *.lyrdb
27 | *.7z
28 | *.mpg
29 | *.txt
30 | *.pdf
31 | *.fsp
32 | *.lms
33 | *.icp
34 | *.ldf
35 | *.bak
36 | *.dat
37 | *.mat
38 |
39 | *.xml
40 | *.tar.gz
41 | *.gds
42 | *.json
43 | *.zip
44 | *.rdb
45 | *.sty
46 | *.blg
47 | *.aux
48 | *.bbl
49 | *.bib
50 | *.bst
51 | *.ppt
52 | *.gif
53 | *.pptx
54 | *.js
55 | *.prj
56 | *.swp
57 | *.lms
58 | *.wpr
59 | *.wpu
60 | .pytest_cache
61 |
62 | # Fab-specific
63 | *.gds
64 | *.gds2
65 |
66 | # Random stuff
67 | .noseids
68 | .tags
69 |
70 | # Byte-compiled / optimized / DLL files
71 | __pycache__/
72 | *.py[cod]
73 |
74 | # C extensions
75 | *.so
76 |
77 | # Distribution / packaging
78 | .Python
79 | env/
80 | build/
81 | develop-eggs/
82 | dist/
83 | downloads/
84 | eggs/
85 | lib/
86 | lib64/
87 | parts/
88 | sdist/
89 | var/
90 | *.egg-info/
91 | .installed.cfg
92 | *.egg
93 |
94 | # PyInstaller
95 | # Usually these files are written by a python script from a template
96 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
97 | *.manifest
98 | *.spec
99 |
100 | # Installer logs
101 | pip-log.txt
102 | pip-delete-this-directory.txt
103 |
104 | # Unit test / coverage reports
105 | htmlcov/
106 | .tox/
107 | .coverage
108 | .cache
109 | nosetests.xml
110 | coverage.xml
111 |
112 | # Translations
113 | *.mo
114 | *.pot
115 |
116 | # Django stuff:
117 | *.log
118 |
119 |
120 | # PyBuilder
121 | target/
122 |
123 | # PYC
124 | *.pyc
125 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 jtambasco
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include modesolverpy/*.gpi
2 | include examples/*
3 | include README.md
4 | include LICENSE
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # modesolverpy
2 | Photonic mode solver with a nice interface and output.
3 | * semi-vectorial and fully vectorial options,
4 | * simple structure drawing,
5 | * automated data saving and plotting via Gnuplot,
6 | * some limited (at this stage) data processing (finding MFD of fundamental mode), and
7 | * easily extensible library
8 |
9 | The documentation for this project can be found [here](http://modesolverpy.rtfd.io).
10 |
11 | ## Examples
12 | * [Ex1: Semi-vectorial mode solving of a ridge waveguide](#example-1-semi-vectorial-mode-solving-of-a-ridge-waveguide)
13 | * [Ex2: Fully vectorial mode solving of an anisotropic material waveguide](#example-2-fully-vectorial-mode-solving-of-anisotropic-material)
14 | * [Ex3: Grating-coupler period](#example-3-grating-coupler-period)
15 | * [Ex4: Mode Hybridisation In SOI](#example-4-mode-hybridisation-in-soi)
16 | * [Ex6: Directional Coupler 3dB Length In SOI](#example-5-directional-coupler-3db-length-in-soi)
17 |
18 | ## Example 1: Semi-vectorial mode solving of a ridge waveguide
19 | The following example finds the first two modes of a waveguide with the following, arbitrary, parameters:
20 |
21 | * thin-film thickness: 500nm
22 | * waveguide height: 400nm,
23 | * waveguide width: 500nm,
24 | * refractive index of waveguide: 3,
25 | * refractive index of substrate: 1.4,
26 | * refractive index of cladding: 1, and
27 | * wavelength: 1550nm.
28 |
29 | #### Python script
30 | ```python
31 | import modesolverpy.mode_solver as ms
32 | import modesolverpy.structure as st
33 | import numpy as np
34 |
35 | # All units are relative. [um] were chosen in this case.
36 | x_step = 0.02
37 | y_step = 0.02
38 | wg_height = 0.4
39 | wg_width = 0.5
40 | sub_height = 0.5
41 | sub_width = 2.
42 | clad_height = 0.5
43 | n_sub = 1.4
44 | n_wg = 3.
45 | n_clad = 1.
46 | film_thickness = 0.5
47 | wavelength = 1.55
48 | angle = 75.
49 |
50 | structure = st.RidgeWaveguide(wavelength,
51 | x_step,
52 | y_step,
53 | wg_height,
54 | wg_width,
55 | sub_height,
56 | sub_width,
57 | clad_height,
58 | n_sub,
59 | n_wg,
60 | angle,
61 | n_clad,
62 | film_thickness)
63 |
64 | structure.write_to_file('example_structure_1.dat')
65 |
66 | mode_solver = ms.ModeSolverSemiVectorial(2, semi_vectorial_method='Ey')
67 | mode_solver.solve(structure)
68 | mode_solver.write_modes_to_file('example_modes_1.dat')
69 | ```
70 |
71 | #### Structure
72 |
73 |
74 | ### Modes
75 |
76 |
77 | ## Example 2: Fully vectorial mode solving of an anisotropic material waveguide
78 | The following looks at a contrived ridge waveguide in Z-cut KTP.
79 |
80 | The simulation outputs:
81 | * 5 plots for each refractive index axis (n_xx, n_xy, n_yx, n_yy and n_zz),
82 | * 48 plots for Ex, Ey, Ez, Hx, Hy and Hz,
83 | * 8 effective index values, one for each mode,
84 | * a wavelength sweep of the waveguide (plotting n_eff vs wavelength for each mode),
85 | * whether a mode is qTE or qTM and the percentage overlap with TE and TM, and
86 | * the group velocity of the mode.
87 |
88 | The waveguide parameters are:
89 | * thin-film thickness: 1.2um,
90 | * waveguide height: 800nm,
91 | * waveguide width: 1.2um,
92 | * refractive index of waveguide: used Sellmeier equations to get n_xx, n_yy, n_zz at 1550nm,
93 | * refractive index of substrate: used Sellmeier equation to get SiO2 at 1550nm,
94 | * refractive index of cladding: 1, and
95 | * wavelength: 1550nm.
96 |
97 | ### Python script
98 | ```python
99 | import modesolverpy.mode_solver as ms
100 | import modesolverpy.structure as st
101 | import opticalmaterialspy as mat
102 | import numpy as np
103 |
104 | wl = 1.55
105 | x_step = 0.06
106 | y_step = 0.06
107 | wg_height = 0.8
108 | wg_width = 1.8
109 | sub_height = 1.0
110 | sub_width = 4.
111 | clad_height = 1.0
112 | film_thickness = 1.2
113 | angle = 60.
114 |
115 | def struct_func(n_sub, n_wg, n_clad):
116 | return st.RidgeWaveguide(wl, x_step, y_step, wg_height, wg_width,
117 | sub_height, sub_width, clad_height,
118 | n_sub, n_wg, angle, n_clad, film_thickness)
119 |
120 | n_sub = mat.SiO2().n(wl)
121 | n_wg_xx = mat.Ktp('x').n(wl)
122 | n_wg_yy = mat.Ktp('y').n(wl)
123 | n_wg_zz = mat.Ktp('z').n(wl)
124 | n_clad = mat.Air().n()
125 |
126 | struct_xx = struct_func(n_sub, n_wg_xx, n_clad)
127 | struct_yy = struct_func(n_sub, n_wg_yy, n_clad)
128 | struct_zz = struct_func(n_sub, n_wg_zz, n_clad)
129 |
130 | struct_ani = st.StructureAni(struct_xx, struct_yy, struct_zz)
131 | struct_ani.write_to_file()
132 |
133 | solver = ms.ModeSolverFullyVectorial(8)
134 | solver.solve(struct_ani)
135 | solver.write_modes_to_file()
136 |
137 | solver.solve_ng(struct_ani, 1.55, 0.01)
138 |
139 | solver.solve_sweep_wavelength(struct_ani, np.linspace(1.501, 1.60, 21))
140 | ```
141 |
142 | ### Group Velocity
143 | The group velocity at 1550nm for each mode is:
144 | ```
145 | # modes_full_vec/ng.dat
146 | # Mode idx, Group index
147 | 0,1.776
148 | 1,1.799
149 | 2,1.826
150 | 3,1.847
151 | 4,1.841
152 | 5,1.882
153 | 6,1.872
154 | 7,1.871
155 | ```
156 |
157 | ### Structure
158 |
159 |
160 |
161 | ### Modes
162 | Only the first 4 (out of 8) modes are shown, and only the E-fields are shown (not H-fields). For the rest of the images, look in the example folder or run the script.
163 |
164 | A_{x,y,z} give the percentage power of that particular E-field component with respect to the total of all components.
165 |
166 | Mode types:
167 | ```
168 | # modes_full_vec/mode_info
169 | # Mode idx, Mode type, % in major direction, n_eff
170 | 0,qTE,97.39,1.643
171 | 1,qTM,92.54,1.640
172 | 2,qTE,90.60,1.576
173 | 3,qTM,91.41,1.571
174 | 4,qTE,89.48,1.497
175 | 5,qTM,86.70,1.475
176 | 6,qTE,89.47,1.447
177 | 7,qTM,68.35,1.437
178 | ```
179 |
180 |
181 |
182 |
183 |
184 | ### Wavelength Sweep
185 |
186 |
187 | ## Example 3: Grating-coupler period
188 | Analytic calculation of the grating coupler period for various duty-cycles in SOI.
189 |
190 | Seems to match well with the periods in [Taillaert et al., _Grating Couplers for
191 | Coupling between Optical Fibers and Nanophotonic Waveguides_, IOP Science, 2006](http://iopscience.iop.org/article/10.1143/JJAP.45.6071/meta).
192 |
193 | ```python
194 | import modesolverpy.mode_solver as ms
195 | import modesolverpy.structure as st
196 | import modesolverpy.design as de
197 | import opticalmaterialspy as mat
198 | import numpy as np
199 |
200 | wls = [1.5, 1.55, 1.6]
201 | x_step = 0.05
202 | y_step = 0.05
203 | etch_depth = 0.07
204 | wg_width = 10
205 | sub_height = 0.5
206 | sub_width = 14.
207 | clad_height = 0.5
208 | film_thickness = 0.22
209 | polarisation = 'TE'
210 | dcs = np.linspace(20, 80, 61) / 100
211 |
212 | ed1 = etch_depth
213 | ft1 = film_thickness
214 | ed2 = ft1 - ed1
215 | ft2 = ed2
216 |
217 | periods = []
218 | periods.append(dcs)
219 |
220 | for wl in wls:
221 | ngc = []
222 | for ed, ft in [(ed1, ft1), (ed2, ft2)]:
223 | def struct_func(n_sub, n_wg, n_clad):
224 | return st.RidgeWaveguide(wl, x_step, y_step, ed, wg_width,
225 | sub_height, sub_width, clad_height,
226 | n_sub, n_wg, None, n_clad, ft)
227 |
228 | n_sub = mat.SiO2().n(wl)
229 | n_wg_xx = 3.46
230 | n_wg_yy = 3.46
231 | n_wg_zz = 3.46
232 | n_clad = mat.Air().n()
233 |
234 | struct_xx = struct_func(n_sub, n_wg_xx, n_clad)
235 | struct_yy = struct_func(n_sub, n_wg_yy, n_clad)
236 | struct_zz = struct_func(n_sub, n_wg_zz, n_clad)
237 |
238 | struct_ani = st.StructureAni(struct_xx, struct_yy, struct_zz)
239 | #struct_ani.write_to_file()
240 |
241 | solver = ms.ModeSolverFullyVectorial(4)
242 | solver.solve(struct_ani)
243 | #solver.write_modes_to_file()
244 |
245 | if polarisation == 'TE':
246 | ngc.append(np.round(np.real(solver.n_effs_te), 4)[0])
247 | elif polarisation == 'TM':
248 | ngc.append(np.round(np.real(solver.n_effs_tm), 4)[0])
249 |
250 | period = de.grating_coupler_period(wl, dcs*ngc[0]+(1-dcs)*ngc[1], n_clad, 8, 1)
251 | periods.append(period)
252 |
253 | filename = 'dc-sweep-%s-%inm-etch-%i-film.dat' % (polarisation, etch_depth*1000, film_thickness*1000)
254 | np.savetxt(filename, np.array(periods).T, delimiter=',', header=','.join([str(val) for val in wls]))
255 | print(np.c_[periods])
256 | ```
257 |
258 |
259 |
260 | ## Example 4: Mode Hybridisation In SOI
261 | Simulation of mode hybridisation in 220nm thick fully-etched SOI ridge
262 | waveguides.
263 |
264 | Results look the same as those found in [Daoxin Dai and Ming Zhang, "Mode hybridization and conversion in silicon-on-insulator nanowires with angled sidewalls," Opt. Express 23, 32452-32464 (2015)](https://www.osapublishing.org/oe/abstract.cfm?uri=oe-23-25-32452).
265 |
266 | ```python
267 | import modesolverpy.mode_solver as ms
268 | import modesolverpy.structure as st
269 | import opticalmaterialspy as mat
270 | import numpy as np
271 |
272 | wl = 1.55
273 | x_step = 0.02
274 | y_step = 0.02
275 | etch_depth = 0.22
276 | wg_widths = np.arange(0.3, 2., 0.05)
277 | sub_height = 1.
278 | sub_width = 4.
279 | clad_height = 1.
280 | film_thickness = 0.22
281 |
282 | n_sub = mat.SiO2().n(wl)
283 | n_clad = mat.Air().n(wl)
284 | n_wg = mat.RefractiveIndexWeb(
285 | 'https://refractiveindex.info/?shelf=main&book=Si&page=Li-293K').n(wl)
286 |
287 | r = []
288 | for w in wg_widths:
289 | r.append(
290 | st.RidgeWaveguide(wl, x_step, y_step, etch_depth, w, sub_height,
291 | sub_width, clad_height, n_sub, n_wg, None, n_clad,
292 | film_thickness))
293 |
294 | r[0].write_to_file('start_n_profile.dat')
295 | r[-1].write_to_file('end_n_profile.dat')
296 |
297 | solver = ms.ModeSolverFullyVectorial(6)
298 | solver.solve_sweep_structure(r, wg_widths, x_label='Taper width', fraction_mode_list=[1,2])
299 | solver.write_modes_to_file()
300 | ```
301 |
302 |
303 |
304 |
305 | ## Example 5: Directional Coupler 3dB Length In SOI
306 | Analytic calculation of 3dB coupling length into two parallel SOI waveguides
307 | with a varying gap at 3 different TE wavelengths.
308 |
309 | An example refractive index profile for the two waveguides spaced 200nm is
310 | shown.
311 |
312 | ```python
313 | import modesolverpy.mode_solver as ms
314 | import modesolverpy.structure as st
315 | import modesolverpy.design as de
316 | import opticalmaterialspy as mat
317 | import numpy as np
318 | import tqdm
319 |
320 | wls = [1.5, 1.55, 1.6]
321 | x_step = 0.02
322 | y_step = 0.02
323 | etch_depth = 0.22
324 | wg_width = 0.44
325 | sub_height = 0.5
326 | sub_width = 2.
327 | clad_height = 0.5
328 | film_thickness = 0.22
329 | gaps = np.linspace(0.1, 0.5, 11)
330 |
331 | for wl in wls:
332 | lengths = []
333 |
334 | n_sub = mat.SiO2().n(wl)
335 | n_clad = mat.Air().n(wl)
336 | n_wg = 3.476
337 |
338 | for gap in tqdm.tqdm(gaps):
339 | r = st.WgArray(wl, x_step, y_step, etch_depth, [wg_width, wg_width], gap,
340 | sub_height, sub_width, clad_height, n_sub, n_wg, None)
341 | #r.write_to_file()
342 |
343 | solver = ms.ModeSolverFullyVectorial(2)
344 | solver.solve(r)
345 | n1 = solver.n_effs_te[0]
346 | n2 = solver.n_effs_te[1]
347 | lengths.append(de.directional_coupler_lc(wl*1000, n1, n2)/2)
348 |
349 | filename = 'dc-sweep-%inm-%s-%inm-etch-%i-film.dat' % (wl*1000, 'TE', etch_depth*1000, film_thickness*1000)
350 | np.savetxt(filename, np.c_[gaps, lengths], delimiter=',', header='Coupling lengths (50\%)')
351 | ```
352 |
353 |
354 |
355 |
356 | ## Installation
357 | It is recommend to install `modesolverpy` either via:
358 |
359 | ### Ubuntu/Mint/Debian:
360 | ```bash
361 | pip3 install modesolverpy # or pip2 install modesolverpy
362 | apt install gnuplot
363 | ```
364 |
365 | ### Arch Linux:
366 | ```bash
367 | yaourt -S python-modesolverpy
368 | ```
369 |
370 | ### Dependencies
371 | If installing using the [Arch Linux AUR package](https://aur.archlinux.org/packages/python-modesolverpy/) or `pip`, dependencies will be automatically downloaded and installed, if not, one should ensure the following dependencies are installed:
372 |
373 | Either Gnuplot or Matplotlib can be used for plotting; I am a Gnuplot user to the code was written with it in mind. If both Gnuplot and Matplotlib are installed, the code will default to Gnuplot.
374 |
375 | * [setuptools](https://pypi.python.org/pypi/setuptools),
376 | * [numpy](http://www.numpy.org/),
377 | * [scipy](https://www.scipy.org/),
378 | * [tqdm](https://pypi.python.org/pypi/tqdm), and
379 | * [opticalmaterialspy](https://github.com/jtambasco/opticalmaterialspy).
380 |
381 | #### Plotting
382 | EITHER:
383 |
384 | * [Gnuplot](http://www.gnuplot.info/).
385 | * [gnuplotpy](https://github.com/jtambasco/gnuplotpy),
386 |
387 | OR:
388 |
389 | * [matplotlib](https://matplotlib.org/),
390 |
391 | ## Acknowledgments
392 | This finite difference mode solver is based on a modified version of [EMpy](https://github.com/lbolla/EMpy).
393 |
394 | Thank you to [Inna Krasnokutska](https://github.com/ikrasnokutska) for testing.
395 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SPHINXPROJ = modesolverpy
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/docs/rtfd-requirements.txt:
--------------------------------------------------------------------------------
1 | modesolverpy
2 | sphinx-automodapi
3 |
--------------------------------------------------------------------------------
/docs/source/api.rst:
--------------------------------------------------------------------------------
1 | API documentation
2 | =================
3 |
4 | ************
5 | Mode Solvers
6 | ************
7 |
8 | .. automodapi:: modesolverpy.mode_solver
9 |
10 | :inherited-members:
11 | :no-heading:
12 |
13 | **********************
14 | Pre-defined Structures
15 | **********************
16 |
17 | .. automodapi:: modesolverpy.structure
18 |
19 | :inherited-members:
20 | :no-heading:
21 |
22 | ******************
23 | Structure Creation
24 | ******************
25 |
26 | .. automodapi:: modesolverpy.structure_base
27 |
28 | :inherited-members:
29 | :no-heading:
30 |
31 | ************
32 | Design Tools
33 | ************
34 |
35 | .. automodapi:: modesolverpy.design
36 |
37 | :inherited-members:
38 | :no-heading:
39 |
40 | *******************
41 | Coupling Efficiency
42 | *******************
43 |
44 | .. automodapi:: modesolverpy.coupling_efficiency
45 |
46 | :inherited-members:
47 | :no-heading:
48 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # modesolverpy documentation build configuration file, created by
5 | # sphinx-quickstart on Fri May 11 13:21:10 2018.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 |
20 | import os
21 | import sys
22 | sys.path.insert(0, os.path.abspath('../..'))
23 |
24 |
25 | # -- General configuration ------------------------------------------------
26 |
27 | # If your documentation needs a minimal Sphinx version, state it here.
28 | #
29 | # needs_sphinx = '1.0'
30 |
31 | # Add any Sphinx extension module names here, as strings. They can be
32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
33 | # ones.
34 | extensions = ['sphinx.ext.autodoc',
35 | 'sphinx.ext.mathjax',
36 | 'sphinx.ext.viewcode',
37 | 'sphinx.ext.githubpages',
38 | 'sphinx.ext.napoleon',
39 | 'sphinx_automodapi.automodapi',
40 | 'sphinx_automodapi.smart_resolver']
41 |
42 | # Add any paths that contain templates here, relative to this directory.
43 | templates_path = ['_templates']
44 |
45 | # The suffix(es) of source filenames.
46 | # You can specify multiple suffix as a list of string:
47 | #
48 | # source_suffix = ['.rst', '.md']
49 | source_suffix = '.rst'
50 |
51 | # The master toctree document.
52 | master_doc = 'index'
53 |
54 | # General information about the project.
55 | project = 'modesolverpy'
56 | copyright = '2018, Jean-Luc Tambasco'
57 | author = 'Jean-Luc Tambasco'
58 |
59 | # The version info for the project you're documenting, acts as replacement for
60 | # |version| and |release|, also used in various other places throughout the
61 | # built documents.
62 | #
63 | # The short X.Y version.
64 | version = '0.4.4'
65 | # The full version, including alpha/beta/rc tags.
66 | release = '0.4.4'
67 |
68 | # The language for content autogenerated by Sphinx. Refer to documentation
69 | # for a list of supported languages.
70 | #
71 | # This is also used if you do content translation via gettext catalogs.
72 | # Usually you set "language" from the command line for these cases.
73 | language = None
74 |
75 | # List of patterns, relative to source directory, that match files and
76 | # directories to ignore when looking for source files.
77 | # This patterns also effect to html_static_path and html_extra_path
78 | exclude_patterns = []
79 |
80 | # The name of the Pygments (syntax highlighting) style to use.
81 | pygments_style = 'tango'
82 |
83 | # If true, `todo` and `todoList` produce output, else they produce nothing.
84 | todo_include_todos = False
85 |
86 |
87 | # -- Options for HTML output ----------------------------------------------
88 |
89 | # The theme to use for HTML and HTML Help pages. See the documentation for
90 | # a list of builtin themes.
91 | #
92 | html_theme = 'sphinx_rtd_theme'
93 |
94 | # Theme options are theme-specific and customize the look and feel of a theme
95 | # further. For a list of options available for each theme, see the
96 | # documentation.
97 | #
98 | # html_theme_options = {}
99 |
100 | # Add any paths that contain custom static files (such as style sheets) here,
101 | # relative to this directory. They are copied after the builtin static files,
102 | # so a file named "default.css" will overwrite the builtin "default.css".
103 | html_static_path = ['_static']
104 |
105 |
106 | # -- Options for HTMLHelp output ------------------------------------------
107 |
108 | # Output file base name for HTML help builder.
109 | htmlhelp_basename = 'modesolverpydoc'
110 |
111 |
112 | # -- Options for LaTeX output ---------------------------------------------
113 |
114 | latex_elements = {
115 | # The paper size ('letterpaper' or 'a4paper').
116 | #
117 | # 'papersize': 'letterpaper',
118 |
119 | # The font size ('10pt', '11pt' or '12pt').
120 | #
121 | # 'pointsize': '10pt',
122 |
123 | # Additional stuff for the LaTeX preamble.
124 | #
125 | # 'preamble': '',
126 |
127 | # Latex figure (float) alignment
128 | #
129 | # 'figure_align': 'htbp',
130 | }
131 |
132 | # Grouping the document tree into LaTeX files. List of tuples
133 | # (source start file, target name, title,
134 | # author, documentclass [howto, manual, or own class]).
135 | latex_documents = [
136 | (master_doc, 'modesolverpy.tex', 'modesolverpy Documentation',
137 | 'Jean-Luc Tambasco', 'manual'),
138 | ]
139 |
140 |
141 | # -- Options for manual page output ---------------------------------------
142 |
143 | # One entry per manual page. List of tuples
144 | # (source start file, name, description, authors, manual section).
145 | man_pages = [
146 | (master_doc, 'modesolverpy', 'modesolverpy Documentation',
147 | [author], 1)
148 | ]
149 |
150 |
151 | # -- Options for Texinfo output -------------------------------------------
152 |
153 | # Grouping the document tree into Texinfo files. List of tuples
154 | # (source start file, target name, title, author,
155 | # dir menu entry, description, category)
156 | texinfo_documents = [
157 | (master_doc, 'modesolverpy', 'modesolverpy Documentation',
158 | author, 'modesolverpy', 'One line description of project.',
159 | 'Miscellaneous'),
160 | ]
161 |
162 | # If true, the current module name will be prepended to all description
163 | # unit titles (such as .. function::).
164 | add_module_names = True
165 |
--------------------------------------------------------------------------------
/docs/source/images/slabs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
761 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to modesolverpy's documentation!
2 | ================================================
3 |
4 | Contents:
5 |
6 | .. toctree::
7 | :maxdepth: 2
8 |
9 | intro
10 | api
11 |
--------------------------------------------------------------------------------
/docs/source/intro.rst:
--------------------------------------------------------------------------------
1 | Introduction
2 | ============
3 |
4 | This is the documentation for the modesolverpy API.
5 |
6 |
--------------------------------------------------------------------------------
/examples/dc-gap-sweep.py:
--------------------------------------------------------------------------------
1 | import modesolverpy.mode_solver as ms
2 | import modesolverpy.structure as st
3 | import modesolverpy.design as de
4 | import opticalmaterialspy as mat
5 | import numpy as np
6 | import tqdm
7 |
8 | wls = [1.5, 1.55, 1.6]
9 | x_step = 0.02
10 | y_step = 0.02
11 | etch_depth = 0.22
12 | wg_width = 0.44
13 | sub_height = 0.5
14 | sub_width = 2.
15 | clad_height = 0.5
16 | film_thickness = 0.22
17 | gaps = np.linspace(0.1, 0.5, 11)
18 |
19 | for wl in wls:
20 | lengths = []
21 |
22 | n_sub = mat.SiO2().n(wl)
23 | n_clad = mat.Air().n(wl)
24 | n_wg = 3.476
25 |
26 | for gap in tqdm.tqdm(gaps):
27 | r = st.WgArray(wl, x_step, y_step, etch_depth, [wg_width, wg_width], gap,
28 | sub_height, sub_width, clad_height, n_sub, n_wg, None)
29 | #r.write_to_file()
30 |
31 | solver = ms.ModeSolverFullyVectorial(2)
32 | solver.solve(r)
33 | n1 = solver.n_effs_te[0]
34 | n2 = solver.n_effs_te[1]
35 | lengths.append(de.directional_coupler_lc(wl*1000, n1, n2)/2)
36 |
37 | filename = 'dc-sweep-%inm-%s-%inm-etch-%i-film.dat' % (wl*1000, 'TE', etch_depth*1000, film_thickness*1000)
38 | np.savetxt(filename, np.c_[gaps, lengths], delimiter=',', header='Coupling lengths (50\%)')
39 |
--------------------------------------------------------------------------------
/examples/dc-sweep-soi-3db-index-profile-200nm-gap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/dc-sweep-soi-3db-index-profile-200nm-gap.png
--------------------------------------------------------------------------------
/examples/dc-sweep-soi-3db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/dc-sweep-soi-3db.png
--------------------------------------------------------------------------------
/examples/example1.py:
--------------------------------------------------------------------------------
1 | import modesolverpy.mode_solver as ms
2 | import modesolverpy.structure as st
3 | import numpy as np
4 |
5 | # All units are relative. [um] were chosen in this case.
6 | x_step = 0.02
7 | y_step = 0.02
8 | wg_height = 0.4
9 | wg_width = 0.5
10 | sub_height = 0.5
11 | sub_width = 2.
12 | clad_height = 0.5
13 | n_sub = 1.4
14 | n_wg = 3.
15 | n_clad = 1.
16 | film_thickness = 0.5
17 | wavelength = 1.55
18 | angle = 75.
19 |
20 | structure = st.RidgeWaveguide(wavelength,
21 | x_step,
22 | y_step,
23 | wg_height,
24 | wg_width,
25 | sub_height,
26 | sub_width,
27 | clad_height,
28 | n_sub,
29 | n_wg,
30 | angle,
31 | n_clad,
32 | film_thickness)
33 |
34 | structure.write_to_file('example_structure_1.dat')
35 |
36 | mode_solver = ms.ModeSolverSemiVectorial(2, semi_vectorial_method='Ey')
37 | mode_solver.solve(structure)
38 | mode_solver.write_modes_to_file('example_modes_1.dat')
39 |
--------------------------------------------------------------------------------
/examples/example2.py:
--------------------------------------------------------------------------------
1 | import modesolverpy.mode_solver as ms
2 | import modesolverpy.structure as st
3 | import opticalmaterialspy as mat
4 | import numpy as np
5 |
6 | wl = 1.55
7 | x_step = 0.06
8 | y_step = 0.06
9 | wg_height = 0.8
10 | wg_width = 1.8
11 | sub_height = 1.0
12 | sub_width = 4.
13 | clad_height = 1.0
14 | film_thickness = 1.2
15 | angle = 60.
16 |
17 | def struct_func(n_sub, n_wg, n_clad):
18 | return st.RidgeWaveguide(wl, x_step, y_step, wg_height, wg_width,
19 | sub_height, sub_width, clad_height,
20 | n_sub, n_wg, angle, n_clad, film_thickness)
21 |
22 | n_sub = mat.SiO2().n(wl)
23 | n_wg_xx = mat.Ktp('x').n(wl)
24 | n_wg_yy = mat.Ktp('y').n(wl)
25 | n_wg_zz = mat.Ktp('z').n(wl)
26 | n_clad = mat.Air().n()
27 |
28 | struct_xx = struct_func(n_sub, n_wg_xx, n_clad)
29 | struct_yy = struct_func(n_sub, n_wg_yy, n_clad)
30 | struct_zz = struct_func(n_sub, n_wg_zz, n_clad)
31 |
32 | struct_ani = st.StructureAni(struct_xx, struct_yy, struct_zz)
33 | struct_ani.write_to_file()
34 |
35 | solver = ms.ModeSolverFullyVectorial(8)
36 | solver.solve(struct_ani)
37 | solver.write_modes_to_file()
38 |
39 | solver.solve_ng(struct_ani, 0.01)
40 |
41 | solver.solve_sweep_wavelength(struct_ani, np.linspace(1.501, 1.60, 21))
42 |
--------------------------------------------------------------------------------
/examples/gc-sweep-soi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/gc-sweep-soi.png
--------------------------------------------------------------------------------
/examples/gc-sweep-soi.py:
--------------------------------------------------------------------------------
1 | import modesolverpy.mode_solver as ms
2 | import modesolverpy.structure as st
3 | import modesolverpy.design as de
4 | import opticalmaterialspy as mat
5 | import numpy as np
6 |
7 | wls = [1.5, 1.55, 1.6]
8 | x_step = 0.05
9 | y_step = 0.05
10 | etch_depth = 0.07
11 | wg_width = 10
12 | sub_height = 0.5
13 | sub_width = 14.
14 | clad_height = 0.5
15 | film_thickness = 0.22
16 | polarisation = 'TE'
17 | dcs = np.linspace(20, 80, 61) / 100
18 |
19 | ed1 = etch_depth
20 | ft1 = film_thickness
21 | ed2 = ft1 - ed1
22 | ft2 = ed2
23 |
24 | periods = []
25 | periods.append(dcs)
26 |
27 | for wl in wls:
28 | ngc = []
29 | for ed, ft in [(ed1, ft1), (ed2, ft2)]:
30 | def struct_func(n_sub, n_wg, n_clad):
31 | return st.RidgeWaveguide(wl, x_step, y_step, ed, wg_width,
32 | sub_height, sub_width, clad_height,
33 | n_sub, n_wg, None, n_clad, ft)
34 |
35 | n_sub = mat.SiO2().n(wl)
36 | n_wg_xx = 3.46
37 | n_wg_yy = 3.46
38 | n_wg_zz = 3.46
39 | n_clad = mat.Air().n()
40 |
41 | struct_xx = struct_func(n_sub, n_wg_xx, n_clad)
42 | struct_yy = struct_func(n_sub, n_wg_yy, n_clad)
43 | struct_zz = struct_func(n_sub, n_wg_zz, n_clad)
44 |
45 | struct_ani = st.StructureAni(struct_xx, struct_yy, struct_zz)
46 | #struct_ani.write_to_file()
47 |
48 | solver = ms.ModeSolverFullyVectorial(4)
49 | solver.solve(struct_ani)
50 | #solver.write_modes_to_file()
51 |
52 | if polarisation == 'TE':
53 | ngc.append(np.round(np.real(solver.n_effs_te), 4)[0])
54 | elif polarisation == 'TM':
55 | ngc.append(np.round(np.real(solver.n_effs_tm), 4)[0])
56 |
57 | period = de.grating_coupler_period(wl, dcs*ngc[0]+(1-dcs)*ngc[1], n_clad, 8, 1)
58 | periods.append(period)
59 |
60 | filename = 'gc-sweep-%s-%inm-etch-%i-film.dat' % (polarisation, etch_depth*1000, film_thickness*1000)
61 | np.savetxt(filename, np.array(periods).T, delimiter=',', header=','.join([str(val) for val in wls]))
62 | print(np.c_[periods])
63 |
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/material_index/material_index_xx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/material_index/material_index_xx.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/material_index/material_index_xy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/material_index/material_index_xy.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/material_index/material_index_yx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/material_index/material_index_yx.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/material_index/material_index_yy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/material_index/material_index_yy.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/material_index/material_index_zz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/material_index/material_index_zz.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_0/mode_Ex_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_0/mode_Ex_0.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_0/mode_Ey_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_0/mode_Ey_0.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_0/mode_Ez_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_0/mode_Ez_0.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_0/mode_Hx_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_0/mode_Hx_0.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_0/mode_Hy_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_0/mode_Hy_0.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_0/mode_Hz_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_0/mode_Hz_0.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_1/mode_Ex_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_1/mode_Ex_1.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_1/mode_Ey_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_1/mode_Ey_1.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_1/mode_Ez_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_1/mode_Ez_1.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_1/mode_Hx_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_1/mode_Hx_1.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_1/mode_Hy_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_1/mode_Hy_1.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_1/mode_Hz_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_1/mode_Hz_1.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_2/mode_Ex_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_2/mode_Ex_2.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_2/mode_Ey_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_2/mode_Ey_2.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_2/mode_Ez_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_2/mode_Ez_2.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_2/mode_Hx_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_2/mode_Hx_2.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_2/mode_Hy_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_2/mode_Hy_2.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_2/mode_Hz_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_2/mode_Hz_2.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_3/mode_Ex_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_3/mode_Ex_3.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_3/mode_Ey_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_3/mode_Ey_3.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_3/mode_Ez_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_3/mode_Ez_3.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_3/mode_Hx_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_3/mode_Hx_3.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_3/mode_Hy_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_3/mode_Hy_3.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_3/mode_Hz_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_3/mode_Hz_3.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_4/mode_Ex_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_4/mode_Ex_4.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_4/mode_Ey_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_4/mode_Ey_4.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_4/mode_Ez_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_4/mode_Ez_4.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_4/mode_Hx_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_4/mode_Hx_4.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_4/mode_Hy_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_4/mode_Hy_4.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_4/mode_Hz_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_4/mode_Hz_4.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_5/mode_Ex_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_5/mode_Ex_5.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_5/mode_Ey_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_5/mode_Ey_5.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_5/mode_Ez_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_5/mode_Ez_5.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_5/mode_Hx_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_5/mode_Hx_5.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_5/mode_Hy_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_5/mode_Hy_5.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_5/mode_Hz_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_5/mode_Hz_5.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_6/mode_Ex_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_6/mode_Ex_6.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_6/mode_Ey_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_6/mode_Ey_6.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_6/mode_Ez_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_6/mode_Ez_6.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_6/mode_Hx_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_6/mode_Hx_6.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_6/mode_Hy_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_6/mode_Hy_6.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_6/mode_Hz_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_6/mode_Hz_6.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_7/mode_Ex_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_7/mode_Ex_7.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_7/mode_Ey_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_7/mode_Ey_7.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_7/mode_Ez_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_7/mode_Ez_7.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_7/mode_Hx_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_7/mode_Hx_7.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_7/mode_Hy_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_7/mode_Hy_7.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_7/mode_Hz_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/mode_7/mode_Hz_7.png
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/mode_info:
--------------------------------------------------------------------------------
1 | # Mode idx, Mode type, % in major direction, n_eff
2 | 0,qTE,97.39,1.643
3 | 1,qTM,92.54,1.640
4 | 2,qTE,90.60,1.576
5 | 3,qTM,91.41,1.571
6 | 4,qTE,89.48,1.497
7 | 5,qTM,86.70,1.475
8 | 6,qTE,89.47,1.447
9 | 7,qTM,68.35,1.437
10 |
--------------------------------------------------------------------------------
/examples/modes_full_vec_ex2/wavelength_n_effs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_full_vec_ex2/wavelength_n_effs.png
--------------------------------------------------------------------------------
/examples/modes_semi_vec_ex1/example_modes_1_Ey_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_semi_vec_ex1/example_modes_1_Ey_0.png
--------------------------------------------------------------------------------
/examples/modes_semi_vec_ex1/example_modes_1_Ey_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_semi_vec_ex1/example_modes_1_Ey_1.png
--------------------------------------------------------------------------------
/examples/modes_semi_vec_ex1/example_structure_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/modes_semi_vec_ex1/example_structure_1.png
--------------------------------------------------------------------------------
/examples/width-sweep-soi.py:
--------------------------------------------------------------------------------
1 | import modesolverpy.mode_solver as ms
2 | import modesolverpy.structure as st
3 | import opticalmaterialspy as mat
4 | import numpy as np
5 |
6 | wl = 1.55
7 | x_step = 0.02
8 | y_step = 0.02
9 | etch_depth = 0.22
10 | wg_widths = np.arange(0.3, 2., 0.02)
11 | sub_height = 1.
12 | sub_width = 4.
13 | clad_height = 1.
14 | film_thickness = 0.22
15 |
16 | n_sub = mat.SiO2().n(wl)
17 | n_clad = mat.Air().n(wl)
18 | n_wg = mat.RefractiveIndexWeb(
19 | 'https://refractiveindex.info/?shelf=main&book=Si&page=Li-293K').n(wl)
20 |
21 | r = []
22 | for w in wg_widths:
23 | r.append(
24 | st.RidgeWaveguide(wl, x_step, y_step, etch_depth, w, sub_height,
25 | sub_width, clad_height, n_sub, n_wg, None, n_clad,
26 | film_thickness))
27 |
28 | r[0].write_to_file('start_n_profile.dat')
29 | r[-1].write_to_file('end_n_profile.dat')
30 |
31 | solver = ms.ModeSolverFullyVectorial(6)
32 | solver.solve_sweep_structure(r, wg_widths, x_label='Taper width', fraction_mode_list=[1,2])
33 | solver.write_modes_to_file()
34 |
--------------------------------------------------------------------------------
/examples/width-sweep/width-sweep-end-n-profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/width-sweep/width-sweep-end-n-profile.png
--------------------------------------------------------------------------------
/examples/width-sweep/width-sweep-soi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/width-sweep/width-sweep-soi.png
--------------------------------------------------------------------------------
/examples/width-sweep/width-sweep-start-n-profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/examples/width-sweep/width-sweep-start-n-profile.png
--------------------------------------------------------------------------------
/modesolverpy/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtambasco/modesolverpy/ebdd70087f8e1c46f36dd7c6ae70ad59d7c38185/modesolverpy/__init__.py
--------------------------------------------------------------------------------
/modesolverpy/_analyse.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.optimize as sciopt
3 |
4 | def gaussian(x, *p):
5 | A, mu, sigma = p
6 | return A*np.exp(-(x-mu)**2/(2.*sigma**2))
7 |
8 | def fit_gaussian(x, y, z_2d, save_fits=False):
9 | z = z_2d
10 |
11 | max_idx = np.unravel_index(z.argmax(), z.shape)
12 | max_row = max_idx[0] - 1
13 | max_col = max_idx[1] - 1
14 |
15 | z_max_row = z[max_row, :]
16 | z_max_col = z[:, max_col]
17 | A = z[max_row, max_col]
18 |
19 | p_guess_x = (A, x[max_col], 0.1*(x[-1] - x[0]))
20 | p_guess_y = (A, y[max_row], 0.1*(y[-1] - y[0]))
21 |
22 | coeffs_x, var_matrix_x = sciopt.curve_fit(gaussian, x, z_max_row, p_guess_x)
23 | coeffs_y, var_matrix_y = sciopt.curve_fit(gaussian, y, z_max_col, p_guess_y)
24 |
25 | c_x = (x[-1]-x[0])*(max_col+1)/x.size + x[0]
26 | c_y = (y[-1]-y[0])*(y.size-(max_row+1))/y.size + y[0]
27 | centre = (c_x, c_y)
28 |
29 | sigma = np.array([coeffs_x[2], coeffs_y[2]])
30 | fwhm = 2.355 * sigma
31 | sigma_2 = 1.699 * fwhm
32 |
33 | if save_fits:
34 | with open('x_fit.dat', 'w') as fs:
35 | for c in np.c_[x, z_max_row, gaussian(x, *coeffs_x)]:
36 | s = ','.join([str(v) for v in c])
37 | fs.write(s+'\n')
38 | with open('y_fit.dat', 'w') as fs:
39 | for c in np.c_[y, z_max_col, gaussian(y, *coeffs_y)]:
40 | s = ','.join([str(v) for v in c])
41 | fs.write(s+'\n')
42 |
43 | return A, centre, sigma_2
44 |
--------------------------------------------------------------------------------
/modesolverpy/_mode_solver_lib.py:
--------------------------------------------------------------------------------
1 | # pylint: disable=line-too-long,too-many-locals,too-many-statements,too-many-branches
2 | # pylint: disable=redefined-builtin,wildcard-import,unused-wildcard-import
3 | # pylint: disable=attribute-defined-outside-init,too-many-instance-attributes
4 | # pylint: disable=arguments-differ,too-many-arguments
5 | """Finite Difference Modesolver.
6 |
7 | @see: Fallahkhair, "Vector Finite Difference Modesolver for Anisotropic Dielectric Waveguides", JLT 2007 }
8 | @see: http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=12734&objectType=FILE
9 |
10 | """
11 | from __future__ import print_function
12 | from builtins import zip
13 | from builtins import str
14 | from builtins import range
15 |
16 | import numpy
17 | import scipy
18 | import scipy.optimize
19 |
20 | import collections as col
21 |
22 | def trapz2(f, x=None, y=None, dx=1.0, dy=1.0):
23 | """Double integrate."""
24 | return numpy.trapz(numpy.trapz(f, x=y, dx=dy), x=x, dx=dx)
25 |
26 | def centered1d(x):
27 | return (x[1:] + x[:-1]) / 2.
28 |
29 | def centered2d(x):
30 | return (x[1:, 1:] + x[1:, :-1] + x[:-1, 1:] + x[:-1, :-1]) / 4.
31 |
32 | class _ModeSolverSemiVectorial():
33 | """
34 | This function calculates the modes of a dielectric waveguide
35 | using the semivectorial finite difference method.
36 | It is slightly faster than the full-vectorial VFDModeSolver,
37 | but it does not accept non-isotropic permittivity. For example,
38 | birefringent materials, which have
39 | different refractive indices along different dimensions cannot be used.
40 | It is adapted from the svmodes.m matlab code of Thomas Murphy and co-workers.
41 |
42 | Parameters
43 | ----------
44 | wl : float
45 | optical wavelength
46 | units are arbitrary, but must be self-consistent. It's recommended to just work in microns.
47 | x : 1D array of floats
48 | Array of x-values
49 | y : 1D array of floats
50 | Array of y-values
51 | epsfunc : function
52 | This is a function that provides the relative permittivity (square of the refractive index)
53 | as a function of the x and y position. The function must be of the form:
54 | ``myRelativePermittivity(x,y)``
55 | The function can either return a single float, corresponding the an isotropic refractive index,
56 | or, it may a length-5 tuple. In the tuple case, the relative permittivity is given in the form
57 | (epsxx, epsxy, epsyx, epsyy, epszz).
58 |
59 | boundary : str
60 | This is a string that identifies the type of boundary conditions applied.
61 | The following options are available:
62 | 'A' - Hx is antisymmetric, Hy is symmetric.
63 | 'S' - Hx is symmetric and, Hy is antisymmetric.
64 | '0' - Hx and Hy are zero immediately outside of the boundary.
65 | The string identifies all four boundary conditions, in the order: North, south, east, west.
66 | For example, boundary='000A'
67 |
68 | method : str
69 | must be 'Ex', 'Ey', or 'scalar'
70 | this identifies the field that will be calculated.
71 |
72 |
73 | Returns
74 | -------
75 | self : an instance of the SVFDModeSolver class
76 | Typically self.solve() will be called in order to actually find the modes.
77 |
78 | """
79 |
80 | def __init__(self, wl, structure, boundary='0000', method='Ex'):
81 | # Polarisation bug fix.
82 | assert method in ('Ex', 'Ey'), 'Invalid polarisation method.'
83 | if method == 'Ex':
84 | method = 'Ey'
85 | elif method == 'Ey':
86 | method = 'Ex'
87 |
88 | self.wl = wl
89 | self.x = structure.y
90 | self.y = structure.x
91 | self.boundary = boundary
92 | self.method = method
93 | self.structure = structure
94 |
95 | def build_matrix(self):
96 |
97 | from scipy.sparse import coo_matrix
98 |
99 | wl = self.wl
100 | x = self.x
101 | y = self.y
102 | structure = self.structure
103 | boundary = self.boundary
104 | method = self.method
105 |
106 | dx = numpy.diff(x)
107 | dy = numpy.diff(y)
108 |
109 | dx = numpy.r_[dx[0], dx, dx[-1]].reshape(-1, 1)
110 | dy = numpy.r_[dy[0], dy, dy[-1]].reshape(1, -1)
111 |
112 | xc = (x[:-1] + x[1:]) / 2
113 | yc = (y[:-1] + y[1:]) / 2
114 |
115 | eps = structure.eps_func(yc, xc)
116 | eps = numpy.c_[eps[:, 0:1], eps, eps[:, -1:]]
117 | eps = numpy.r_[eps[0:1, :], eps, eps[-1:, :]]
118 |
119 | nx = len(xc)
120 | ny = len(yc)
121 |
122 | self.nx = nx
123 | self.ny = ny
124 |
125 | k = 2 * numpy.pi / wl
126 |
127 | ones_nx = numpy.ones((nx, 1))
128 | ones_ny = numpy.ones((1, ny))
129 |
130 | n = numpy.dot(ones_nx, 0.5 * (dy[:, 2:] + dy[:, 1:-1])).flatten()
131 | s = numpy.dot(ones_nx, 0.5 * (dy[:, 0:-2] + dy[:, 1:-1])).flatten()
132 | e = numpy.dot(0.5 * (dx[2:, :] + dx[1:-1, :]), ones_ny).flatten()
133 | w = numpy.dot(0.5 * (dx[0:-2, :] + dx[1:-1, :]), ones_ny).flatten()
134 | p = numpy.dot(dx[1:-1, :], ones_ny).flatten()
135 | q = numpy.dot(ones_nx, dy[:, 1:-1]).flatten()
136 |
137 | en = eps[1:-1, 2:].flatten()
138 | es = eps[1:-1, 0:-2].flatten()
139 | ee = eps[2:, 1:-1].flatten()
140 | ew = eps[0:-2, 1:-1].flatten()
141 | ep = eps[1:-1, 1:-1].flatten()
142 |
143 | # three methods: Ex, Ey and scalar
144 |
145 | if method == 'Ex':
146 |
147 | # Ex
148 |
149 | An = 2 / n / (n + s)
150 | As = 2 / s / (n + s)
151 | Ae = 8 * (p * (ep - ew) + 2 * w * ew) * ee / \
152 | ((p * (ep - ee) + 2 * e * ee) * (p ** 2 * (ep - ew) + 4 * w ** 2 * ew) +
153 | (p * (ep - ew) + 2 * w * ew) * (p ** 2 * (ep - ee) + 4 * e ** 2 * ee))
154 | Aw = 8 * (p * (ep - ee) + 2 * e * ee) * ew / \
155 | ((p * (ep - ee) + 2 * e * ee) * (p ** 2 * (ep - ew) + 4 * w ** 2 * ew) +
156 | (p * (ep - ew) + 2 * w * ew) * (p ** 2 * (ep - ee) + 4 * e ** 2 * ee))
157 | Ap = ep * k ** 2 - An - As - Ae * ep / ee - Aw * ep / ew
158 |
159 | elif method == 'Ey':
160 |
161 | # Ey
162 |
163 | An = 8 * (q * (ep - es) + 2 * s * es) * en / \
164 | ((q * (ep - en) + 2 * n * en) * (q ** 2 * (ep - es) + 4 * s ** 2 * es) +
165 | (q * (ep - es) + 2 * s * es) * (q ** 2 * (ep - en) + 4 * n ** 2 * en))
166 | As = 8 * (q * (ep - en) + 2 * n * en) * es / \
167 | ((q * (ep - en) + 2 * n * en) * (q ** 2 * (ep - es) + 4 * s ** 2 * es) +
168 | (q * (ep - es) + 2 * s * es) * (q ** 2 * (ep - en) + 4 * n ** 2 * en))
169 | Ae = 2 / e / (e + w)
170 | Aw = 2 / w / (e + w)
171 | Ap = ep * k ** 2 - An * ep / en - As * ep / es - Ae - Aw
172 |
173 | elif method == 'scalar':
174 |
175 | # scalar
176 |
177 | An = 2 / n / (n + s)
178 | As = 2 / s / (n + s)
179 | Ae = 2 / e / (e + w)
180 | Aw = 2 / w / (e + w)
181 | Ap = ep * k ** 2 - An - As - Ae - Aw
182 |
183 | else:
184 |
185 | raise ValueError('unknown method')
186 |
187 | ii = numpy.arange(nx * ny).reshape(nx, ny)
188 |
189 | # north boundary
190 | ib = ii[:, -1]
191 | if boundary[0] == 'S':
192 | Ap[ib] += An[ib]
193 | elif boundary[0] == 'A':
194 | Ap[ib] -= An[ib]
195 | # else:
196 | # raise ValueError('unknown boundary')
197 |
198 | # south
199 | ib = ii[:, 0]
200 | if boundary[1] == 'S':
201 | Ap[ib] += As[ib]
202 | elif boundary[1] == 'A':
203 | Ap[ib] -= As[ib]
204 | # else:
205 | # raise ValueError('unknown boundary')
206 |
207 | # east
208 | ib = ii[-1, :]
209 | if boundary[2] == 'S':
210 | Ap[ib] += Ae[ib]
211 | elif boundary[2] == 'A':
212 | Ap[ib] -= Ae[ib]
213 | # else:
214 | # raise ValueError('unknown boundary')
215 |
216 | # west
217 | ib = ii[0, :]
218 | if boundary[3] == 'S':
219 | Ap[ib] += Aw[ib]
220 | elif boundary[3] == 'A':
221 | Ap[ib] -= Aw[ib]
222 | # else:
223 | # raise ValueError('unknown boundary')
224 |
225 | iall = ii.flatten()
226 | i_n = ii[:, 1:].flatten()
227 | i_s = ii[:, :-1].flatten()
228 | i_e = ii[1:, :].flatten()
229 | i_w = ii[:-1, :].flatten()
230 |
231 | I = numpy.r_[iall, i_w, i_e, i_s, i_n]
232 | J = numpy.r_[iall, i_e, i_w, i_n, i_s]
233 | V = numpy.r_[Ap[iall], Ae[i_w], Aw[i_e], An[i_s], As[i_n]]
234 |
235 | A = coo_matrix((V, (I, J))).tocsr()
236 |
237 | return A
238 |
239 | def solve(self, neigs, tol=0, mode_profiles=True, initial_mode_guess=None):
240 |
241 | from scipy.sparse.linalg import eigen
242 |
243 | self.nmodes = neigs
244 | self.tol = tol
245 |
246 | A = self.build_matrix()
247 |
248 | eigs = eigen.eigs(A,
249 | k=neigs,
250 | which='LR',
251 | tol=0.001,
252 | ncv=None,
253 | v0 = initial_mode_guess,
254 | return_eigenvectors=mode_profiles)
255 | if mode_profiles:
256 | eigvals, eigvecs = eigs
257 | else:
258 | eigvals = eigs
259 | eigvecs = None
260 |
261 | neff = self.wl * scipy.sqrt(eigvals) / (2 * numpy.pi)
262 | if mode_profiles:
263 | phi = []
264 | for ieig in range(neigs):
265 | tmp = eigvecs[:, ieig].reshape(self.nx, self.ny)
266 | phi.append(tmp)
267 |
268 | # sort and save the modes
269 | idx = numpy.flipud(numpy.argsort(neff))
270 | self.neff = neff[idx]
271 | if mode_profiles:
272 | tmp = []
273 | for i in idx:
274 | tmp.append(phi[i])
275 |
276 | if self.method == 'scalar':
277 | self.phi = tmp
278 | elif self.method == 'Ex':
279 | self.Ex = tmp
280 | elif self.method == 'Ey':
281 | self.Ey = tmp
282 | self.modes = tmp
283 |
284 | return self
285 |
286 | def __str__(self):
287 | descr = 'Semi-Vectorial Finite Difference Modesolver\n\tmethod: %s\n' % self.method
288 | return descr
289 |
290 |
291 | class _ModeSolverVectorial():
292 |
293 | """
294 | The VFDModeSolver class computes the electric and magnetic fields for modes of a dielectric
295 | waveguide using the "Vector Finite Difference (VFD)" method, as described in
296 | A. B. Fallahkhair, K. S. Li and T. E. Murphy, "Vector Finite Difference Modesolver for
297 | Anisotropic Dielectric Waveguides", J. Lightwave Technol. 26(11), 1423-1431, (2008).
298 |
299 |
300 | Parameters
301 | ----------
302 | wl : float
303 | The wavelength of the optical radiation (units are arbitrary, but must be self-consistent
304 | between all inputs. Recommandation is to just use micron for everthing)
305 | x : 1D array of floats
306 | Array of x-values
307 | y : 1D array of floats
308 | Array of y-values
309 | epsfunc : function
310 | This is a function that provides the relative permittivity (square of the refractive index)
311 | as a function of the x and y position. The function must be of the form:
312 | ``myRelativePermittivity(x,y)``
313 | The function can either return a single float, corresponding the an isotropic refractive index,
314 | or, ir may a length-5 tuple. In the tuple case, the relative permittivity is given in the form
315 | (epsxx, epsxy, epsyx, epsyy, epszz).
316 | The light is `z` propagating.
317 | boundary : str
318 | This is a string that identifies the type of boundary conditions applied.
319 | The following options are available:
320 | 'A' - Hx is antisymmetric, Hy is symmetric.
321 | 'S' - Hx is symmetric and, Hy is antisymmetric.
322 | '0' - Hx and Hy are zero immediately outside of the boundary.
323 | The string identifies all four boundary conditions, in the order: North, south, east, west.
324 | For example, boundary='000A'
325 |
326 | Returns
327 | -------
328 | self : an instance of the VFDModeSolver class
329 | Typically self.solve() will be called in order to actually find the modes.
330 |
331 | """
332 |
333 | def __init__(self, wl, structure, boundary):
334 | self.wl = wl
335 | self.x = structure.y
336 | self.y = structure.x
337 | self.epsfunc = structure.eps_func
338 | self.boundary = boundary
339 |
340 | def build_matrix(self):
341 |
342 | from scipy.sparse import coo_matrix
343 |
344 | wl = self.wl
345 | x = self.x
346 | y = self.y
347 | epsfunc = self.epsfunc
348 | boundary = self.boundary
349 |
350 | dx = numpy.diff(x)
351 | dy = numpy.diff(y)
352 |
353 | dx = numpy.r_[dx[0], dx, dx[-1]].reshape(-1, 1)
354 | dy = numpy.r_[dy[0], dy, dy[-1]].reshape(1, -1)
355 |
356 | xc = (x[:-1] + x[1:]) / 2
357 | yc = (y[:-1] + y[1:]) / 2
358 |
359 | tmp = epsfunc(yc, xc)
360 | if isinstance(tmp, tuple):
361 | tmp = [numpy.c_[t[:, 0:1], t, t[:, -1:]] for t in tmp]
362 | tmp = [numpy.r_[t[0:1, :], t, t[-1:, :]] for t in tmp]
363 | epsyy, epsyx, epsxy, epsxx, epszz = tmp
364 | else:
365 | tmp = numpy.c_[tmp[:, 0:1], tmp, tmp[:, -1:]]
366 | tmp = numpy.r_[tmp[0:1, :], tmp, tmp[-1:, :]]
367 | epsxx = epsyy = epszz = tmp
368 | epsxy = epsyx = numpy.zeros_like(epsxx)
369 |
370 | nx = len(x)
371 | ny = len(y)
372 |
373 | self.nx = nx
374 | self.ny = ny
375 |
376 | k = 2 * numpy.pi / wl
377 |
378 | ones_nx = numpy.ones((nx, 1))
379 | ones_ny = numpy.ones((1, ny))
380 |
381 | n = numpy.dot(ones_nx, dy[:, 1:]).flatten()
382 | s = numpy.dot(ones_nx, dy[:, :-1]).flatten()
383 | e = numpy.dot(dx[1:, :], ones_ny).flatten()
384 | w = numpy.dot(dx[:-1, :], ones_ny).flatten()
385 |
386 | exx1 = epsxx[:-1, 1:].flatten()
387 | exx2 = epsxx[:-1, :-1].flatten()
388 | exx3 = epsxx[1:, :-1].flatten()
389 | exx4 = epsxx[1:, 1:].flatten()
390 |
391 | eyy1 = epsyy[:-1, 1:].flatten()
392 | eyy2 = epsyy[:-1, :-1].flatten()
393 | eyy3 = epsyy[1:, :-1].flatten()
394 | eyy4 = epsyy[1:, 1:].flatten()
395 |
396 | exy1 = epsxy[:-1, 1:].flatten()
397 | exy2 = epsxy[:-1, :-1].flatten()
398 | exy3 = epsxy[1:, :-1].flatten()
399 | exy4 = epsxy[1:, 1:].flatten()
400 |
401 | eyx1 = epsyx[:-1, 1:].flatten()
402 | eyx2 = epsyx[:-1, :-1].flatten()
403 | eyx3 = epsyx[1:, :-1].flatten()
404 | eyx4 = epsyx[1:, 1:].flatten()
405 |
406 | ezz1 = epszz[:-1, 1:].flatten()
407 | ezz2 = epszz[:-1, :-1].flatten()
408 | ezz3 = epszz[1:, :-1].flatten()
409 | ezz4 = epszz[1:, 1:].flatten()
410 |
411 | ns21 = n * eyy2 + s * eyy1
412 | ns34 = n * eyy3 + s * eyy4
413 | ew14 = e * exx1 + w * exx4
414 | ew23 = e * exx2 + w * exx3
415 |
416 | axxn = ((2 * eyy4 * e - eyx4 * n) * (eyy3 / ezz4) / ns34 +
417 | (2 * eyy1 * w + eyx1 * n) * (eyy2 / ezz1) / ns21) / (n * (e + w))
418 | axxs = ((2 * eyy3 * e + eyx3 * s) * (eyy4 / ezz3) / ns34 +
419 | (2 * eyy2 * w - eyx2 * s) * (eyy1 / ezz2) / ns21) / (s * (e + w))
420 | ayye = (2 * n * exx4 - e * exy4) * exx1 / ezz4 / e / ew14 / \
421 | (n + s) + (2 * s * exx3 + e * exy3) * \
422 | exx2 / ezz3 / e / ew23 / (n + s)
423 | ayyw = (2 * exx1 * n + exy1 * w) * exx4 / ezz1 / w / ew14 / \
424 | (n + s) + (2 * exx2 * s - exy2 * w) * \
425 | exx3 / ezz2 / w / ew23 / (n + s)
426 | axxe = 2 / (e * (e + w)) + \
427 | (eyy4 * eyx3 / ezz3 - eyy3 * eyx4 / ezz4) / (e + w) / ns34
428 | axxw = 2 / (w * (e + w)) + \
429 | (eyy2 * eyx1 / ezz1 - eyy1 * eyx2 / ezz2) / (e + w) / ns21
430 | ayyn = 2 / (n * (n + s)) + \
431 | (exx4 * exy1 / ezz1 - exx1 * exy4 / ezz4) / (n + s) / ew14
432 | ayys = 2 / (s * (n + s)) + \
433 | (exx2 * exy3 / ezz3 - exx3 * exy2 / ezz2) / (n + s) / ew23
434 |
435 | axxne = +eyx4 * eyy3 / ezz4 / (e + w) / ns34
436 | axxse = -eyx3 * eyy4 / ezz3 / (e + w) / ns34
437 | axxnw = -eyx1 * eyy2 / ezz1 / (e + w) / ns21
438 | axxsw = +eyx2 * eyy1 / ezz2 / (e + w) / ns21
439 |
440 | ayyne = +exy4 * exx1 / ezz4 / (n + s) / ew14
441 | ayyse = -exy3 * exx2 / ezz3 / (n + s) / ew23
442 | ayynw = -exy1 * exx4 / ezz1 / (n + s) / ew14
443 | ayysw = +exy2 * exx3 / ezz2 / (n + s) / ew23
444 |
445 | axxp = -axxn - axxs - axxe - axxw - axxne - axxse - axxnw - axxsw + k ** 2 * \
446 | (n + s) * \
447 | (eyy4 * eyy3 * e / ns34 + eyy1 * eyy2 * w / ns21) / (e + w)
448 | ayyp = -ayyn - ayys - ayye - ayyw - ayyne - ayyse - ayynw - ayysw + k ** 2 * \
449 | (e + w) * \
450 | (exx1 * exx4 * n / ew14 + exx2 * exx3 * s / ew23) / (n + s)
451 | axyn = (eyy3 * eyy4 / ezz4 / ns34 - eyy2 * eyy1 / ezz1 /
452 | ns21 + s * (eyy2 * eyy4 - eyy1 * eyy3) / ns21 / ns34) / (e + w)
453 | axys = (eyy1 * eyy2 / ezz2 / ns21 - eyy4 * eyy3 / ezz3 /
454 | ns34 + n * (eyy2 * eyy4 - eyy1 * eyy3) / ns21 / ns34) / (e + w)
455 | ayxe = (exx1 * exx4 / ezz4 / ew14 - exx2 * exx3 / ezz3 /
456 | ew23 + w * (exx2 * exx4 - exx1 * exx3) / ew23 / ew14) / (n + s)
457 | ayxw = (exx3 * exx2 / ezz2 / ew23 - exx4 * exx1 / ezz1 /
458 | ew14 + e * (exx4 * exx2 - exx1 * exx3) / ew23 / ew14) / (n + s)
459 |
460 | axye = (eyy4 * (1 + eyy3 / ezz4) - eyy3 * (1 + eyy4 / ezz4)) / ns34 / (e + w) - \
461 | (2 * eyx1 * eyy2 / ezz1 * n * w / ns21 +
462 | 2 * eyx2 * eyy1 / ezz2 * s * w / ns21 +
463 | 2 * eyx4 * eyy3 / ezz4 * n * e / ns34 +
464 | 2 * eyx3 * eyy4 / ezz3 * s * e / ns34 +
465 | 2 * eyy1 * eyy2 * (1. / ezz1 - 1. / ezz2) * w ** 2 / ns21) / e / (e + w) ** 2
466 |
467 | axyw = (eyy2 * (1 + eyy1 / ezz2) - eyy1 * (1 + eyy2 / ezz2)) / ns21 / (e + w) - \
468 | (2 * eyx1 * eyy2 / ezz1 * n * e / ns21 +
469 | 2 * eyx2 * eyy1 / ezz2 * s * e / ns21 +
470 | 2 * eyx4 * eyy3 / ezz4 * n * w / ns34 +
471 | 2 * eyx3 * eyy4 / ezz3 * s * w / ns34 +
472 | 2 * eyy3 * eyy4 * (1. / ezz3 - 1. / ezz4) * e ** 2 / ns34) / w / (e + w) ** 2
473 |
474 | ayxn = (exx4 * (1 + exx1 / ezz4) - exx1 * (1 + exx4 / ezz4)) / ew14 / (n + s) - \
475 | (2 * exy3 * exx2 / ezz3 * e * s / ew23 +
476 | 2 * exy2 * exx3 / ezz2 * w * n / ew23 +
477 | 2 * exy4 * exx1 / ezz4 * e * s / ew14 +
478 | 2 * exy1 * exx4 / ezz1 * w * n / ew14 +
479 | 2 * exx3 * exx2 * (1. / ezz3 - 1. / ezz2) * s ** 2 / ew23) / n / (n + s) ** 2
480 |
481 | ayxs = (exx2 * (1 + exx3 / ezz2) - exx3 * (1 + exx2 / ezz2)) / ew23 / (n + s) - \
482 | (2 * exy3 * exx2 / ezz3 * e * n / ew23 +
483 | 2 * exy2 * exx3 / ezz2 * w * n / ew23 +
484 | 2 * exy4 * exx1 / ezz4 * e * s / ew14 +
485 | 2 * exy1 * exx4 / ezz1 * w * s / ew14 +
486 | 2 * exx1 * exx4 * (1. / ezz1 - 1. / ezz4) * n ** 2 / ew14) / s / (n + s) ** 2
487 |
488 | axyne = +eyy3 * (1 - eyy4 / ezz4) / (e + w) / ns34
489 | axyse = -eyy4 * (1 - eyy3 / ezz3) / (e + w) / ns34
490 | axynw = -eyy2 * (1 - eyy1 / ezz1) / (e + w) / ns21
491 | axysw = +eyy1 * (1 - eyy2 / ezz2) / (e + w) / ns21
492 |
493 | ayxne = +exx1 * (1 - exx4 / ezz4) / (n + s) / ew14
494 | ayxse = -exx2 * (1 - exx3 / ezz3) / (n + s) / ew23
495 | ayxnw = -exx4 * (1 - exx1 / ezz1) / (n + s) / ew14
496 | ayxsw = +exx3 * (1 - exx2 / ezz2) / (n + s) / ew23
497 |
498 | axyp = -(axyn + axys + axye + axyw + axyne + axyse + axynw + axysw) - k ** 2 * (w * (n * eyx1 *
499 | eyy2 + s * eyx2 * eyy1) / ns21 + e * (s * eyx3 * eyy4 + n * eyx4 * eyy3) / ns34) / (e + w)
500 | ayxp = -(ayxn + ayxs + ayxe + ayxw + ayxne + ayxse + ayxnw + ayxsw) - k ** 2 * (n * (w * exy1 *
501 | exx4 + e * exy4 * exx1) / ew14 + s * (w * exy2 * exx3 + e * exy3 * exx2) / ew23) / (n + s)
502 |
503 | ii = numpy.arange(nx * ny).reshape(nx, ny)
504 |
505 | # NORTH boundary
506 |
507 | ib = ii[:, -1]
508 |
509 | if boundary[0] == 'S':
510 | sign = 1
511 | elif boundary[0] == 'A':
512 | sign = -1
513 | elif boundary[0] == '0':
514 | sign = 0
515 | else:
516 | raise ValueError('unknown boundary conditions')
517 |
518 | axxs[ib] += sign * axxn[ib]
519 | axxse[ib] += sign * axxne[ib]
520 | axxsw[ib] += sign * axxnw[ib]
521 | ayxs[ib] += sign * ayxn[ib]
522 | ayxse[ib] += sign * ayxne[ib]
523 | ayxsw[ib] += sign * ayxnw[ib]
524 | ayys[ib] -= sign * ayyn[ib]
525 | ayyse[ib] -= sign * ayyne[ib]
526 | ayysw[ib] -= sign * ayynw[ib]
527 | axys[ib] -= sign * axyn[ib]
528 | axyse[ib] -= sign * axyne[ib]
529 | axysw[ib] -= sign * axynw[ib]
530 |
531 | # SOUTH boundary
532 |
533 | ib = ii[:, 0]
534 |
535 | if boundary[1] == 'S':
536 | sign = 1
537 | elif boundary[1] == 'A':
538 | sign = -1
539 | elif boundary[1] == '0':
540 | sign = 0
541 | else:
542 | raise ValueError('unknown boundary conditions')
543 |
544 | axxn[ib] += sign * axxs[ib]
545 | axxne[ib] += sign * axxse[ib]
546 | axxnw[ib] += sign * axxsw[ib]
547 | ayxn[ib] += sign * ayxs[ib]
548 | ayxne[ib] += sign * ayxse[ib]
549 | ayxnw[ib] += sign * ayxsw[ib]
550 | ayyn[ib] -= sign * ayys[ib]
551 | ayyne[ib] -= sign * ayyse[ib]
552 | ayynw[ib] -= sign * ayysw[ib]
553 | axyn[ib] -= sign * axys[ib]
554 | axyne[ib] -= sign * axyse[ib]
555 | axynw[ib] -= sign * axysw[ib]
556 |
557 | # EAST boundary
558 |
559 | ib = ii[-1, :]
560 |
561 | if boundary[2] == 'S':
562 | sign = 1
563 | elif boundary[2] == 'A':
564 | sign = -1
565 | elif boundary[2] == '0':
566 | sign = 0
567 | else:
568 | raise ValueError('unknown boundary conditions')
569 |
570 | axxw[ib] += sign * axxe[ib]
571 | axxnw[ib] += sign * axxne[ib]
572 | axxsw[ib] += sign * axxse[ib]
573 | ayxw[ib] += sign * ayxe[ib]
574 | ayxnw[ib] += sign * ayxne[ib]
575 | ayxsw[ib] += sign * ayxse[ib]
576 | ayyw[ib] -= sign * ayye[ib]
577 | ayynw[ib] -= sign * ayyne[ib]
578 | ayysw[ib] -= sign * ayyse[ib]
579 | axyw[ib] -= sign * axye[ib]
580 | axynw[ib] -= sign * axyne[ib]
581 | axysw[ib] -= sign * axyse[ib]
582 |
583 | # WEST boundary
584 |
585 | ib = ii[0, :]
586 |
587 | if boundary[3] == 'S':
588 | sign = 1
589 | elif boundary[3] == 'A':
590 | sign = -1
591 | elif boundary[3] == '0':
592 | sign = 0
593 | else:
594 | raise ValueError('unknown boundary conditions')
595 |
596 | axxe[ib] += sign * axxw[ib]
597 | axxne[ib] += sign * axxnw[ib]
598 | axxse[ib] += sign * axxsw[ib]
599 | ayxe[ib] += sign * ayxw[ib]
600 | ayxne[ib] += sign * ayxnw[ib]
601 | ayxse[ib] += sign * ayxsw[ib]
602 | ayye[ib] -= sign * ayyw[ib]
603 | ayyne[ib] -= sign * ayynw[ib]
604 | ayyse[ib] -= sign * ayysw[ib]
605 | axye[ib] -= sign * axyw[ib]
606 | axyne[ib] -= sign * axynw[ib]
607 | axyse[ib] -= sign * axysw[ib]
608 |
609 | # Assemble sparse matrix
610 |
611 | iall = ii.flatten()
612 | i_s = ii[:, :-1].flatten()
613 | i_n = ii[:, 1:].flatten()
614 | i_e = ii[1:, :].flatten()
615 | i_w = ii[:-1, :].flatten()
616 | i_ne = ii[1:, 1:].flatten()
617 | i_se = ii[1:, :-1].flatten()
618 | i_sw = ii[:-1, :-1].flatten()
619 | i_nw = ii[:-1, 1:].flatten()
620 |
621 | Ixx = numpy.r_[iall, i_w, i_e, i_s, i_n, i_ne, i_se, i_sw, i_nw]
622 | Jxx = numpy.r_[iall, i_e, i_w, i_n, i_s, i_sw, i_nw, i_ne, i_se]
623 | Vxx = numpy.r_[axxp[iall], axxe[i_w], axxw[i_e], axxn[i_s], axxs[
624 | i_n], axxsw[i_ne], axxnw[i_se], axxne[i_sw], axxse[i_nw]]
625 |
626 | Ixy = numpy.r_[iall, i_w, i_e, i_s, i_n, i_ne, i_se, i_sw, i_nw]
627 | Jxy = numpy.r_[
628 | iall, i_e, i_w, i_n, i_s, i_sw, i_nw, i_ne, i_se] + nx * ny
629 | Vxy = numpy.r_[axyp[iall], axye[i_w], axyw[i_e], axyn[i_s], axys[
630 | i_n], axysw[i_ne], axynw[i_se], axyne[i_sw], axyse[i_nw]]
631 |
632 | Iyx = numpy.r_[
633 | iall, i_w, i_e, i_s, i_n, i_ne, i_se, i_sw, i_nw] + nx * ny
634 | Jyx = numpy.r_[iall, i_e, i_w, i_n, i_s, i_sw, i_nw, i_ne, i_se]
635 | Vyx = numpy.r_[ayxp[iall], ayxe[i_w], ayxw[i_e], ayxn[i_s], ayxs[
636 | i_n], ayxsw[i_ne], ayxnw[i_se], ayxne[i_sw], ayxse[i_nw]]
637 |
638 | Iyy = numpy.r_[
639 | iall, i_w, i_e, i_s, i_n, i_ne, i_se, i_sw, i_nw] + nx * ny
640 | Jyy = numpy.r_[
641 | iall, i_e, i_w, i_n, i_s, i_sw, i_nw, i_ne, i_se] + nx * ny
642 | Vyy = numpy.r_[ayyp[iall], ayye[i_w], ayyw[i_e], ayyn[i_s], ayys[
643 | i_n], ayysw[i_ne], ayynw[i_se], ayyne[i_sw], ayyse[i_nw]]
644 |
645 | I = numpy.r_[Ixx, Ixy, Iyx, Iyy]
646 | J = numpy.r_[Jxx, Jxy, Jyx, Jyy]
647 | V = numpy.r_[Vxx, Vxy, Vyx, Vyy]
648 | A = coo_matrix((V, (I, J))).tocsr()
649 |
650 | return A
651 |
652 | def compute_other_fields(self, neffs, Hxs, Hys):
653 |
654 | from scipy.sparse import coo_matrix
655 |
656 | wl = self.wl
657 | x = self.x
658 | y = self.y
659 | epsfunc = self.epsfunc
660 | boundary = self.boundary
661 |
662 | Hzs = []
663 | Exs = []
664 | Eys = []
665 | Ezs = []
666 | for neff, Hx, Hy in zip(neffs, Hxs, Hys):
667 |
668 | dx = numpy.diff(x)
669 | dy = numpy.diff(y)
670 |
671 | dx = numpy.r_[dx[0], dx, dx[-1]].reshape(-1, 1)
672 | dy = numpy.r_[dy[0], dy, dy[-1]].reshape(1, -1)
673 |
674 | xc = (x[:-1] + x[1:]) / 2
675 | yc = (y[:-1] + y[1:]) / 2
676 |
677 | tmp = epsfunc(yc, xc)
678 | if isinstance(tmp, tuple):
679 | tmp = [numpy.c_[t[:, 0:1], t, t[:, -1:]] for t in tmp]
680 | tmp = [numpy.r_[t[0:1, :], t, t[-1:, :]] for t in tmp]
681 | epsxx, epsxy, epsyx, epsyy, epszz = tmp
682 | else:
683 | tmp = numpy.c_[tmp[:, 0:1], tmp, tmp[:, -1:]]
684 | tmp = numpy.r_[tmp[0:1, :], tmp, tmp[-1:, :]]
685 | epsxx = epsyy = epszz = tmp
686 | epsxy = epsyx = numpy.zeros_like(epsxx)
687 |
688 | nx = len(x)
689 | ny = len(y)
690 |
691 | k = 2 * numpy.pi / wl
692 |
693 | ones_nx = numpy.ones((nx, 1))
694 | ones_ny = numpy.ones((1, ny))
695 |
696 | n = numpy.dot(ones_nx, dy[:, 1:]).flatten()
697 | s = numpy.dot(ones_nx, dy[:, :-1]).flatten()
698 | e = numpy.dot(dx[1:, :], ones_ny).flatten()
699 | w = numpy.dot(dx[:-1, :], ones_ny).flatten()
700 |
701 | exx1 = epsxx[:-1, 1:].flatten()
702 | exx2 = epsxx[:-1, :-1].flatten()
703 | exx3 = epsxx[1:, :-1].flatten()
704 | exx4 = epsxx[1:, 1:].flatten()
705 |
706 | eyy1 = epsyy[:-1, 1:].flatten()
707 | eyy2 = epsyy[:-1, :-1].flatten()
708 | eyy3 = epsyy[1:, :-1].flatten()
709 | eyy4 = epsyy[1:, 1:].flatten()
710 |
711 | exy1 = epsxy[:-1, 1:].flatten()
712 | exy2 = epsxy[:-1, :-1].flatten()
713 | exy3 = epsxy[1:, :-1].flatten()
714 | exy4 = epsxy[1:, 1:].flatten()
715 |
716 | eyx1 = epsyx[:-1, 1:].flatten()
717 | eyx2 = epsyx[:-1, :-1].flatten()
718 | eyx3 = epsyx[1:, :-1].flatten()
719 | eyx4 = epsyx[1:, 1:].flatten()
720 |
721 | ezz1 = epszz[:-1, 1:].flatten()
722 | ezz2 = epszz[:-1, :-1].flatten()
723 | ezz3 = epszz[1:, :-1].flatten()
724 | ezz4 = epszz[1:, 1:].flatten()
725 |
726 | b = neff * k
727 |
728 | bzxne = (0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * eyx4 / ezz4 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy3 * eyy1 * w * eyy2 +
729 | 0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * (1 - exx4 / ezz4) / ezz3 / ezz2 / (w * exx3 + e * exx2) / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * exx1 * s) / b
730 |
731 | bzxse = (-0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * eyx3 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy1 * w * eyy2 +
732 | 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * (1 - exx3 / ezz3) / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * n * exx1 * exx4) / b
733 |
734 | bzxnw = (-0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * eyx1 / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy2 * e -
735 | 0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * (1 - exx1 / ezz1) / ezz3 / ezz2 / (w * exx3 + e * exx2) / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * exx4 * s) / b
736 |
737 | bzxsw = (0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * eyx2 / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * e -
738 | 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * (1 - exx2 / ezz2) / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx3 * n * exx1 * exx4) / b
739 |
740 | bzxn = ((0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * n * ezz1 * ezz2 / eyy1 * (2 * eyy1 / ezz1 / n ** 2 + eyx1 / ezz1 / n / w) + 0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * n * ezz4 * ezz3 / eyy4 * (2 * eyy4 / ezz4 / n ** 2 - eyx4 / ezz4 / n / e)) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e + ((ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * (0.5 * ezz4 * ((1 - exx1 / ezz1) / n / w - exy1 / ezz1 *
741 | (2. / n ** 2 - 2 / n ** 2 * s / (n + s))) / exx1 * ezz1 * w + (ezz4 - ezz1) * s / n / (n + s) + 0.5 * ezz1 * (-(1 - exx4 / ezz4) / n / e - exy4 / ezz4 * (2. / n ** 2 - 2 / n ** 2 * s / (n + s))) / exx4 * ezz4 * e) - (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * (-ezz3 * exy2 / n / (n + s) / exx2 * w + (ezz3 - ezz2) * s / n / (n + s) - ezz2 * exy3 / n / (n + s) / exx3 * e)) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
742 |
743 | bzxs = ((0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * s * ezz2 * ezz1 / eyy2 * (2 * eyy2 / ezz2 / s ** 2 - eyx2 / ezz2 / s / w) + 0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * s * ezz3 * ezz4 / eyy3 * (2 * eyy3 / ezz3 / s ** 2 + eyx3 / ezz3 / s / e)) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e + ((ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * (-ezz4 * exy1 / s / (n + s) / exx1 * w - (ezz4 - ezz1)
744 | * n / s / (n + s) - ezz1 * exy4 / s / (n + s) / exx4 * e) - (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * (0.5 * ezz3 * (-(1 - exx2 / ezz2) / s / w - exy2 / ezz2 * (2. / s ** 2 - 2 / s ** 2 * n / (n + s))) / exx2 * ezz2 * w - (ezz3 - ezz2) * n / s / (n + s) + 0.5 * ezz2 * ((1 - exx3 / ezz3) / s / e - exy3 / ezz3 * (2. / s ** 2 - 2 / s ** 2 * n / (n + s))) / exx3 * ezz3 * e)) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
745 |
746 | bzxe = ((n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * (0.5 * n * ezz4 * ezz3 / eyy4 * (2. / e ** 2 - eyx4 / ezz4 / n / e) + 0.5 * s * ezz3 * ezz4 / eyy3 * (2. / e ** 2 + eyx3 / ezz3 / s / e)) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e +
747 | (-0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * ezz1 * (1 - exx4 / ezz4) / n / exx4 * ezz4 - 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * ezz2 * (1 - exx3 / ezz3) / s / exx3 * ezz3) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
748 |
749 | bzxw = ((-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * (0.5 * n * ezz1 * ezz2 / eyy1 * (2. / w ** 2 + eyx1 / ezz1 / n / w) + 0.5 * s * ezz2 * ezz1 / eyy2 * (2. / w ** 2 - eyx2 / ezz2 / s / w)) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e +
750 | (0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * ezz4 * (1 - exx1 / ezz1) / n / exx1 * ezz1 + 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * ezz3 * (1 - exx2 / ezz2) / s / exx2 * ezz2) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
751 |
752 | bzxp = (((-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * (0.5 * n * ezz1 * ezz2 / eyy1 * (-2. / w ** 2 - 2 * eyy1 / ezz1 / n ** 2 + k ** 2 * eyy1 - eyx1 / ezz1 / n / w) + 0.5 * s * ezz2 * ezz1 / eyy2 * (-2. / w ** 2 - 2 * eyy2 / ezz2 / s ** 2 + k ** 2 * eyy2 + eyx2 / ezz2 / s / w)) + (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * (0.5 * n * ezz4 * ezz3 / eyy4 * (-2. / e ** 2 - 2 * eyy4 / ezz4 / n ** 2 + k ** 2 * eyy4 + eyx4 / ezz4 / n / e) + 0.5 * s * ezz3 * ezz4 / eyy3 * (-2. / e ** 2 - 2 * eyy3 / ezz3 / s ** 2 + k ** 2 * eyy3 - eyx3 / ezz3 / s / e))) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e + ((ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * (0.5 * ezz4 * (-k **
753 | 2 * exy1 - (1 - exx1 / ezz1) / n / w - exy1 / ezz1 * (-2. / n ** 2 - 2 / n ** 2 * (n - s) / s)) / exx1 * ezz1 * w + (ezz4 - ezz1) * (n - s) / n / s + 0.5 * ezz1 * (-k ** 2 * exy4 + (1 - exx4 / ezz4) / n / e - exy4 / ezz4 * (-2. / n ** 2 - 2 / n ** 2 * (n - s) / s)) / exx4 * ezz4 * e) - (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * (0.5 * ezz3 * (-k ** 2 * exy2 + (1 - exx2 / ezz2) / s / w - exy2 / ezz2 * (-2. / s ** 2 + 2 / s ** 2 * (n - s) / n)) / exx2 * ezz2 * w + (ezz3 - ezz2) * (n - s) / n / s + 0.5 * ezz2 * (-k ** 2 * exy3 - (1 - exx3 / ezz3) / s / e - exy3 / ezz3 * (-2. / s ** 2 + 2 / s ** 2 * (n - s) / n)) / exx3 * ezz3 * e)) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
754 |
755 | bzyne = (0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * (1 - eyy4 / ezz4) / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy3 * eyy1 * w *
756 | eyy2 + 0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * exy4 / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * exx1 * s) / b
757 |
758 | bzyse = (-0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * (1 - eyy3 / ezz3) / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy1 * w *
759 | eyy2 + 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * exy3 / ezz3 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * n * exx1 * exx4) / b
760 |
761 | bzynw = (-0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * (1 - eyy1 / ezz1) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 *
762 | eyy2 * e - 0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * exy1 / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * exx4 * s) / b
763 |
764 | bzysw = (0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * (1 - eyy2 / ezz2) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 *
765 | e - 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * exy2 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx3 * n * exx1 * exx4) / b
766 |
767 | bzyn = ((0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * ezz1 * ezz2 / eyy1 * (1 - eyy1 / ezz1) / w - 0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * ezz4 * ezz3 / eyy4 * (1 - eyy4 / ezz4) / e) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w *
768 | eyy2 * e + (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * (0.5 * ezz4 * (2. / n ** 2 + exy1 / ezz1 / n / w) / exx1 * ezz1 * w + 0.5 * ezz1 * (2. / n ** 2 - exy4 / ezz4 / n / e) / exx4 * ezz4 * e) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
769 |
770 | bzys = ((-0.5 * (-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * ezz2 * ezz1 / eyy2 * (1 - eyy2 / ezz2) / w + 0.5 * (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * ezz3 * ezz4 / eyy3 * (1 - eyy3 / ezz3) / e) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w *
771 | eyy2 * e - (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * (0.5 * ezz3 * (2. / s ** 2 - exy2 / ezz2 / s / w) / exx2 * ezz2 * w + 0.5 * ezz2 * (2. / s ** 2 + exy3 / ezz3 / s / e) / exx3 * ezz3 * e) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
772 |
773 | bzye = (((-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * (-n * ezz2 / eyy1 * eyx1 / e / (e + w) + (ezz1 - ezz2) * w / e / (e + w) - s * ezz1 / eyy2 * eyx2 / e / (e + w)) + (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * (0.5 * n * ezz4 * ezz3 / eyy4 * (-(1 - eyy4 / ezz4) / n / e - eyx4 / ezz4 * (2. / e ** 2 - 2 / e ** 2 * w / (e + w))) + 0.5 * s * ezz3 * ezz4 / eyy3 * ((1 - eyy3 / ezz3) / s / e - eyx3 / ezz3 * (2. / e ** 2 - 2 / e ** 2 * w / (e + w))) + (ezz4 - ezz3) * w / e / (e + w))) / ezz4 /
774 | ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e + (0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * ezz1 * (2 * exx4 / ezz4 / e ** 2 - exy4 / ezz4 / n / e) / exx4 * ezz4 * e - 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * ezz2 * (2 * exx3 / ezz3 / e ** 2 + exy3 / ezz3 / s / e) / exx3 * ezz3 * e) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
775 |
776 | bzyw = (((-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * (0.5 * n * ezz1 * ezz2 / eyy1 * ((1 - eyy1 / ezz1) / n / w - eyx1 / ezz1 * (2. / w ** 2 - 2 / w ** 2 * e / (e + w))) - (ezz1 - ezz2) * e / w / (e + w) + 0.5 * s * ezz2 * ezz1 / eyy2 * (-(1 - eyy2 / ezz2) / s / w - eyx2 / ezz2 * (2. / w ** 2 - 2 / w ** 2 * e / (e + w)))) + (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * (-n * ezz3 / eyy4 * eyx4 / w / (e + w) - s * ezz4 / eyy3 * eyx3 / w / (e + w) - (ezz4 - ezz3) * e / w / (e + w))) / ezz4 /
777 | ezz3 / (n * eyy3 + s * eyy4) / ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e + (0.5 * (ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * ezz4 * (2 * exx1 / ezz1 / w ** 2 + exy1 / ezz1 / n / w) / exx1 * ezz1 * w - 0.5 * (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * ezz3 * (2 * exx2 / ezz2 / w ** 2 - exy2 / ezz2 / s / w) / exx2 * ezz2 * w) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
778 |
779 | bzyp = (((-n * ezz4 * ezz3 / eyy4 - s * ezz3 * ezz4 / eyy3) * (0.5 * n * ezz1 * ezz2 / eyy1 * (-k ** 2 * eyx1 - (1 - eyy1 / ezz1) / n / w - eyx1 / ezz1 * (-2. / w ** 2 + 2 / w ** 2 * (e - w) / e)) + (ezz1 - ezz2) * (e - w) / e / w + 0.5 * s * ezz2 * ezz1 / eyy2 * (-k ** 2 * eyx2 + (1 - eyy2 / ezz2) / s / w - eyx2 / ezz2 * (-2. / w ** 2 + 2 / w ** 2 * (e - w) / e))) + (n * ezz1 * ezz2 / eyy1 + s * ezz2 * ezz1 / eyy2) * (0.5 * n * ezz4 * ezz3 / eyy4 * (-k ** 2 * eyx4 + (1 - eyy4 / ezz4) / n / e - eyx4 / ezz4 * (-2. / e ** 2 - 2 / e ** 2 * (e - w) / w)) + 0.5 * s * ezz3 * ezz4 / eyy3 * (-k ** 2 * eyx3 - (1 - eyy3 / ezz3) / s / e - eyx3 / ezz3 * (-2. / e ** 2 - 2 / e ** 2 * (e - w) / w)) + (ezz4 - ezz3) * (e - w) / e / w)) / ezz4 / ezz3 / (n * eyy3 + s * eyy4) /
780 | ezz2 / ezz1 / (n * eyy2 + s * eyy1) / (e + w) * eyy4 * eyy3 * eyy1 * w * eyy2 * e + ((ezz3 / exx2 * ezz2 * w + ezz2 / exx3 * ezz3 * e) * (0.5 * ezz4 * (-2. / n ** 2 - 2 * exx1 / ezz1 / w ** 2 + k ** 2 * exx1 - exy1 / ezz1 / n / w) / exx1 * ezz1 * w + 0.5 * ezz1 * (-2. / n ** 2 - 2 * exx4 / ezz4 / e ** 2 + k ** 2 * exx4 + exy4 / ezz4 / n / e) / exx4 * ezz4 * e) - (ezz4 / exx1 * ezz1 * w + ezz1 / exx4 * ezz4 * e) * (0.5 * ezz3 * (-2. / s ** 2 - 2 * exx2 / ezz2 / w ** 2 + k ** 2 * exx2 + exy2 / ezz2 / s / w) / exx2 * ezz2 * w + 0.5 * ezz2 * (-2. / s ** 2 - 2 * exx3 / ezz3 / e ** 2 + k ** 2 * exx3 - exy3 / ezz3 / s / e) / exx3 * ezz3 * e)) / ezz3 / ezz2 / (w * exx3 + e * exx2) / ezz4 / ezz1 / (w * exx4 + e * exx1) / (n + s) * exx2 * exx3 * n * exx1 * exx4 * s) / b
781 |
782 | ii = numpy.arange(nx * ny).reshape(nx, ny)
783 |
784 | # NORTH boundary
785 |
786 | ib = ii[:, -1]
787 |
788 | if boundary[0] == 'S':
789 | sign = 1
790 | elif boundary[0] == 'A':
791 | sign = -1
792 | elif boundary[0] == '0':
793 | sign = 0
794 | else:
795 | raise ValueError('unknown boundary conditions')
796 |
797 | bzxs[ib] += sign * bzxn[ib]
798 | bzxse[ib] += sign * bzxne[ib]
799 | bzxsw[ib] += sign * bzxnw[ib]
800 | bzys[ib] -= sign * bzyn[ib]
801 | bzyse[ib] -= sign * bzyne[ib]
802 | bzysw[ib] -= sign * bzynw[ib]
803 |
804 | # SOUTH boundary
805 |
806 | ib = ii[:, 0]
807 |
808 | if boundary[1] == 'S':
809 | sign = 1
810 | elif boundary[1] == 'A':
811 | sign = -1
812 | elif boundary[1] == '0':
813 | sign = 0
814 | else:
815 | raise ValueError('unknown boundary conditions')
816 |
817 | bzxn[ib] += sign * bzxs[ib]
818 | bzxne[ib] += sign * bzxse[ib]
819 | bzxnw[ib] += sign * bzxsw[ib]
820 | bzyn[ib] -= sign * bzys[ib]
821 | bzyne[ib] -= sign * bzyse[ib]
822 | bzynw[ib] -= sign * bzysw[ib]
823 |
824 | # EAST boundary
825 |
826 | ib = ii[-1, :]
827 |
828 | if boundary[2] == 'S':
829 | sign = 1
830 | elif boundary[2] == 'A':
831 | sign = -1
832 | elif boundary[2] == '0':
833 | sign = 0
834 | else:
835 | raise ValueError('unknown boundary conditions')
836 |
837 | bzxw[ib] += sign * bzxe[ib]
838 | bzxnw[ib] += sign * bzxne[ib]
839 | bzxsw[ib] += sign * bzxse[ib]
840 | bzyw[ib] -= sign * bzye[ib]
841 | bzynw[ib] -= sign * bzyne[ib]
842 | bzysw[ib] -= sign * bzyse[ib]
843 |
844 | # WEST boundary
845 |
846 | ib = ii[0, :]
847 |
848 | if boundary[3] == 'S':
849 | sign = 1
850 | elif boundary[3] == 'A':
851 | sign = -1
852 | elif boundary[3] == '0':
853 | sign = 0
854 | else:
855 | raise ValueError('unknown boundary conditions')
856 |
857 | bzxe[ib] += sign * bzxw[ib]
858 | bzxne[ib] += sign * bzxnw[ib]
859 | bzxse[ib] += sign * bzxsw[ib]
860 | bzye[ib] -= sign * bzyw[ib]
861 | bzyne[ib] -= sign * bzynw[ib]
862 | bzyse[ib] -= sign * bzysw[ib]
863 |
864 | # Assemble sparse matrix
865 |
866 | iall = ii.flatten()
867 | i_s = ii[:, :-1].flatten()
868 | i_n = ii[:, 1:].flatten()
869 | i_e = ii[1:, :].flatten()
870 | i_w = ii[:-1, :].flatten()
871 | i_ne = ii[1:, 1:].flatten()
872 | i_se = ii[1:, :-1].flatten()
873 | i_sw = ii[:-1, :-1].flatten()
874 | i_nw = ii[:-1, 1:].flatten()
875 |
876 | Izx = numpy.r_[iall, i_w, i_e, i_s, i_n, i_ne, i_se, i_sw, i_nw]
877 | Jzx = numpy.r_[iall, i_e, i_w, i_n, i_s, i_sw, i_nw, i_ne, i_se]
878 | Vzx = numpy.r_[bzxp[iall], bzxe[i_w], bzxw[i_e], bzxn[i_s], bzxs[
879 | i_n], bzxsw[i_ne], bzxnw[i_se], bzxne[i_sw], bzxse[i_nw]]
880 |
881 | Izy = numpy.r_[iall, i_w, i_e, i_s, i_n, i_ne, i_se, i_sw, i_nw]
882 | Jzy = numpy.r_[
883 | iall, i_e, i_w, i_n, i_s, i_sw, i_nw, i_ne, i_se] + nx * ny
884 | Vzy = numpy.r_[bzyp[iall], bzye[i_w], bzyw[i_e], bzyn[i_s], bzys[
885 | i_n], bzysw[i_ne], bzynw[i_se], bzyne[i_sw], bzyse[i_nw]]
886 |
887 | I = numpy.r_[Izx, Izy]
888 | J = numpy.r_[Jzx, Jzy]
889 | V = numpy.r_[Vzx, Vzy]
890 | B = coo_matrix((V, (I, J))).tocsr()
891 |
892 | HxHy = numpy.r_[Hx, Hy]
893 | Hz = B * HxHy.ravel() / 1j
894 | Hz = Hz.reshape(Hx.shape)
895 |
896 | # in xc e yc
897 | exx = epsxx[1:-1, 1:-1]
898 | exy = epsxy[1:-1, 1:-1]
899 | eyx = epsyx[1:-1, 1:-1]
900 | eyy = epsyy[1:-1, 1:-1]
901 | ezz = epszz[1:-1, 1:-1]
902 | edet = (exx * eyy - exy * eyx)
903 |
904 | h = e.reshape(nx, ny)[:-1, :-1]
905 | v = n.reshape(nx, ny)[:-1, :-1]
906 |
907 | # in xc e yc
908 | Dx = neff * centered2d(Hy) + (
909 | Hz[:-1, 1:] + Hz[1:, 1:] - Hz[:-1, :-1] - Hz[1:, :-1]) / (2j * k * v)
910 | Dy = -neff * centered2d(Hx) - (
911 | Hz[1:, :-1] + Hz[1:, 1:] - Hz[:-1, 1:] - Hz[:-1, :-1]) / (2j * k * h)
912 | Dz = ((Hy[1:, :-1] + Hy[1:, 1:] - Hy[:-1, 1:] - Hy[:-1, :-1]) / (2 * h) -
913 | (Hx[:-1, 1:] + Hx[1:, 1:] - Hx[:-1, :-1] - Hx[1:, :-1]) / (2 * v)) / (1j * k)
914 |
915 | Ex = (eyy * Dx - exy * Dy) / edet
916 | Ey = (exx * Dy - eyx * Dx) / edet
917 | Ez = Dz / ezz
918 |
919 | Hzs.append(Hz)
920 | Exs.append(Ex)
921 | Eys.append(Ey)
922 | Ezs.append(Ez)
923 |
924 | return (Hzs, Exs, Eys, Ezs)
925 |
926 | def solve(self, neigs=4, tol=0, guess=None, mode_profiles=True, initial_mode_guess=None):
927 | """
928 | This function finds the eigenmodes.
929 |
930 | Parameters
931 | ----------
932 | neigs : int
933 | number of eigenmodes to find
934 | tol : float
935 | Relative accuracy for eigenvalues. The default value of 0 implies machine precision.
936 | guess : float
937 | a guess for the refractive index. Only finds eigenvectors with an effective refractive index
938 | higher than this value.
939 |
940 | Returns
941 | -------
942 | self : an instance of the VFDModeSolver class
943 | obtain the fields of interest for specific modes using, for example:
944 | solver = EMpy.modesolvers.FD.VFDModeSolver(wavelength, x, y, epsf, boundary).solve()
945 | Ex = solver.modes[0].Ex
946 | Ey = solver.modes[0].Ey
947 | Ez = solver.modes[0].Ez
948 | """
949 |
950 | from scipy.sparse.linalg import eigen
951 |
952 | self.nmodes = neigs
953 | self.tol = tol
954 |
955 | A = self.build_matrix()
956 |
957 | if guess is not None:
958 | # calculate shift for eigs function
959 | k = 2 * numpy.pi / self.wl
960 | shift = (guess * k) ** 2
961 | else:
962 | shift = None
963 |
964 | [eigvals, eigvecs] = eigen.eigs(A,
965 | k=neigs,
966 | which='LR',
967 | tol=0.001,
968 | ncv=None,
969 | v0 = initial_mode_guess,
970 | return_eigenvectors=mode_profiles,
971 | sigma=shift)
972 |
973 | neffs = self.wl * scipy.sqrt(eigvals) / (2 * numpy.pi)
974 | if mode_profiles:
975 | Hxs = []
976 | Hys = []
977 | nx = self.nx
978 | ny = self.ny
979 | for ieig in range(neigs):
980 | Hxs.append(eigvecs[:nx * ny, ieig].reshape(nx, ny))
981 | Hys.append(eigvecs[nx * ny:, ieig].reshape(nx, ny))
982 |
983 | # sort the modes
984 | idx = numpy.flipud(numpy.argsort(neffs))
985 | neffs = neffs[idx]
986 | self.neff = neffs
987 | if mode_profiles:
988 | tmpx = []
989 | tmpy = []
990 | for i in idx:
991 | tmpx.append(Hxs[i])
992 | tmpy.append(Hys[i])
993 | Hxs = tmpx
994 | Hys = tmpy
995 |
996 | [Hzs, Exs, Eys, Ezs] = self.compute_other_fields(neffs, Hxs, Hys)
997 |
998 | self.modes = []
999 | for (neff, Hx, Hy, Hz, Ex, Ey, Ez) in zip(neffs, Hxs, Hys, Hzs, Exs, Eys, Ezs):
1000 | self.modes.append(
1001 | FDMode(self.wl, self.x, self.y, neff, Ey, Ex, Ez, Hy, Hx, Hz).normalize())
1002 |
1003 | return self
1004 |
1005 | def __str__(self):
1006 | descr = 'Vectorial Finite Difference Modesolver\n'
1007 | return descr
1008 |
1009 |
1010 | class FDMode():
1011 | def __init__(self, wl, x, y, neff, Ex, Ey, Ez, Hx, Hy, Hz):
1012 | self.wl = wl
1013 | self.x = x
1014 | self.y = y
1015 | self.neff = neff
1016 | self.Ex = Ex
1017 | self.Ey = Ey
1018 | self.Ez = Ez
1019 | self.Hx = Hx
1020 | self.Hy = Hy
1021 | self.Hz = Hz
1022 |
1023 | self.fields = col.OrderedDict({
1024 | 'Ex': Ex,
1025 | 'Ey': Ey,
1026 | 'Ez': Ez,
1027 | 'Hx': Hx,
1028 | 'Hy': Hy,
1029 | 'Hz': Hz
1030 | })
1031 |
1032 | def norm(self):
1033 | x = centered1d(self.x)
1034 | y = centered1d(self.y)
1035 | return scipy.sqrt(trapz2(self.intensity(), x=x, y=y))
1036 |
1037 | def normalize(self):
1038 | n = self.norm()
1039 | self.Ex /= n
1040 | self.Ey /= n
1041 | self.Ez /= n
1042 | self.Hx /= n
1043 | self.Hy /= n
1044 | self.Hz /= n
1045 |
1046 | return self
1047 |
1048 | def intensityTETM(self, x=None, y=None):
1049 | I_TE = self.Ex * centered2d(numpy.conj(self.Hy)) / 2.
1050 | I_TM = -self.Ey * centered2d(numpy.conj(self.Hx)) / 2.
1051 | if x is None and y is None:
1052 | return (I_TE, I_TM)
1053 | else:
1054 | x0 = centered1d(self.x)
1055 | y0 = centered1d(self.y)
1056 | I_TE_ = interp2(x, y, x0, y0, I_TE)
1057 | I_TM_ = interp2(x, y, x0, y0, I_TM)
1058 | return (I_TE_, I_TM_)
1059 |
1060 | def intensity(self, x=None, y=None):
1061 | I_TE, I_TM = self.intensityTETM(x, y)
1062 | return I_TE + I_TM
1063 |
1064 |
--------------------------------------------------------------------------------
/modesolverpy/coupling_efficiency.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | def _make_gaussian(x_pts, y_pts, mfd, x_offset=0, y_offset=0):
4 | x0 = (x_pts[-1]+x_pts[0])/2 + x_offset
5 | y0 = (y_pts[-1]+y_pts[0])/2 + y_offset
6 | xx, yy = np.meshgrid(x_pts, y_pts)
7 |
8 | sigma = mfd * 0.707 / 2.355
9 | sigma_x = sigma
10 | sigma_y = sigma
11 |
12 | gaus_2d = np.exp(-((xx-x0)**2/(2*sigma_x**2)+
13 | (yy-y0)**2/(2*sigma_y**2)))
14 | gaus_2d /= np.sum(gaus_2d)
15 |
16 | return gaus_2d
17 |
18 | def _overlap(mode, gaussian):
19 | mode_1 = mode
20 | mode_2 = np.sqrt(gaussian) # square-root for E-field (not power)
21 | eta = np.abs(np.sum(np.conj(mode_1)*mode_2))**2 / \
22 | (np.sum(np.abs(mode_1)**2) * np.sum(np.abs(mode_2)**2))
23 | return eta
24 |
25 | def reflection(n1, n2):
26 | '''
27 | Calculate the power reflection at the interface
28 | of two refractive index materials.
29 |
30 | Args:
31 | n1 (float): Refractive index of material 1.
32 | n2 (float): Refractive index of material 2.
33 |
34 | Returns:
35 | float: The percentage of reflected power.
36 | '''
37 | r = abs((n1-n2) / (n1+n2))**2
38 | return r
39 |
40 | def transmission(n1, n2):
41 | '''
42 | Calculate the power transmission at the interface
43 | of two refractive index materials.
44 |
45 | Args:
46 | n1 (float): Refractive index of material 1.
47 | n2 (float): Refractive index of material 2.
48 |
49 | Returns:
50 | float: The percentage of transmitted power.
51 | '''
52 | return 1-reflection(n1, n2)
53 |
54 | def coupling_efficiency(mode_solver, fibre_mfd,
55 | fibre_offset_x=0, fibre_offset_y=0,
56 | n_eff_fibre=1.441):
57 | '''
58 | Finds the coupling efficiency between a solved
59 | fundamental mode and a fibre of given MFD.
60 |
61 | Args:
62 | mode_solver (_ModeSolver): Mode solver that
63 | has found a fundamental mode.
64 | fibre_mfd (float): The mode-field diameter
65 | (MFD) of the fibre.
66 | fibre_offset_x (float): Offset the fibre
67 | from the centre position of the window
68 | in x. Default is 0 (no offset).
69 | fibre_offset_y (float): Offset the fibre
70 | from the centre position of the window
71 | in y. Default is 0 (no offset).
72 | n_eff_fibre (float): The effective index
73 | of the fibre mode. Default is 1.441.
74 |
75 | Returns:
76 | float: The power coupling efficiency.
77 | '''
78 | etas = []
79 |
80 | gaus = _make_gaussian(mode_solver._structure.xc, mode_solver._structure.yc,
81 | fibre_mfd, fibre_offset_x, fibre_offset_y)
82 |
83 | for mode, n_eff in zip(mode_solver.modes, mode_solver.n_effs):
84 | o = abs(_overlap(mode, gaus))
85 | t = abs(transmission(n_eff, n_eff_fibre))
86 | eta = o * t
87 | etas.append(eta)
88 |
89 | return etas
90 |
--------------------------------------------------------------------------------
/modesolverpy/design.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 |
4 | def directional_coupler_lc(wavelength_nm, n_eff_1, n_eff_2):
5 | '''
6 | Calculates the coherence length (100% power transfer) of a
7 | directional coupler.
8 |
9 | Args:
10 | wavelength_nm (float): The wavelength in [nm] the
11 | directional coupler should operate at.
12 | n_eff_1 (float): n_eff of the fundamental (even)
13 | supermode of the directional coupler.
14 | n_eff_2 (float): n_eff of the first-order (odd)
15 | supermode of the directional coupler.
16 |
17 | Returns:
18 | float: The length [um] the directional coupler
19 | needs to be to achieve 100% power transfer.
20 |
21 | '''
22 | wavelength_m = wavelength_nm * 1.e-9
23 | dn_eff = (n_eff_1 - n_eff_2).real
24 | lc_m = wavelength_m / (2. * dn_eff)
25 | lc_um = lc_m * 1.e6
26 | return lc_um
27 |
28 |
29 | def grating_coupler_period(wavelength,
30 | n_eff,
31 | n_clad,
32 | incidence_angle_deg,
33 | diffration_order=1):
34 | '''
35 | Calculate the period needed for a grating coupler.
36 |
37 | Args:
38 | wavelength (float): The target wavelength for the
39 | grating coupler.
40 | n_eff (float): The effective index of the mode
41 | of a waveguide with the width of the grating
42 | coupler.
43 | n_clad (float): The refractive index of the cladding.
44 | incidence_angle_deg (float): The incidence angle
45 | the grating coupler should operate at [degrees].
46 | diffration_order (int): The grating order the coupler
47 | should work at. Default is 1st order (1).
48 |
49 | Returns:
50 | float: The period needed for the grating coupler
51 | in the same units as the wavelength was given at.
52 | '''
53 | k0 = 2. * np.pi / wavelength
54 | beta = n_eff.real * k0
55 | n_inc = n_clad
56 |
57 | grating_period = (2.*np.pi*diffration_order) \
58 | / (beta - k0*n_inc*np.sin(np.radians(incidence_angle_deg)))
59 |
60 | return grating_period
61 |
62 |
63 | def loss(n, wavelength):
64 | kappa = n.imag
65 | alpha = 4.34 * 4 * np.pi * np.abs(
66 | kappa) / wavelength # 4.34 = 10*np.log10(np.e) -> [dB/m] = 4.34 [/m]
67 | return alpha # [db/um] if working in [um]
68 |
69 |
70 | def qpm_wavenumber(pmp_n,
71 | pmp_l,
72 | sig_n,
73 | sig_l,
74 | idl_n,
75 | idl_l,
76 | period_qpm,
77 | type='forward'):
78 | pi2 = np.pi * 2
79 |
80 | k_pmp = pmp_n * pi2 / pmp_l
81 | k_sig = sig_n * pi2 / sig_l
82 | k_idl = idl_n * pi2 / idl_l
83 | k_qpm = pi2 / period_qpm
84 |
85 | if type == 'forward':
86 | sgn_1 = 1
87 | sgn_2 = 1
88 | elif type == 'forward_backward':
89 | sgn_1 = 1
90 | sgn_2 = -1
91 | elif type == 'backward':
92 | sgn_1 = -1
93 | sgn_2 = -1
94 |
95 | k_mismatch = k_idl * sgn_1 + k_sig * sgn_2 + k_qpm - k_pmp
96 | return k_mismatch
97 |
98 |
99 | def qpm_period(pmp_n, pmp_l, sig_n, sig_l, idl_n, idl_l, type='forward'):
100 | pi2 = np.pi * 2
101 |
102 | k_pmp = pmp_n * pi2 / pmp_l
103 | k_sig = sig_n * pi2 / sig_l
104 | k_idl = idl_n * pi2 / idl_l
105 |
106 | if type == 'forward':
107 | sgn_1 = 1
108 | sgn_2 = 1
109 | elif type == 'forward_backward':
110 | sgn_1 = 1
111 | sgn_2 = -1
112 | elif type == 'backward':
113 | sgn_1 = -1
114 | sgn_2 = -1
115 |
116 | k_qpm = k_pmp - k_idl * sgn_1 - k_sig * sgn_2
117 | l_qpm = pi2 / k_qpm
118 | return l_qpm
119 |
--------------------------------------------------------------------------------
/modesolverpy/fractions.gpi:
--------------------------------------------------------------------------------
1 | set dataf sep ','
2 |
3 | set term pngcairo size 30cm,20cm
4 | set out filename_image
5 |
6 | set key below
7 | set grid
8 | set border lw 1.5
9 |
10 | set xlabel xlab
11 | set ylabel ylab
12 | set title titl font ',14'
13 |
14 | set yrange [0:100]
15 | set ytics 0,25,100
16 |
17 | plot for [col in mode_list] filename_data u 1:(column(col+2)*100.) w lp lw 2 pt 7 title sprintf('Mode %s', col)
18 |
--------------------------------------------------------------------------------
/modesolverpy/mode.gpi:
--------------------------------------------------------------------------------
1 | set dataf sep ','
2 |
3 | set term pngcairo size 25cm,20cm enhanced
4 | set out filename_image
5 |
6 | set view map
7 | set palette defined (0 "green", 1 "blue", 2 "black", 3 "red", 4 "yellow")
8 | stats filename_data matrix nooutput
9 | if (abs(STATS_max) > abs(STATS_min)) {
10 | inv = 1
11 | smm = abs(STATS_max)
12 | } else {
13 | inv = -1
14 | smm = abs(STATS_min)
15 | }
16 | set cbrange [-smm:smm]
17 | set colorbox
18 |
19 | unset key
20 | set tics scale 0
21 | unset border
22 | set size ratio -1
23 |
24 | set title title
25 | set xlabel 'x'
26 | set ylabel 'y'
27 |
28 | set xrange [x_min:x_max]
29 | set yrange [y_min:y_max]
30 |
31 | set pm3d interpolate 4,4
32 |
33 | if (e2_x > 0. && ctr_x > 0.) {
34 | x1 = ctr_x - e2_x / 2.
35 | x2 = ctr_x + e2_x / 2.
36 | set arrow from x1,y_min,0 to x1,y_max-y_step,0 lc rgb 'gray' nohead front
37 | set arrow from x2,y_min,0 to x2,y_max-y_step,0 lc rgb 'gray' nohead front
38 | }
39 |
40 | if (e2_y > 0. && ctr_y > 0.) {
41 | y1 = ctr_y - e2_y / 2.
42 | y2 = ctr_y + e2_y / 2.
43 | set arrow from x_min,y1,0 to x_max,y1,0 lc rgb 'gray' nohead front
44 | set arrow from x_min,y2,0 to x_max,y2,0 lc rgb 'gray' nohead front
45 | }
46 |
47 | if (ctr_x > 0. && ctr_y > 0.) {
48 | set label point pt 2 at ctr_x,ctr_y front
49 | }
50 |
51 | splot filename_data matrix u (x_step*$1 + x_min):\
52 | (y_step*$2 + y_min):(inv*$3) w pm3d
53 |
--------------------------------------------------------------------------------
/modesolverpy/mode_solver.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import abc
3 | import os
4 | import sys
5 | import subprocess
6 | import copy
7 | import tqdm
8 | import numpy as np
9 | from six import with_metaclass
10 |
11 | from . import _mode_solver_lib as ms
12 | from . import _analyse as anal
13 | from . import structure_base as stb
14 |
15 | try:
16 | devnull = open(os.devnull, "w")
17 | subprocess.call(["gnuplot", "--version"], stdout=devnull, stderr=devnull)
18 | import gnuplotpy as gp
19 |
20 | MPL = False
21 | except:
22 | import matplotlib.pylab as plt
23 |
24 | MPL = True
25 |
26 | def use_gnuplot():
27 | """
28 | Use gnuplot as the plotting tool for any mode related outputs.
29 | """
30 | global gp
31 | import gnuplotpy as gp
32 | global MPL
33 | MPL = False
34 |
35 | def use_matplotlib():
36 | """
37 | Use matplotlib as the plotting tool for any mode related outputs.
38 | """
39 | global plt
40 | import matplotlib.pylab as plt
41 | global MPL
42 | MPL = True
43 |
44 | class _ModeSolver(with_metaclass(abc.ABCMeta)):
45 | def __init__(
46 | self,
47 | n_eigs,
48 | tol=0.0,
49 | boundary="0000",
50 | mode_profiles=True,
51 | initial_mode_guess=None,
52 | n_eff_guess=None,
53 | ):
54 | self._n_eigs = int(n_eigs)
55 | self._tol = tol
56 | self._boundary = boundary
57 | self._mode_profiles = mode_profiles
58 | self._initial_mode_guess = initial_mode_guess
59 | self._n_eff_guess = n_eff_guess
60 |
61 | self.n_effs = None
62 | self.modes = None
63 | self.mode_types = None
64 | self.overlaps = None
65 |
66 | self._path = os.path.dirname(sys.modules[__name__].__file__) + "/"
67 |
68 | @abc.abstractproperty
69 | def _modes_directory(self):
70 | pass
71 |
72 | @abc.abstractmethod
73 | def _solve(self, structure, wavelength):
74 | pass
75 |
76 | def solve(self, structure):
77 | """
78 | Find the modes of a given structure.
79 |
80 | Args:
81 | structure (Structure): The target structure to solve
82 | for modes.
83 |
84 | Returns:
85 | dict: The 'n_effs' key gives the effective indices
86 | of the modes. The 'modes' key exists of mode
87 | profiles were solved for; in this case, it will
88 | return arrays of the mode profiles.
89 | """
90 | return self._solve(structure, structure._wl)
91 |
92 | def solve_sweep_structure(
93 | self,
94 | structures,
95 | sweep_param_list,
96 | filename="structure_n_effs.dat",
97 | plot=True,
98 | x_label="Structure number",
99 | fraction_mode_list=[],
100 | ):
101 | """
102 | Find the modes of many structures.
103 |
104 | Args:
105 | structures (list): A list of `Structures` to find the modes
106 | of.
107 | sweep_param_list (list): A list of the parameter-sweep sweep
108 | that was used. This is for plotting purposes only.
109 | filename (str): The nominal filename to use when saving the
110 | effective indices. Defaults to 'structure_n_effs.dat'.
111 | plot (bool): `True` if plots should be generates,
112 | otherwise `False`. Default is `True`.
113 | x_label (str): x-axis text to display in the plot.
114 | fraction_mode_list (list): A list of mode indices of the modes
115 | that should be included in the TE/TM mode fraction plot.
116 | If the list is empty, all modes will be included. The list
117 | is empty by default.
118 |
119 | Returns:
120 | list: A list of the effective indices found for each structure.
121 | """
122 | n_effs = []
123 | mode_types = []
124 | fractions_te = []
125 | fractions_tm = []
126 | for s in tqdm.tqdm(structures, ncols=70):
127 | self.solve(s)
128 | n_effs.append(np.real(self.n_effs))
129 | mode_types.append(self._get_mode_types())
130 | fractions_te.append(self.fraction_te)
131 | fractions_tm.append(self.fraction_tm)
132 |
133 | if filename:
134 | self._write_n_effs_to_file(
135 | n_effs, self._modes_directory + filename, sweep_param_list
136 | )
137 |
138 | with open(self._modes_directory + "mode_types.dat", "w") as fs:
139 | header = ",".join(
140 | "Mode%i" % i for i, _ in enumerate(mode_types[0])
141 | )
142 | fs.write("# " + header + "\n")
143 | for mt in mode_types:
144 | txt = ",".join("%s %.2f" % pair for pair in mt)
145 | fs.write(txt + "\n")
146 |
147 | with open(self._modes_directory + "fraction_te.dat", "w") as fs:
148 | header = "fraction te"
149 | fs.write("# param sweep," + header + "\n")
150 | for param, fte in zip(sweep_param_list, fractions_te):
151 | txt = "%.6f," % param
152 | txt += ",".join("%.2f" % f for f in fte)
153 | fs.write(txt + "\n")
154 |
155 | with open(self._modes_directory + "fraction_tm.dat", "w") as fs:
156 | header = "fraction tm"
157 | fs.write("# param sweep," + header + "\n")
158 | for param, ftm in zip(sweep_param_list, fractions_tm):
159 | txt = "%.6f," % param
160 | txt += ",".join("%.2f" % f for f in ftm)
161 | fs.write(txt + "\n")
162 |
163 | if plot:
164 | if MPL:
165 | title = "$n_{eff}$ vs %s" % x_label
166 | y_label = "$n_{eff}$"
167 | else:
168 | title = "n_{effs} vs %s" % x_label
169 | y_label = "n_{eff}"
170 | self._plot_n_effs(
171 | self._modes_directory + filename, self._modes_directory + "fraction_te.dat", x_label, y_label, title
172 | )
173 |
174 | title = "TE Fraction vs %s" % x_label
175 | self._plot_fraction(
176 | self._modes_directory + "fraction_te.dat",
177 | x_label,
178 | "TE Fraction [%]",
179 | title,
180 | fraction_mode_list,
181 | )
182 |
183 | title = "TM Fraction vs %s" % x_label
184 | self._plot_fraction(
185 | self._modes_directory + "fraction_tm.dat",
186 | x_label,
187 | "TM Fraction [%]",
188 | title,
189 | fraction_mode_list,
190 | )
191 |
192 | return n_effs
193 |
194 | def solve_sweep_wavelength(
195 | self,
196 | structure,
197 | wavelengths,
198 | filename="wavelength_n_effs.dat",
199 | plot=True,
200 | ):
201 | """
202 | Solve for the effective indices of a fixed structure at
203 | different wavelengths.
204 |
205 | Args:
206 | structure (Slabs): The target structure to solve
207 | for modes.
208 | wavelengths (list): A list of wavelengths to sweep
209 | over.
210 | filename (str): The nominal filename to use when saving the
211 | effective indices. Defaults to 'wavelength_n_effs.dat'.
212 | plot (bool): `True` if plots should be generates,
213 | otherwise `False`. Default is `True`.
214 |
215 | Returns:
216 | list: A list of the effective indices found for each wavelength.
217 | """
218 | n_effs = []
219 | for w in tqdm.tqdm(wavelengths, ncols=70):
220 | structure.change_wavelength(w)
221 | self.solve(structure)
222 | n_effs.append(np.real(self.n_effs))
223 |
224 | if filename:
225 | self._write_n_effs_to_file(
226 | n_effs, self._modes_directory + filename, wavelengths
227 | )
228 | if plot:
229 | if MPL:
230 | title = "$n_{eff}$ vs Wavelength"
231 | y_label = "$n_{eff}$"
232 | else:
233 | title = "n_{effs} vs Wavelength" % x_label
234 | y_label = "n_{eff}"
235 | self._plot_n_effs(
236 | self._modes_directory + filename,
237 | self._modes_directory + "fraction_te.dat",
238 | "Wavelength",
239 | "n_{eff}",
240 | title,
241 | )
242 |
243 | return n_effs
244 |
245 | def solve_ng(self, structure, wavelength_step=0.01, filename="ng.dat"):
246 | r"""
247 | Solve for the group index, :math:`n_g`, of a structure at a particular
248 | wavelength.
249 |
250 | Args:
251 | structure (Structure): The target structure to solve
252 | for modes.
253 | wavelength_step (float): The step to take below and
254 | above the nominal wavelength. This is used for
255 | approximating the gradient of :math:`n_\mathrm{eff}`
256 | at the nominal wavelength. Default is 0.01.
257 | filename (str): The nominal filename to use when saving the
258 | effective indices. Defaults to 'ng.dat'.
259 |
260 | Returns:
261 | list: A list of the group indices found for each mode.
262 | """
263 | wl_nom = structure._wl
264 |
265 | self.solve(structure)
266 | n_ctrs = self.n_effs
267 |
268 | structure.change_wavelength(wl_nom - wavelength_step)
269 | self.solve(structure)
270 | n_bcks = self.n_effs
271 |
272 | structure.change_wavelength(wl_nom + wavelength_step)
273 | self.solve(structure)
274 | n_frws = self.n_effs
275 |
276 | n_gs = []
277 | for n_ctr, n_bck, n_frw in zip(n_ctrs, n_bcks, n_frws):
278 | n_gs.append(
279 | n_ctr - wl_nom * (n_frw - n_bck) / (2 * wavelength_step)
280 | )
281 |
282 | if filename:
283 | with open(self._modes_directory + filename, "w") as fs:
284 | fs.write("# Mode idx, Group index\n")
285 | for idx, n_g in enumerate(n_gs):
286 | fs.write("%i,%.3f\n" % (idx, np.round(n_g.real, 3)))
287 |
288 | return n_gs
289 |
290 | def _get_mode_filename(self, field_name, mode_number, filename):
291 | filename_prefix, filename_ext = os.path.splitext(filename)
292 | filename_mode = (
293 | filename_prefix
294 | + "_"
295 | + field_name
296 | + "_"
297 | + str(mode_number)
298 | + filename_ext
299 | )
300 | return filename_mode
301 |
302 | def _write_n_effs_to_file(self, n_effs, filename, x_vals=None):
303 | with open(filename, "w") as fs:
304 | fs.write('# Sweep param, mode 1, mode 2, ...\n')
305 | for i, n_eff in enumerate(n_effs):
306 | if x_vals is not None:
307 | line_start = str(x_vals[i]) + ","
308 | else:
309 | line_start = ""
310 | line = ",".join([str(np.round(n, 3)) for n in n_eff])
311 | fs.write(line_start + line + "\n")
312 | return n_effs
313 |
314 | def _write_mode_to_file(self, mode, filename):
315 | with open(filename, "w") as fs:
316 | for e in mode[::-1]:
317 | e_str = ",".join([str(v) for v in e])
318 | fs.write(e_str + "\n")
319 | return mode
320 |
321 | def _plot_n_effs(self, filename_n_effs, filename_te_fractions, xlabel, ylabel, title):
322 | args = {
323 | "titl": title,
324 | "xlab": xlabel,
325 | "ylab": ylabel,
326 | "filename_data": filename_n_effs,
327 | "filename_frac_te": filename_te_fractions,
328 | "filename_image": None,
329 | "num_modes": len(self.modes),
330 | }
331 |
332 | filename_image_prefix, _ = os.path.splitext(filename_n_effs)
333 | filename_image = filename_image_prefix + ".png"
334 | args["filename_image"] = filename_image
335 |
336 | if MPL:
337 | data = np.loadtxt(args["filename_data"], delimiter=",").T
338 | plt.clf()
339 | plt.title(title)
340 | plt.xlabel(args["xlab"])
341 | plt.ylabel(args["ylab"])
342 | for i in range(args["num_modes"]):
343 | plt.plot(data[0], data[i + 1], "-o")
344 | plt.savefig(args["filename_image"])
345 | else:
346 | gp.gnuplot(self._path + "n_effs.gpi", args, silent=False)
347 | gp.trim_pad_image(filename_image)
348 |
349 | return args
350 |
351 | def _plot_fraction(
352 | self, filename_fraction, xlabel, ylabel, title, mode_list=[]
353 | ):
354 | if not mode_list:
355 | mode_list = range(len(self.modes))
356 | gp_mode_list = " ".join(str(idx) for idx in mode_list)
357 |
358 | args = {
359 | "titl": title,
360 | "xlab": xlabel,
361 | "ylab": ylabel,
362 | "filename_data": filename_fraction,
363 | "filename_image": None,
364 | "mode_list": gp_mode_list,
365 | }
366 |
367 | filename_image_prefix, _ = os.path.splitext(filename_fraction)
368 | filename_image = filename_image_prefix + ".png"
369 | args["filename_image"] = filename_image
370 |
371 | if MPL:
372 | data = np.loadtxt(args["filename_data"], delimiter=",").T
373 | plt.clf()
374 | plt.title(title)
375 | plt.xlabel(args["xlab"])
376 | plt.ylabel(args["ylab"])
377 | for i, _ in enumerate(self.modes):
378 | plt.plot(data[0], data[i + 1], "-o")
379 | plt.savefig(args["filename_image"])
380 | else:
381 | gp.gnuplot(self._path + "fractions.gpi", args, silent=False)
382 | gp.trim_pad_image(filename_image)
383 |
384 | return args
385 |
386 | def _plot_mode(
387 | self,
388 | field_name,
389 | mode_number,
390 | filename_mode,
391 | n_eff=None,
392 | subtitle="",
393 | e2_x=0.0,
394 | e2_y=0.0,
395 | ctr_x=0.0,
396 | ctr_y=0.0,
397 | area=None,
398 | wavelength=None,
399 | ):
400 | fn = field_name[0] + "_{" + field_name[1:] + "}"
401 | if MPL:
402 | title = r"Mode %i $|%s|$ Profile" % (mode_number, fn)
403 | else:
404 | title = r"Mode %i |%s| Profile" % (mode_number, fn)
405 | if n_eff:
406 | if MPL:
407 | title += r", $n_{eff}$: " + "{:.3f}".format(n_eff.real)
408 | else:
409 | title += ", n_{eff}: " + "{:.3f}".format(n_eff.real)
410 | if wavelength:
411 | if MPL:
412 | title += r", $\lambda = %s " % "{:.3f} \mu$m".format(wavelength)
413 | else:
414 | title += r", $\lambda = %s " % "{:.3f} \mu$m".format(wavelength)
415 | if area:
416 | if MPL:
417 | title += ", $A_%s$: " % field_name[1] + "{:.1f}%".format(area)
418 | else:
419 | title += ", A_%s: " % field_name[1] + "{:.1f}\%".format(area)
420 |
421 | if subtitle:
422 | if MPL:
423 | title2 = "\n$%s$" % subtitle
424 | else:
425 | title += "\n{/*0.7 %s}" % subtitle
426 |
427 | args = {
428 | "title": title,
429 | "x_pts": self._structure.xc_pts,
430 | "y_pts": self._structure.yc_pts,
431 | "x_min": self._structure.xc_min,
432 | "x_max": self._structure.xc_max,
433 | "y_min": self._structure.yc_min,
434 | "y_max": self._structure.yc_max,
435 | "x_step": self._structure.x_step,
436 | "y_step": self._structure.y_step,
437 | "filename_data": filename_mode,
438 | "filename_image": None,
439 | "e2_x": e2_x,
440 | "e2_y": e2_y,
441 | "ctr_x": ctr_x,
442 | "ctr_y": ctr_y,
443 | }
444 |
445 | filename_image_prefix, _ = os.path.splitext(filename_mode)
446 | filename_image = filename_image_prefix + ".png"
447 | args["filename_image"] = filename_image
448 |
449 | if MPL:
450 | heatmap = np.loadtxt(filename_mode, delimiter=",")
451 | plt.clf()
452 | plt.suptitle(title)
453 | if subtitle:
454 | plt.rcParams.update({"axes.titlesize": "small"})
455 | plt.title(title2)
456 | plt.xlabel("x")
457 | plt.ylabel("y")
458 | plt.imshow(
459 | np.flipud(heatmap),
460 | extent=(
461 | args["x_min"],
462 | args["x_max"],
463 | args["y_min"],
464 | args["y_max"],
465 | ),
466 | aspect="auto",
467 | )
468 | plt.colorbar()
469 | plt.savefig(filename_image)
470 | else:
471 | gp.gnuplot(self._path + "mode.gpi", args)
472 | gp.trim_pad_image(filename_image)
473 |
474 | return args
475 |
476 |
477 | class ModeSolverSemiVectorial(_ModeSolver):
478 | """
479 | A semi-vectorial mode solver object used to
480 | setup and run a mode solving simulation.
481 |
482 | Args:
483 | n_eigs (int): The number of eigen-values to solve for.
484 | tol (float): The precision of the eigen-value/eigen-vector
485 | solver. Default is 0.001.
486 | boundary (str): The boundary conditions to use.
487 | This is a string that identifies the type of boundary conditions applied.
488 | The following options are available: 'A' - Hx is antisymmetric, Hy is symmetric,
489 | 'S' - Hx is symmetric and, Hy is antisymmetric, and '0' - Hx and Hy are zero
490 | immediately outside of the boundary.
491 | The string identifies all four boundary conditions, in the order:
492 | North, south, east, west. For example, boundary='000A'. Default is '0000'.
493 | mode_profiles (bool): `True if the the mode-profiles should be found, `False`
494 | if only the effective indices should be found.
495 | initial_mode_guess (list): An initial mode guess for the modesolver.
496 | semi_vectorial_method (str): Either 'Ex' or 'Ey'. If 'Ex', the mode solver
497 | will only find TE modes (horizontally polarised to the simulation window),
498 | if 'Ey', the mode solver will find TM modes (vertically polarised to the
499 | simulation window).
500 | """
501 |
502 | def __init__(
503 | self,
504 | n_eigs,
505 | tol=0.001,
506 | boundary="0000",
507 | mode_profiles=True,
508 | initial_mode_guess=None,
509 | semi_vectorial_method="Ex",
510 | ):
511 | self._semi_vectorial_method = semi_vectorial_method
512 | _ModeSolver.__init__(
513 | self, n_eigs, tol, boundary, mode_profiles, initial_mode_guess
514 | )
515 |
516 | @property
517 | def _modes_directory(self):
518 | modes_directory = "./modes_semi_vec/"
519 | if not os.path.exists(modes_directory):
520 | os.mkdir(modes_directory)
521 | _modes_directory = modes_directory
522 | return _modes_directory
523 |
524 | def _solve(self, structure, wavelength):
525 | self._structure = structure
526 | self._ms = ms._ModeSolverSemiVectorial(
527 | wavelength, structure, self._boundary, self._semi_vectorial_method
528 | )
529 | self._ms.solve(
530 | self._n_eigs,
531 | self._tol,
532 | self._mode_profiles,
533 | initial_mode_guess=self._initial_mode_guess,
534 | )
535 | self.n_effs = self._ms.neff
536 |
537 | r = {"n_effs": self.n_effs}
538 |
539 | if self._mode_profiles:
540 | r["modes"] = self._ms.modes
541 | self._ms.modes[0] = np.real(self._ms.modes[0])
542 | self._initial_mode_guess = np.real(self._ms.modes[0])
543 |
544 | self.modes = self._ms.modes
545 |
546 | return r
547 |
548 | def write_modes_to_file(self, filename="mode.dat", plot=True, analyse=True):
549 | """
550 | Writes the mode fields to a file and optionally plots them.
551 |
552 | Args:
553 | filename (str): The nominal filename to use for the saved
554 | data. The suffix will be automatically be changed to
555 | identifiy each mode number. Default is 'mode.dat'
556 | plot (bool): `True` if plots should be generates,
557 | otherwise `False`. Default is `True`.
558 | analyse (bool): `True` if an analysis on the fundamental
559 | mode should be performed. The analysis adds to the
560 | plot of the fundamental mode the power mode-field
561 | diameter (MFD) and marks it on the output, and it
562 | marks with a cross the maximum E-field value.
563 | Default is `True`.
564 |
565 | Returns:
566 | dict: A dictionary containing the effective indices
567 | and mode field profiles (if solved for).
568 | """
569 | modes_directory = "./modes_semi_vec/"
570 | if not os.path.isdir(modes_directory):
571 | os.mkdir(modes_directory)
572 | filename = modes_directory + filename
573 |
574 | for i, mode in enumerate(self._ms.modes):
575 | filename_mode = self._get_mode_filename(
576 | self._semi_vectorial_method, i, filename
577 | )
578 | self._write_mode_to_file(np.real(mode), filename_mode)
579 |
580 | if plot:
581 | if i == 0 and analyse:
582 | A, centre, sigma_2 = anal.fit_gaussian(
583 | self._structure.xc, self._structure.yc, np.abs(mode)
584 | )
585 | subtitle = (
586 | "E_{max} = %.3f, (x_{max}, y_{max}) = (%.3f, %.3f), MFD_{x} = %.3f, "
587 | "MFD_{y} = %.3f"
588 | ) % (A, centre[0], centre[1], sigma_2[0], sigma_2[1])
589 | self._plot_mode(
590 | self._semi_vectorial_method,
591 | i,
592 | filename_mode,
593 | self.n_effs[i],
594 | subtitle,
595 | sigma_2[0],
596 | sigma_2[1],
597 | centre[0],
598 | centre[1],
599 | wavelength=self._structure._wl,
600 | )
601 | else:
602 | self._plot_mode(
603 | self._semi_vectorial_method,
604 | i,
605 | filename_mode,
606 | self.n_effs[i],
607 | wavelength=self._structure._wl,
608 | )
609 |
610 | return self.modes
611 |
612 |
613 | class ModeSolverFullyVectorial(_ModeSolver):
614 | """
615 | A fully-vectorial mode solver object used to
616 | setup and run a mode solving simulation.
617 |
618 | Args:
619 | n_eigs (int): The number of eigen-values to solve for.
620 | tol (float): The precision of the eigen-value/eigen-vector
621 | solver. Default is 0.001.
622 | boundary (str): The boundary conditions to use.
623 | This is a string that identifies the type of boundary conditions applied.
624 | The following options are available: 'A' - Hx is antisymmetric, Hy is symmetric,
625 | 'S' - Hx is symmetric and, Hy is antisymmetric, and '0' - Hx and Hy are zero
626 | immediately outside of the boundary.
627 | The string identifies all four boundary conditions, in the order:
628 | North, south, east, west. For example, boundary='000A'. Default is '0000'.
629 | initial_mode_guess (list): An initial mode guess for the modesolver.
630 | initial_n_eff_guess (list): An initial effective index guess for the modesolver.
631 | """
632 |
633 | def __init__(
634 | self,
635 | n_eigs,
636 | tol=0.001,
637 | boundary="0000",
638 | initial_mode_guess=None,
639 | n_eff_guess=None,
640 | ):
641 | self.n_effs_te = None
642 | self.n_effs_tm = None
643 | _ModeSolver.__init__(
644 | self, n_eigs, tol, boundary, False, initial_mode_guess, n_eff_guess
645 | )
646 |
647 | @property
648 | def _modes_directory(self):
649 | modes_directory = "./modes_full_vec/"
650 | if not os.path.exists(modes_directory):
651 | os.mkdir(modes_directory)
652 | _modes_directory = modes_directory
653 | return _modes_directory
654 |
655 | def _solve(self, structure, wavelength):
656 | self._structure = structure
657 | self._ms = ms._ModeSolverVectorial(
658 | wavelength, structure, self._boundary
659 | )
660 | self._ms.solve(
661 | self._n_eigs,
662 | self._tol,
663 | self._n_eff_guess,
664 | initial_mode_guess=self._initial_mode_guess,
665 | )
666 | self.n_effs = self._ms.neff
667 |
668 | r = {"n_effs": self.n_effs}
669 | r["modes"] = self.modes = self._ms.modes
670 |
671 | self.overlaps, self.fraction_te, self.fraction_tm = self._get_overlaps(
672 | self.modes
673 | )
674 | self.mode_types = self._get_mode_types()
675 |
676 | self._initial_mode_guess = None
677 |
678 | self.n_effs_te, self.n_effs_tm = self._sort_neffs(self._ms.neff)
679 |
680 | return r
681 |
682 | def _get_mode_types(self):
683 | mode_types = []
684 | labels = {0: "qTE", 1: "qTM", 2: "qTE/qTM"}
685 | for overlap in self.overlaps:
686 | idx = np.argmax(overlap[0:3])
687 | mode_types.append((labels[idx], np.round(overlap[idx], 2)))
688 | return mode_types
689 |
690 | def _sort_neffs(self, n_effs):
691 | mode_types = self._get_mode_types()
692 |
693 | n_effs_te = []
694 | n_effs_tm = []
695 |
696 | for mt, n_eff in zip(mode_types, n_effs):
697 | if mt[0] == "qTE":
698 | n_effs_te.append(n_eff)
699 | elif mt[0] == "qTM":
700 | n_effs_tm.append(n_eff)
701 |
702 | return n_effs_te, n_effs_tm
703 |
704 | def _get_overlaps(self, fields):
705 | mode_areas = []
706 | fraction_te = []
707 | fraction_tm = []
708 | for mode in self._ms.modes:
709 | e_fields = (mode.fields["Ex"], mode.fields["Ey"], mode.fields["Ez"])
710 | h_fields = (mode.fields["Hx"], mode.fields["Hy"], mode.fields["Hz"])
711 |
712 | areas_e = [np.sum(np.abs(e) ** 2) for e in e_fields]
713 | areas_e /= np.sum(areas_e)
714 | areas_e *= 100
715 |
716 | areas_h = [np.sum(np.abs(h) ** 2) for h in h_fields]
717 | areas_h /= np.sum(areas_h)
718 | areas_h *= 100
719 |
720 | fraction_te.append(areas_e[0] / (areas_e[0] + areas_e[1]))
721 | fraction_tm.append(areas_e[1] / (areas_e[0] + areas_e[1]))
722 |
723 | areas = areas_e.tolist()
724 | areas.extend(areas_h)
725 | mode_areas.append(areas)
726 |
727 | return mode_areas, fraction_te, fraction_tm
728 |
729 | def write_modes_to_file(
730 | self,
731 | filename="mode.dat",
732 | plot=True,
733 | fields_to_write=("Ex", "Ey", "Ez", "Hx", "Hy", "Hz"),
734 | ):
735 | """
736 | Writes the mode fields to a file and optionally plots them.
737 |
738 | Args:
739 | filename (str): The nominal filename to use for the saved
740 | data. The suffix will be automatically be changed to
741 | identifiy each field and mode number. Default is
742 | 'mode.dat'
743 | plot (bool): `True` if plots should be generates,
744 | otherwise `False`. Default is `True`.
745 | fields_to_write (tuple): A tuple of strings where the
746 | strings can be 'Ex', 'Ey', 'Ez', 'Hx', 'Hy' and 'Hz'
747 | defining what part of the mode should be saved and
748 | plotted. By default, all six components are written
749 | and plotted.
750 |
751 | Returns:
752 | dict: A dictionary containing the effective indices
753 | and mode field profiles (if solved for).
754 | """
755 | modes_directory = self._modes_directory
756 |
757 | # Mode info file.
758 | with open(modes_directory + "mode_info", "w") as fs:
759 | fs.write("# Mode idx, Mode type, % in major direction, n_eff\n")
760 | for i, (n_eff, (mode_type, percentage)) in enumerate(
761 | zip(self.n_effs, self.mode_types)
762 | ):
763 | mode_idx = str(i)
764 | line = "%s,%s,%.2f,%.3f" % (
765 | mode_idx,
766 | mode_type,
767 | percentage,
768 | n_eff.real,
769 | )
770 | fs.write(line + "\n")
771 |
772 | # Mode field plots.
773 | for i, (mode, areas) in enumerate(zip(self._ms.modes, self.overlaps)):
774 | mode_directory = "%smode_%i/" % (modes_directory, i)
775 | if not os.path.isdir(mode_directory):
776 | os.mkdir(mode_directory)
777 | filename_full = mode_directory + filename
778 |
779 | for (field_name, field_profile), area in zip(
780 | mode.fields.items(), areas
781 | ):
782 | if field_name in fields_to_write:
783 | filename_mode = self._get_mode_filename(
784 | field_name, i, filename_full
785 | )
786 | self._write_mode_to_file(
787 | np.real(field_profile), filename_mode
788 | )
789 | if plot:
790 | self._plot_mode(
791 | field_name,
792 | i,
793 | filename_mode,
794 | self.n_effs[i],
795 | area=area,
796 | wavelength=self._structure._wl,
797 | )
798 |
799 | return self.modes
800 |
--------------------------------------------------------------------------------
/modesolverpy/n_effs.gpi:
--------------------------------------------------------------------------------
1 | set dataf sep ','
2 |
3 | set term pngcairo size 30cm,20cm
4 | set out filename_image
5 |
6 | unset key
7 | set grid
8 | set border lw 1.5
9 |
10 | set pal def (0 'green', 50 'red', 100 'blue')
11 | set cbrange [0:100]
12 | set cblabel 'TE Fraction [%]'
13 |
14 | #set yrange [1:*]
15 |
16 | set xlabel xlab
17 | set ylabel ylab
18 | set title titl font ',14'
19 |
20 | N = num_modes + 1
21 | cmd = '< paste ' . filename_data . ' ' . filename_frac_te
22 | plot for [col=2:N] cmd u 1:col w l lw 3 lc rgb 'gray80',\
23 | for [col=2:N] cmd u 1:col:(column(N+col-1)*100) w p pt 7 palette z
24 |
--------------------------------------------------------------------------------
/modesolverpy/structure.gpi:
--------------------------------------------------------------------------------
1 | set dataf sep ','
2 |
3 | set term pngcairo size 20cm,20cm enhanced
4 | set out filename_image
5 |
6 | set view map
7 | set palette defined (0 "gray90", 1 "royalblue", 2 "midnight-blue")
8 | stats filename_data matrix nooutput
9 | set cbrange [1:STATS_max]
10 | set colorbox
11 |
12 | unset key
13 | set tics scale 0
14 | unset border
15 |
16 | set title title
17 | set xlabel 'x'
18 | set ylabel 'y'
19 |
20 | set xrange [0:x_max]
21 | set yrange [0:y_max]
22 |
23 | splot filename_data matrix u ((x_max-x_min)*$1/x_pts+x_min):\
24 | ((y_max-y_min)*$2/y_pts+y_min):3 w pm3d
25 |
--------------------------------------------------------------------------------
/modesolverpy/structure.py:
--------------------------------------------------------------------------------
1 | import copy
2 | from . import structure_base as sb
3 | from .structure_base import use_gnuplot
4 | from .structure_base import use_matplotlib
5 | import opticalmaterialspy as mat
6 | import numpy as np
7 |
8 | class RidgeWaveguide(sb.Slabs):
9 | '''
10 | A general ridge waveguide structure.
11 |
12 | Args:
13 | wavelength (float): Wavelength the structure should
14 | operate at.
15 | x_step (float): The grid step in x that the structure
16 | is created on.
17 | y_step (float): The grid step in y that the structure
18 | is created on.
19 | wg_height (float): The height of the ridge.
20 | wg_width (float): The width of the ridge.
21 | sub_height (float): The thickness of the substrate.
22 | sub_width (float): The width of the substrate.
23 | clad_height (float): The thickness of the cladding.
24 | n_sub (float, function): Refractive index of the
25 | substrate. Either a constant (`float`), or
26 | a function that accepts one parameters, the
27 | wavelength, and returns a float of the refractive
28 | index. This is useful when doing wavelength
29 | sweeps and solving for the group velocity. The
30 | function provided could be a Sellmeier equation.
31 | n_wg (float, function): Refractive index of the
32 | waveguide. Either a constant (`float`), or
33 | a function that accepts one parameters, the
34 | wavelength, and returns a float of the refractive
35 | index. This is useful when doing wavelength
36 | sweeps and solving for the group velocity. The
37 | function provided could be a Sellmeier equation.
38 | angle (float): The angle of the sidewall [degrees] of
39 | the waveguide. Default is 0 degrees (vertical
40 | sidewalls).
41 | n_clad (float, function): Refractive index of the
42 | cladding. Either a constant (`float`), or
43 | a function that accepts one parameters, the
44 | wavelength, and returns a float of the refractive
45 | index. This is useful when doing wavelength
46 | sweeps and solving for the group velocity. The
47 | function provided could be a Sellmeier equation.
48 | Default is air.
49 | film_thickness (float, str): The thickness of the
50 | film the waveguide is on. If the waveguide
51 | is a true ridge (fully etched), then the film thickness
52 | can be set to 'wg_height', otherwise the waveguide
53 | is a rib waveguide, and a float should be given
54 | specifying the thickness of the film.
55 |
56 | '''
57 | def __init__(self, wavelength, x_step, y_step, wg_height, wg_width, sub_height, sub_width,
58 | clad_height, n_sub, n_wg, angle=0, n_clad=mat.Air().n(),
59 | film_thickness='wg_height'):
60 | sb.Slabs.__init__(self, wavelength, y_step, x_step, sub_width)
61 |
62 | self.n_sub = n_sub
63 | self.n_clad = n_clad
64 | self.n_wg = n_wg
65 |
66 | self.add_slab(sub_height, n_sub)
67 | if film_thickness != 'wg_height' and film_thickness != wg_height:
68 | assert film_thickness > 0., 'Film must have some thickness to it.'
69 | assert wg_height <= film_thickness, 'Waveguide can\'t be thicker than the film.'
70 | self.add_slab(film_thickness-wg_height, n_wg)
71 | k = self.add_slab(wg_height, n_clad)
72 |
73 | self.slabs[k].add_material(self.x_ctr-wg_width/2., self.x_ctr+wg_width/2.,
74 | n_wg, angle)
75 |
76 | self.add_slab(clad_height, n_clad)
77 |
78 | class WgArray(sb.Slabs):
79 | def __init__(self, wavelength, x_step, y_step, wg_height, wg_widths, wg_gaps, sub_height,
80 | sub_width, clad_height, n_sub, n_wg, angle=0, n_clad=mat.Air().n()):
81 | sb.Slabs.__init__(self, wavelength, y_step, x_step, sub_width)
82 |
83 | try:
84 | iter(wg_gaps)
85 | except TypeError:
86 | wg_gaps = [wg_gaps]
87 |
88 | try:
89 | assert len(wg_widths) == len(wg_gaps)+1
90 | except TypeError:
91 | wg_widths = [wg_widths for _ in wg_gaps]
92 |
93 | wg_gaps_pad = copy.copy(wg_gaps)
94 | wg_gaps_pad.append(0.)
95 |
96 | self.add_slab(sub_height, n_sub)
97 |
98 | k = self.add_slab(wg_height, n_clad)
99 | air_width_l_r = 0.5*(sub_width - np.sum(wg_widths) - np.sum(wg_gaps))
100 | position = air_width_l_r
101 |
102 | for wg_width, wg_gap in zip(wg_widths, wg_gaps_pad):
103 | self.slabs[k].add_material(position, position + wg_width, n_wg, angle)
104 |
105 | position += wg_width + wg_gap
106 |
107 | self.add_slab(clad_height, n_clad)
108 |
--------------------------------------------------------------------------------
/modesolverpy/structure_base.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from scipy import interpolate
3 | import os
4 | import sys
5 | import subprocess
6 | import abc
7 | from six import with_metaclass
8 |
9 | try:
10 | devnull = open(os.devnull, 'w')
11 | subprocess.call(['gnuplot', '--version'], stdout=devnull, stderr=devnull)
12 | import gnuplotpy as gp
13 | MPL = False
14 | except:
15 | import matplotlib.pylab as plt
16 | MPL = True
17 |
18 | def use_gnuplot():
19 | """
20 | Use gnuplot as the plotting tool for any structure related outputs.
21 | """
22 | global gp
23 | import gnuplotpy as gp
24 | global MPL
25 | MPL = False
26 |
27 | def use_matplotlib():
28 | """
29 | Use matplotlib as the plotting tool for any structure related outputs.
30 | """
31 | global plt
32 | import matplotlib.pylab as plt
33 | global MPL
34 | MPL = True
35 |
36 | class _AbstractStructure(with_metaclass(abc.ABCMeta)):
37 | @abc.abstractproperty
38 | def n(self):
39 | '''
40 | np.array: A grid of refractive indices representing
41 | the refractive index profile of the structure.
42 | '''
43 | pass
44 |
45 | @property
46 | def x_pts(self):
47 | '''
48 | int: The number of grid points in x.
49 | '''
50 | return int((self.x_max - self.x_min) / self.x_step + 1)
51 |
52 | @property
53 | def y_pts(self):
54 | '''
55 | int: The number of grid points in y.
56 | '''
57 | return int((self.y_max - self.y_min) / self.y_step)
58 |
59 | @property
60 | def x_ctr(self):
61 | '''
62 | float: The centre distance in x.
63 | '''
64 | return 0.5*(self.x_max + self.x_min)
65 |
66 | @property
67 | def y_ctr(self):
68 | '''
69 | float: The centre distance in y
70 | '''
71 | return 0.5*(self.y_max + self.y_min)
72 |
73 | @property
74 | def xc(self):
75 | '''
76 | np.array: The centre points of the x points.
77 | '''
78 | return 0.5*(self.x[1:] + self.x[:-1])
79 |
80 | @property
81 | def yc(self):
82 | '''
83 | np.array: The centre points of the y points.
84 | '''
85 | return 0.5*(self.y[1:] + self.y[:-1])
86 |
87 | @property
88 | def xc_pts(self):
89 | '''
90 | int: The number of points in `xc`.
91 | '''
92 | return self.x_pts - 1
93 |
94 | @property
95 | def yc_pts(self):
96 | '''
97 | int: The number of points in `yc`.
98 | '''
99 | return self.y_pts - 1
100 |
101 | @property
102 | def xc_min(self):
103 | '''
104 | float: The minimum value of `xc`.
105 | '''
106 | return self.xc[0]
107 |
108 | @property
109 | def xc_max(self):
110 | '''
111 | float: The maximum value of `xc`.
112 | '''
113 | return self.xc[-1]
114 |
115 | @property
116 | def yc_min(self):
117 | '''
118 | float: The minimum value of `yc`.
119 | '''
120 | return self.yc[0]
121 |
122 | @property
123 | def yc_max(self):
124 | '''
125 | float: The maximum value of `yc`.
126 | '''
127 | return self.yc[-1]
128 |
129 | @property
130 | def x(self):
131 | '''
132 | np.array: The grid points in x.
133 | '''
134 | if None not in (self.x_min, self.x_max, self.x_step) and \
135 | self.x_min != self.x_max:
136 | x = np.arange(self.x_min, self.x_max+self.x_step-self.y_step*0.1, self.x_step)
137 | else:
138 | x = np.array([])
139 | return x
140 |
141 | @property
142 | def y(self):
143 | '''
144 | np.array: The grid points in y.
145 | '''
146 | if None not in (self.y_min, self.y_max, self.y_step) and \
147 | self.y_min != self.y_max:
148 | y = np.arange(self.y_min, self.y_max-self.y_step*0.1, self.y_step)
149 | else:
150 | y = np.array([])
151 | return y
152 |
153 | @property
154 | def eps(self):
155 | '''
156 | np.array: A grid of permittivies representing
157 | the permittivity profile of the structure.
158 | '''
159 | return self.n**2
160 |
161 | @property
162 | def eps_func(self):
163 | '''
164 | function: a function that when passed a `x` and `y` values,
165 | returns the permittivity profile of the structure,
166 | interpolating if necessary.
167 | '''
168 | interp_real = interpolate.interp2d(self.x, self.y, self.eps.real)
169 | interp_imag = interpolate.interp2d(self.x, self.y, self.eps.imag)
170 | interp = lambda x, y: interp_real(x, y) + 1.j*interp_imag(x, y)
171 | return interp
172 |
173 | @property
174 | def n_func(self):
175 | '''
176 | function: a function that when passed a `x` and `y` values,
177 | returns the refractive index profile of the structure,
178 | interpolating if necessary.
179 | '''
180 | return interpolate.interp2d(self.x, self.y, self.n)
181 |
182 | def _add_triangular_sides(self, xy_mask, angle, y_top_right, y_bot_left,
183 | x_top_right, x_bot_left, n_material):
184 | angle = np.radians(angle)
185 | trap_len = (y_top_right - y_bot_left) / np.tan(angle)
186 | num_x_iterations = trap_len / self.x_step
187 | y_per_iteration = num_x_iterations / self.y_pts
188 |
189 | lhs_x_start_index = int(x_bot_left/ self.x_step + 0.5)
190 | rhs_x_stop_index = int(x_top_right/ self.x_step + 1 + 0.5)
191 |
192 | running_removal_float = y_per_iteration
193 | for i, _ in enumerate(xy_mask):
194 | if running_removal_float >= 1:
195 | removal_int = int(round(running_removal_float))
196 | lhs_x_start_index -= removal_int
197 | rhs_x_stop_index += removal_int
198 | running_removal_float -= removal_int
199 | running_removal_float += y_per_iteration
200 |
201 | xy_mask[i][:lhs_x_start_index] = False
202 | xy_mask[i][lhs_x_start_index:rhs_x_stop_index] = True
203 |
204 | self.n[xy_mask] = n_material
205 | return self.n
206 |
207 | def _add_material(self, x_bot_left, y_bot_left, x_top_right, y_top_right,
208 | n_material, angle=0):
209 | '''
210 | A low-level function that allows writing a rectangle refractive
211 | index profile to a `Structure`.
212 |
213 | Args:
214 | x_bot_left (float): The bottom-left x-coordinate of the
215 | rectangle.
216 | y_bot_left (float): The bottom-left y-coordinate of the
217 | rectangle.
218 | x_top_right (float): The top-right x-coordinate of the
219 | rectangle.
220 | y_top_right (float): The top-right y-coordinate of the
221 | rectangle.
222 | n_material (float): The refractive index of the points
223 | encompassed by the defined rectangle.
224 | angle (float): The angle in degrees of the sidewalls
225 | of the defined rectangle. Default is 0. This
226 | is useful for creating a ridge with angled
227 | sidewalls.
228 | '''
229 | x_mask = np.logical_and(x_bot_left<=self.x, self.x<=x_top_right)
230 | y_mask = np.logical_and(y_bot_left<=self.y, self.y<=y_top_right)
231 |
232 | xy_mask = np.kron(y_mask, x_mask).reshape((y_mask.size, x_mask.size))
233 | self.n[xy_mask] = n_material
234 |
235 | if angle:
236 | self._add_triangular_sides(xy_mask, angle, y_top_right, y_bot_left,
237 | x_top_right, x_bot_left, n_material)
238 |
239 | return self.n
240 |
241 | def write_to_file(self, filename='material_index.dat', plot=True):
242 | '''
243 | Write the refractive index profile to file.
244 |
245 | Args:
246 | filename (str): The nominal filename the refractive
247 | index data should be saved to.
248 | plot (bool): `True` if plots should be generates,
249 | otherwise `False`. Default is `True`.
250 | '''
251 | path = os.path.dirname(sys.modules[__name__].__file__) + '/'
252 |
253 | with open(filename, 'w') as fs:
254 | for n_row in np.abs(self.n[::-1]):
255 | n_str = ','.join([str(v) for v in n_row])
256 | fs.write(n_str+'\n')
257 |
258 | if plot:
259 | filename_image_prefix, _ = os.path.splitext(filename)
260 | filename_image = filename_image_prefix + '.png'
261 | args = {
262 | 'title': 'Refractive Index Profile',
263 | 'x_pts': self.x_pts,
264 | 'y_pts': self.y_pts,
265 | 'x_min': self.x_min,
266 | 'x_max': self.x_max,
267 | 'y_min': self.y_min,
268 | 'y_max': self.y_max,
269 | 'filename_data': filename,
270 | 'filename_image': filename_image
271 | }
272 |
273 | if MPL:
274 | heatmap = np.loadtxt(args['filename_data'], delimiter=',')
275 | plt.clf()
276 | plt.title(args['title'])
277 | plt.xlabel('$x$')
278 | plt.ylabel('$y$')
279 | plt.imshow(np.flipud(heatmap),
280 | extent=(args['x_min'], args['x_max'], args['y_min'], args['y_max']),
281 | aspect="auto")
282 | plt.colorbar()
283 | plt.savefig(filename_image)
284 | else:
285 | gp.gnuplot(path+'structure.gpi', args)
286 |
287 | def __str__(self):
288 | return self.n.__str__()
289 |
290 | class Structure(_AbstractStructure):
291 | def __init__(self, x_step, y_step, x_max, y_max, x_min=0., y_min=0.,
292 | n_background=1.):
293 | self.x_min = x_min
294 | self.x_max = x_max
295 | self.y_min = y_min
296 | self.y_max = y_max
297 | self.x_step = x_step
298 | self.y_step = y_step
299 | self.n_background = n_background
300 | self._n = np.ones((self.y.size,self.x.size), 'complex_') * n_background
301 |
302 | @property
303 | def n(self):
304 | return self._n
305 |
306 | class Slabs(_AbstractStructure):
307 | '''
308 | Class to implement device refractive index
309 | profile cross-section designs.
310 |
311 | :class:`Slabs` is a collection of :class:`Slab` objects. Each
312 | slab has a fixed height (usually less than the
313 | maximum height of the desired simulation window),
314 | and is as wide as the simulation window.
315 |
316 | :class:`Slabs` objects can be index using `[name]` to return
317 | the various :class:`Slab` objects. The bottom slab is
318 | returned first and so on up to the top slab.
319 |
320 | .. image:: ../images/slabs.svg
321 | :width: 200%
322 |
323 | Args:
324 | wavelength (float): The wavelength the structure
325 | operates at.
326 | y_step (float): The step in y.
327 | x_step (float): The step in x.
328 | x_max (float): The maximum x-value.
329 | x_min (float): The minimum x-value. Default is 0.
330 |
331 | Attributes:
332 | slabs (dict): The key is the name of the slab,
333 | and the value is the :class:`Slab` object.
334 | slab_count (int): The number of :class:`Slab` objects
335 | added so far.
336 | '''
337 | def __init__(self, wavelength, y_step, x_step, x_max, x_min=0.):
338 | _AbstractStructure.__init__(self)
339 |
340 | self._wl = wavelength
341 | self.x_min = x_min
342 | self.x_max = x_max
343 | self.x_step = x_step
344 | self.y_step = y_step
345 | self.y_min = 0
346 |
347 | self.slabs = {}
348 | self.slab_count = 0
349 | self._next_start = 0.
350 |
351 | def add_slab(self, height, n_background=1., position='top'):
352 | '''
353 | Creates and adds a :class:`Slab` object.
354 |
355 | Args:
356 | height (float): Height of the slab.
357 | n_background (float): The nominal refractive
358 | index of the slab. Default is 1 (air).
359 |
360 | Returns:
361 | str: The name of the slab.
362 | '''
363 | assert position in ('top', 'bottom')
364 |
365 | name = str(self.slab_count)
366 |
367 | if not callable(n_background):
368 | n_back = lambda wl: n_background
369 | else:
370 | n_back = n_background
371 |
372 | height_discretised = self.y_step*((height // self.y_step) + 1)
373 |
374 | y_min = self._next_start
375 | y_max = y_min + height_discretised
376 | self.slabs[name] = Slab(name, self.x_step, self.y_step, self.x_max,
377 | y_max, self.x_min, y_min, n_back, self._wl)
378 |
379 | self.y_max = y_max
380 | self._next_start = y_min + height_discretised
381 | self.slab_count += 1
382 |
383 | if position == 'bottom':
384 | slabs = {}
385 | for k in self.slabs.keys():
386 | slabs[str(int(k)+1)] = self.slabs[k]
387 | slabs['0'] = slabs.pop(str(self.slab_count))
388 | self.slabs = slabs
389 |
390 | return name
391 |
392 | def change_wavelength(self, wavelength):
393 | '''
394 | Changes the wavelength of the structure.
395 |
396 | This will affect the mode solver and potentially
397 | the refractive indices used (provided functions
398 | were provided as refractive indices).
399 |
400 | Args:
401 | wavelength (float): The new wavelength.
402 | '''
403 | for name, slab in self.slabs.items():
404 | const_args = slab._const_args
405 | mat_args = slab._mat_params
406 |
407 | const_args[8] = wavelength
408 |
409 | s = Slab(*const_args)
410 | for mat_arg in mat_args:
411 | s.add_material(*mat_arg)
412 |
413 | self.slabs[name] = s
414 |
415 | self._wl = wavelength
416 |
417 | @property
418 | def n(self):
419 | '''
420 | np.array: The refractive index profile matrix
421 | of the current slab.
422 | '''
423 | try:
424 | n_mat = self.slabs['0'].n
425 | for s in range(1, self.slab_count):
426 | n_mat = np.vstack((self.slabs[str(s)].n, n_mat))
427 | except KeyError:
428 | n_mat = None
429 | return n_mat
430 |
431 | def __getitem__(self, slab_name):
432 | return self.slabs[str(slab_name)]
433 |
434 | class Slab(Structure):
435 | '''
436 | A :class:`Slab` represents a horizontal slice of
437 | the refractive index profile.
438 |
439 | A :class:`Slabs` object composes many :class:`Slab` objects.
440 | The more :class:`Slab` are added, the more horizontal
441 | slices are added. A :class:`Slab` has a chosen fixed
442 | height, and a background (nominal) refractive
443 | index. A slab can then be customised to include
444 | a desired design.
445 |
446 | Args:
447 | name (str): The name of the slab.
448 | x_step (float): The step in x.
449 | y_step (float): The step in y.
450 | x_max (float): The maximum x-value.
451 | y_max (float): The maximum y-value.
452 | x_min (float): The minimum x-value.
453 | y_min (float): The minimum x-value.
454 | n_background (float): The nominal refractive
455 | index.
456 | wavelength (float): The wavelength the structure
457 | operates at.
458 |
459 | Attributes:
460 | name (str): The name of the :class:`Slab` object.
461 | position (int): A unique identifier for the
462 | :class:`Slab` object.
463 | '''
464 | position = 0
465 |
466 | def __init__(self, name, x_step, y_step, x_max, y_max, x_min, y_min,
467 | n_background, wavelength):
468 | self._wl = wavelength
469 | self.name = name
470 | self.position = Slab.position
471 | Slab.position += 1
472 |
473 | Structure.__init__(self, x_step, y_step, x_max, y_max, x_min, y_min,
474 | n_background(self._wl))
475 |
476 | self._const_args = [name, x_step, y_step, x_max, y_max, x_min, y_min, n_background, wavelength]
477 | self._mat_params = []
478 |
479 | def add_material(self, x_min, x_max, n, angle=0):
480 | '''
481 | Add a refractive index between two x-points.
482 |
483 | Args:
484 | x_min (float): The start x-point.
485 | x_max (float): The stop x-point.
486 | n (float, function): Refractive index between
487 | `x_min` and `x_max`. Either a constant (`float`), or
488 | a function that accepts one parameters, the
489 | wavelength, and returns a float of the refractive
490 | index. This is useful when doing wavelength
491 | sweeps and solving for the group velocity. The
492 | function provided could be a Sellmeier equation.
493 | angle (float): Angle in degrees of the slope of the
494 | sidewalls at `x_min` and `x_max`. This is useful
495 | for defining a ridge with angled sidewalls.
496 | '''
497 | self._mat_params.append([x_min, x_max, n, angle])
498 |
499 | if not callable(n):
500 | n_mat = lambda wl: n
501 | else:
502 | n_mat = n
503 |
504 | Structure._add_material(self, x_min, self.y_min, x_max, self.y_max, n_mat(self._wl), angle)
505 | return self.n
506 |
507 | class StructureAni():
508 | r"""
509 | Anisottropic structure object.
510 |
511 | This is used with the fully-vectorial simulation when
512 | an anisotropic material is being used.
513 |
514 | The form of the refractive index is
515 |
516 | .. math::
517 |
518 | n = \begin{bmatrix}
519 | n_{xx} & n_{xy} & 0 \\
520 | n_{yx} & n_{yy} & 0 \\
521 | 0 & 0 & n_{zz}
522 | \end{bmatrix}.
523 |
524 | Args:
525 | structure_xx (Structure): The structure with refractive
526 | index, :math:`n_{xx}`.
527 | structure_yy (Structure): The structure with refractive
528 | index, :math:`n_{yy}`. Presumably the same structure
529 | as `structure_xx`, but with different refractive index
530 | parameters.
531 | structure_zz (Structure): The structure with refractive
532 | index, :math:`n_{zz}`. Presumably the same structure
533 | as `structure_xx`, but with different refractive index
534 | parameters.
535 | structure_xy (None, Structure): The structure with refractive
536 | index, :math:`n_{yx}`. Presumably the same structure
537 | as `structure_xx`, but with different refractive index
538 | parameters. Default is `None`.
539 | structure_yx (None, Structure): The structure with refractive
540 | index, :math:`n_{yx}`. Presumably the same structure
541 | as `structure_xx`, but with different refractive index
542 | parameters. Default is `None`.
543 | """
544 | def __init__(self, structure_xx, structure_yy, structure_zz,
545 | structure_xy=None, structure_yx=None):
546 | self.xx = structure_xx
547 | self.yy = structure_yy
548 | self.zz = structure_zz
549 |
550 | if not structure_xy or not structure_yx:
551 | struct_dummy = Structure(self.xx.x_step, self.xx.y_step,
552 | self.xx.x_max, self.xx.y_max,
553 | self.xx.x_min, self.xx.y_min,
554 | n_background=0.)
555 | struct_dummy._wl = self.xx._wl
556 |
557 | if structure_xy:
558 | self.xy = structure_xy
559 | else:
560 | self.xy = struct_dummy
561 |
562 | if structure_yx:
563 | self.yx = structure_yx
564 | else:
565 | self.yx = struct_dummy
566 |
567 | assert self.xx._wl == self.xy._wl == self.yx._wl == \
568 | self.yy._wl == self.zz._wl
569 |
570 | self._wl = structure_xx._wl
571 |
572 | self.axes = (self.xx, self.xy, self.yx, self.yy, self.zz)
573 | self.axes_str = ('xx', 'xy', 'yx', 'yy', 'zz')
574 |
575 | @property
576 | def n(self):
577 | return [a.n for a in self.axes]
578 |
579 | @property
580 | def x_step(self):
581 | return self.xx.x_step
582 |
583 | @property
584 | def y_step(self):
585 | return self.xx.y_step
586 |
587 | @property
588 | def x_pts(self):
589 | return int((self.xx.x_max - self.xx.x_min) / self.xx.x_step + 1)
590 |
591 | @property
592 | def y_pts(self):
593 | return int((self.xx.y_max - self.xx.y_min) / self.xx.y_step)
594 |
595 | @property
596 | def x_ctr(self):
597 | return 0.5*(self.xx.x_max + self.xx.x_min)
598 |
599 | @property
600 | def y_ctr(self):
601 | return 0.5*(self.xx.y_max + self.xx.y_min)
602 |
603 | @property
604 | def xc(self):
605 | return 0.5*(self.xx.x[1:] + self.xx.x[:-1])
606 |
607 | @property
608 | def yc(self):
609 | return 0.5*(self.xx.y[1:] + self.xx.y[:-1])
610 |
611 | @property
612 | def xc_pts(self):
613 | return self.xx.x_pts - 1
614 |
615 | @property
616 | def yc_pts(self):
617 | return self.xx.y_pts - 1
618 |
619 | @property
620 | def xc_min(self):
621 | return self.xx.xc[0]
622 |
623 | @property
624 | def xc_max(self):
625 | return self.xx.xc[-1]
626 |
627 | @property
628 | def yc_min(self):
629 | return self.xx.yc[0]
630 |
631 | @property
632 | def yc_max(self):
633 | return self.xx.yc[-1]
634 |
635 | @property
636 | def x(self):
637 | if None not in (self.xx.x_min, self.xx.x_max, self.xx.x_step) and \
638 | self.xx.x_min != self.xx.x_max:
639 | x = np.arange(self.xx.x_min, self.xx.x_max+self.xx.x_step-self.xx.y_step*0.1, self.xx.x_step)
640 | else:
641 | x = np.array([])
642 | return x
643 |
644 | @property
645 | def y(self):
646 | if None not in (self.xx.y_min, self.xx.y_max, self.xx.y_step) and \
647 | self.xx.y_min != self.xx.y_max:
648 | y = np.arange(self.xx.y_min, self.xx.y_max-self.xx.y_step*0.1, self.xx.y_step)
649 | else:
650 | y = np.array([])
651 | return y
652 |
653 | @property
654 | def eps(self):
655 | eps_ani = [a.n**2 for a in self.axes]
656 | return eps_ani
657 |
658 | @property
659 | def eps_func(self):
660 | return lambda x,y: tuple(axis.eps_func(x,y) for axis in self.axes)
661 |
662 | @property
663 | def n_func(self):
664 | return lambda x,y: tuple(axis.n_func(x,y) for axis in self.axes)
665 |
666 | def write_to_file(self, filename='material_index.dat', plot=True):
667 | '''
668 | Write the refractive index profile to file.
669 |
670 | Args:
671 | filename (str): The nominal filename the refractive
672 | index data should be saved to.
673 | plot (bool): `True` if plots should be generates,
674 | otherwise `False`. Default is `True`.
675 | '''
676 | path = os.path.dirname(sys.modules[__name__].__file__) + '/'
677 |
678 | dir_plot = 'material_index/'
679 | if not os.path.exists(dir_plot):
680 | os.makedirs(dir_plot)
681 |
682 | for axis, name in zip(self.axes, self.axes_str):
683 | root, ext = os.path.splitext(filename)
684 | fn = dir_plot + root + '_'+ name + ext
685 | with open(fn, 'w') as fs:
686 | for n_row in np.abs(axis.n[::-1]):
687 | n_str = ','.join([str(v) for v in n_row])
688 | fs.write(n_str+'\n')
689 |
690 | if plot:
691 | filename_image_prefix, _ = os.path.splitext(fn)
692 | filename_image = filename_image_prefix + '.png'
693 | args = {
694 | 'title': 'Refractive Index Profile: %s' % name,
695 | 'x_pts': self.xx.x_pts,
696 | 'y_pts': self.xx.y_pts,
697 | 'x_min': self.xx.x_min,
698 | 'x_max': self.xx.x_max,
699 | 'y_min': self.xx.y_min,
700 | 'y_max': self.xx.y_max,
701 | 'filename_data': fn,
702 | 'filename_image': filename_image
703 | }
704 | if MPL:
705 | heatmap = np.loadtxt(args['filename_data'], delimiter=',')
706 | plt.clf()
707 | plt.title(args['title'])
708 | plt.xlabel('$x$')
709 | plt.ylabel('$y$')
710 | plt.imshow(np.flipud(heatmap),
711 | extent=(args['x_min'], args['x_max'], args['y_min'], args['y_max']),
712 | aspect="auto")
713 | plt.colorbar()
714 | plt.savefig(filename_image)
715 | else:
716 | gp.gnuplot(path+'structure.gpi', args, silent=False)
717 |
718 | def change_wavelength(self, wavelength):
719 | '''
720 | Changes the wavelength of the structure.
721 |
722 | This will affect the mode solver and potentially
723 | the refractive indices used (provided functions
724 | were provided as refractive indices).
725 |
726 | Args:
727 | wavelength (float): The new wavelength.
728 | '''
729 | for axis in self.axes:
730 | if issubclass(type(axis), Slabs):
731 | axis.change_wavelength(wavelength)
732 | self.xx, self.xy, self.yx, self.yy, self.zz = self.axes
733 | self._wl = wavelength
734 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(name='modesolverpy',
4 | version='0.4.4',
5 | description='Photonic mode solver.',
6 | url='https://github.com/jtambasco/modesolverpy',
7 | author='Jean-Luc Tambasco',
8 | author_email='an.obscurity@gmail.com',
9 | license='MIT',
10 | install_requires=[
11 | 'tqdm',
12 | 'scipy',
13 | 'numpy',
14 | 'gnuplotpy',
15 | 'opticalmaterialspy',
16 | 'matplotlib',
17 | 'future'
18 | ],
19 | packages=['modesolverpy'],
20 | include_package_data=True,
21 | zip_safe=False)
22 |
--------------------------------------------------------------------------------