├── tests ├── __init__.py ├── context.py ├── test_visu.py ├── test_Green.py ├── test_map.py ├── test_dispersion.py ├── test_fields.py ├── test_abeles.py ├── test_grad_abeles_Tmat.py ├── test_opti.py ├── speed_test_coefficients.py ├── test_vectorize.py ├── test_anisotropic_with_Pyllama.py ├── test_units.py ├── test_non_local.py ├── test_abs_coefficients.py └── test_diff_coefficients.py ├── MANIFEST.in ├── spr.png ├── field.png ├── fresnel.png ├── refraction.png ├── PyMoosh_Logo.pdf ├── pymoosh_logo.png ├── setup.py ├── PyMoosh_function_list.pdf ├── Fig └── Non_local_optimized.png ├── .gitignore ├── new_examples ├── README.txt ├── anisotropic_structures.py ├── optimising.py ├── basic_functions.py ├── non_locality.py └── matrix_formalism.py ├── LICENSE.txt ├── pyproject.toml ├── PyMoosh ├── __init__.py ├── models.py ├── green.py ├── data │ └── material_data.json ├── optim_algo.py ├── modes.py └── grads.py ├── notebooks └── In-depth_examples │ ├── h70_2.dat │ ├── nlplot.data │ └── How_materials_works.ipynb └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include PyMoosh/data * 2 | -------------------------------------------------------------------------------- /spr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/spr.png -------------------------------------------------------------------------------- /field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/field.png -------------------------------------------------------------------------------- /fresnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/fresnel.png -------------------------------------------------------------------------------- /refraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/refraction.png -------------------------------------------------------------------------------- /PyMoosh_Logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/PyMoosh_Logo.pdf -------------------------------------------------------------------------------- /pymoosh_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/pymoosh_logo.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() # backwards compatibility 4 | -------------------------------------------------------------------------------- /PyMoosh_function_list.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/PyMoosh_function_list.pdf -------------------------------------------------------------------------------- /Fig/Non_local_optimized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnMoreau/PyMoosh/HEAD/Fig/Non_local_optimized.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environments 2 | .venv 3 | .vscode 4 | .git 5 | exclude 6 | *__pycache__ 7 | notebooks/.ipynb_checkpoints 8 | *.egg-info 9 | dist 10 | build 11 | code/* 12 | code 13 | mine 14 | mine/* 15 | mine_PyMoosh/* 16 | Fig/* 17 | *.svg 18 | TODO.txt 19 | tests/* 20 | -------------------------------------------------------------------------------- /tests/context.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) 5 | 6 | import PyMoosh as PM 7 | from PyMoosh import alt_methods 8 | from PyMoosh import modes 9 | from PyMoosh import green 10 | from PyMoosh import non_local 11 | from PyMoosh import models 12 | -------------------------------------------------------------------------------- /tests/test_visu.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | import matplotlib.pyplot as plt 4 | 5 | materials = [] 6 | 7 | wavelength = 600 8 | kr = PM.Structure( 9 | [1.5, 1.7**2, "Gold"], [0, 1, 0, 1, 0, 2], [0, 40, 20, 30, 30, 0], unit="um" 10 | ) 11 | polarization = 1 12 | 13 | 14 | ax = kr.plot_stack(mode="return") 15 | ax.set_xlabel("pop") 16 | plt.show() 17 | -------------------------------------------------------------------------------- /new_examples/README.txt: -------------------------------------------------------------------------------- 1 | The scripts in this folder are designed as simple use cases that can be easily copy/pasted and modified. 2 | They are very similar to the Jupyter Notebooks provided with the code, so have a look there if you need more details. 3 | 4 | RUNNING CODE: 5 | 6 | (local installation) 7 | If not using the latest PyPi version (typically, because you want access to functions that are on github but not yet PyPi): 8 | Just copy the PyMoosh folder in your working directory, and run the scripts there! -------------------------------------------------------------------------------- /tests/test_Green.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | from context import green 4 | import matplotlib.pyplot as plt 5 | 6 | green_struct = PM.Structure([1, 4 + 0.1j], [0, 1, 1, 0], [2000, 500, 500, 2000]) 7 | wavelength = 800 8 | window = PM.Window(30 * wavelength, 0.5, 10.0, 10.0) 9 | 10 | 11 | source_interface = 2 12 | En = green.green(green_struct, window, wavelength, source_interface) 13 | # plt.imshow(abs(np.real(En)),cmap='jet',aspect='auto') 14 | # plt.colorbar() 15 | 16 | plt.imsave("champ.png", abs(np.real(En)), cmap="jet") 17 | -------------------------------------------------------------------------------- /tests/test_map.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | from context import modes 4 | import matplotlib.pyplot as plt 5 | import itertools 6 | 7 | 8 | # materials = [1.513**2, 1.455**2, 2.079**2, (1.9+4.8j)**2, 1.0003**2] 9 | materials = [1.0, 2.0 + 0.1j] 10 | stack = [0, 1, 0] 11 | thickness = [0, 500, 0] 12 | formap = PM.Structure(materials, stack, thickness) 13 | 14 | X, Y, T = modes.complex_map( 15 | formap, 600.0, 0.0, [1.0, np.sqrt(2)], [-0.1, 0.1], 100, 100 16 | ) 17 | 18 | plt.imshow(np.log(np.abs(T)), cmap="jet", extent=[X.min(), X.max(), Y.min(), Y.max()]) 19 | # plt.imshow(np.angle(T), cmap='jet', extent=[X.min(), X.max(), Y.min(), Y.max()]) 20 | 21 | # plt.colorbar() 22 | plt.xlabel("Re") 23 | plt.ylabel("Im") 24 | plt.show() 25 | -------------------------------------------------------------------------------- /tests/test_dispersion.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | from context import modes 4 | import matplotlib.pyplot as plt 5 | import itertools 6 | 7 | mat = 1.5 8 | # materials = [1.513**2, 1.455**2, 2.079**2, (1.9+4.8j)**2, 1.0003**2] 9 | materials = [1.0, mat**2, 2] 10 | 11 | 12 | print("Normal incidence:") 13 | incidence = 0 * np.pi / 180 14 | nb_prob = 0 15 | 16 | structure = np.array([2000, 500]) 17 | epaisseurs = np.concatenate(([0], structure, [0])) 18 | stack = [0, 1, 2, 0] 19 | 20 | wavs = np.linspace(400, 600, 100) 21 | neff_min, neff_max = 1.0, 2.0 22 | 23 | 24 | chose = PM.Structure(materials, stack, epaisseurs) 25 | 26 | indices, follow_modes = modes.follow_guided_modes( 27 | chose, wavs, 0, neff_min, neff_max, format="n" 28 | ) 29 | -------------------------------------------------------------------------------- /tests/test_fields.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | import matplotlib.pyplot as plt 4 | 5 | materials = [] 6 | 7 | wavelength = 600 8 | kr = PM.Structure([1.0, "Gold", "Water", 1.46**2, 1.7**2], [4, 1, 0], [500, 40, 500]) 9 | polarization = 1 10 | # incidence,r,t,R,T=PM.Angular(kr,wavelength,polarization,0.,80.,400) 11 | # plt.plot(incidence,R) 12 | # plt.show() 13 | 14 | window = PM.Window(70 * wavelength, 0.4, 5.0, 5.0) 15 | beam = PM.Beam(wavelength, 38.7 / 180 * np.pi, polarization, 10 * wavelength) 16 | E, Hx, Hz = PM.fields(kr, beam, window) 17 | plt.figure(2) 18 | plt.imshow( 19 | abs(E), cmap="jet", extent=[0, window.width, 0, sum(kr.thickness)], aspect="auto" 20 | ) 21 | plt.colorbar() 22 | plt.show() 23 | 24 | 25 | plt.imshow( 26 | abs(Hx), cmap="jet", extent=[0, window.width, 0, sum(kr.thickness)], aspect="auto" 27 | ) 28 | plt.colorbar() 29 | plt.show() 30 | 31 | plt.imshow( 32 | abs(Hz), cmap="jet", extent=[0, window.width, 0, sum(kr.thickness)], aspect="auto" 33 | ) 34 | plt.colorbar() 35 | plt.show() 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright © 2025 Antoine Moreau and Denis Langevin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "PyMoosh" 7 | authors = [ 8 | {name = "Denis Langevin", email = "denis.langevin@univ-amu.fr"}, 9 | {name = "Antoine Moreau", email = "antoine.moreau@uca.fr"}] 10 | version = "4.0.1" 11 | description = "A scattering matrix formalism to solve Maxwell's equations in a multilayered structure." 12 | readme = "README.md" 13 | requires-python = ">=3.7" 14 | license = {text = "MIT"} 15 | keywords = ["multilayer optics", "photonic simulation", "optimization"] 16 | classifiers = [ 17 | "Development Status :: 5 - Production/Stable", 18 | "Intended Audience :: Science/Research", 19 | "Topic :: Scientific/Engineering :: Physics", 20 | "Programming Language :: Python :: 3", 21 | ] 22 | dependencies = [ 23 | "numpy", 24 | "matplotlib", 25 | "scipy", 26 | "refractiveindex; python_version >= '3.10'" 27 | ] 28 | 29 | 30 | [project.urls] 31 | Homepage = "https://github.com/AnMoreau/PyMoosh" 32 | Repository = "https://github.com/AnMoreau/PyMoosh/stable" 33 | Documentation = "https://github.com/AnMoreau/PyMoosh#readme" 34 | 35 | [tool.setuptools.packages.find] 36 | include = ["PyMoosh*"] 37 | 38 | [tool.setuptools] 39 | include-package-data = true 40 | zip-safe = false -------------------------------------------------------------------------------- /PyMoosh/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Copyright (C) 2022-2023, Antoine Moreau and Denis Langevin 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the MIT License 16 | # along with this program. If not, see . 17 | # 18 | """ 19 | PyMoosh - a scattering matrix formalism to solve Maxwell's equations 20 | in a multilayered structure. This makes PyMoosh unconditionally stable, 21 | allowing to explore even advanced properties of such multilayers, 22 | find poles and zeros of the scattering matrix (and thus guided modes), 23 | and many other things... 24 | 25 | """ 26 | __name__ = "PyMoosh" 27 | ## make accessible everything from `core` directly from the PyMoosh base package 28 | from PyMoosh.core import * 29 | from PyMoosh.classes import * 30 | from PyMoosh.vectorized import * 31 | from PyMoosh.optim_algo import * 32 | -------------------------------------------------------------------------------- /tests/test_abeles.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | from context import alt_methods 4 | import matplotlib.pyplot as plt 5 | 6 | incidence = 10 / 180 * np.pi 7 | polarisation = 0 8 | # materials = [1.513**2, 1.455**2, 2.079**2, (1.9+4.8j)**2, 1.0003**2] 9 | materials = [4.0, 1.5, 1.0] 10 | w_mean = 1 11 | 12 | nb_couches = 0 13 | 14 | wav = 1 15 | 16 | rs = [] 17 | r_abs = [] 18 | for i in range(1): 19 | # structure = np.random.random(nb_couches*2+1)*w_mean 20 | structure = np.array([1, 1]) 21 | 22 | # stack = [0]+[1,2]*nb_couches+[1,0] 23 | stack = [0, 2, 1, 0] 24 | 25 | epaisseurs = np.concatenate(([0], structure, [0])) 26 | 27 | print(structure, stack) 28 | 29 | chose = PM.Structure(materials, stack, epaisseurs, verbose=True) 30 | r, t, R, T = PM.coefficient(chose, wav, incidence, polarisation) 31 | 32 | r_ab, t_ab, R_ab, T_ab = alt_methods.coefficient_A( 33 | chose, wav, incidence, polarisation 34 | ) 35 | 36 | chose = PM.Structure(materials, stack, epaisseurs, verbose=True) 37 | r_t, t_t, R_t, T_t = alt_methods.coefficient_T(chose, wav, incidence, polarisation) 38 | 39 | print("POP") 40 | print(r, r_ab, r_t) 41 | print(t, t_ab, t_t) 42 | print("DIFFS") 43 | print(r - r_ab, r - r_t) 44 | print(t - t_ab, t - t_t) 45 | print(R, R_ab, R_t) 46 | print(T, T_ab, T_t) 47 | # rs.append(r) 48 | # r_abs.append(r_ab) 49 | 50 | # plt.plot(np.real(rs), label="RE r") 51 | # plt.plot(np.imag(rs), label="RE r") 52 | # plt.plot(np.real(r_abs), label="RE r_abeles") 53 | # plt.plot(np.imag(r_abs), label="IM r_abeles") 54 | # plt.legend() 55 | 56 | # plt.figure() 57 | # plt.plot(np.real(rs)**2, label="RE r") 58 | # plt.plot(np.imag(rs)**2, label="RE r") 59 | # plt.plot(np.real(r_abs)**2, label="RE r_abeles") 60 | # plt.plot(np.imag(r_abs)**2, label="IM r_abeles") 61 | # plt.show() 62 | -------------------------------------------------------------------------------- /tests/test_grad_abeles_Tmat.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import PyMoosh as PM 3 | from context import alt_methods 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | # materials = [1.513**2, 1.455**2, 2.079**2, (1.9+4.8j)**2, 1.0003**2] 8 | materials = [4.0, 1.5**2, 2.0 + 0.2j] 9 | wav = 20 10 | eps = 1e-10 11 | 12 | 13 | print("WARNING: the impedance formalism only computes r and R for the moment") 14 | 15 | print("Normal incidence:") 16 | incidence = 0 17 | nb_prob = 0 18 | prob = False 19 | 20 | ## Case 1: single layer, TE 21 | # structure = np.random.random(nb_couches*2+1)*w_mean 22 | structure = np.array([1, 2.1]) 23 | 24 | # stack = [0]+[1,2]*nb_couches+[1,0] 25 | stack = [0, 2, 1, 0] 26 | 27 | 28 | epaisseurs = np.concatenate(([0], structure, [0])) 29 | 30 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 31 | r, t, R, T = PM.coefficient_S(chose, wav, incidence, 0) 32 | 33 | 34 | chose1 = PM.Structure(materials, stack, epaisseurs, verbose=False) 35 | r_ab, t_ab, R_ab, T_ab, A_ab, B_ab = PM.coefficient_with_grad_A( 36 | chose1, wav, incidence, 0 37 | ) 38 | 39 | 40 | chose1 = PM.Structure(materials, stack, epaisseurs, verbose=False) 41 | r_t, t_t, R_t, T_t, A_T, B_T = PM.coefficient_with_grad_T(chose1, wav, incidence, 0) 42 | 43 | print(f"r_t = {r_t}, r_ab = {r_ab}") 44 | 45 | # print(f"matrices, A_ab={A_ab}\n, B_ab={B_ab}\n, A_T={A_T}\n, B_T={B_T}") 46 | 47 | # print("pop1") 48 | # for j in range(len(A_ab)): 49 | # print(B_ab[-j-1] @ A_ab[j]) 50 | # 51 | # print("pop2") 52 | # for j in range(len(A_T)): 53 | # print(A_T[j] @ B_T[-j-1]) 54 | 55 | for i in range(len(structure) + 1): 56 | r_ab, t_ab, R_ab, T_ab = PM.coefficient_with_grad_A( 57 | chose1, wav, incidence, 0, mode="grad", saved_mat=[A_ab, B_ab], i_change=i 58 | ) 59 | print(f"step {i}: r_ab = {r_ab}") 60 | 61 | for i in range(len(structure) + 1): 62 | r_t, t_t, R_t, T_t = PM.coefficient_with_grad_T( 63 | chose1, wav, incidence, 0, mode="grad", saved_mat=[A_T, B_T], i_change=i 64 | ) 65 | print(f"step {i}: r_t = {r_t}") 66 | -------------------------------------------------------------------------------- /tests/test_opti.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | import matplotlib.pyplot as plt 4 | 5 | materials = [] 6 | 7 | wavelength = 600 8 | 9 | 10 | def bragg(x): 11 | # This cost function corresponds to the problem 12 | # of maximizing the reflectance, at a given wavelength, 13 | # of a multilayered structure with alternating refractive 14 | # indexes. This problem is inspired by the first cases studied in 15 | # https://www.nature.com/articles/s41598-020-68719-3 16 | # :-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-: 17 | # The input, x, corresponds to the thicknesses of all the layers, : 18 | # starting with the upper one. It should be a python list, not a : 19 | # numpy array, with an even number of elements. : 20 | # :-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-: 21 | x = list(x) 22 | n = len(x) 23 | # Working wavelength 24 | wl = 600.0 25 | materials = [1, 1.4**2, 1.8**2] 26 | stack = [0] + [2, 1] * (n // 2) + [2] 27 | thicknesses = [0.0] + x + [0.0] 28 | structure = PM.Structure(materials, stack, thicknesses, verbose=False) 29 | _, _, R, _ = PM.coefficient(structure, wl, 0.0, 0) 30 | cost = 1 - R 31 | 32 | return cost 33 | 34 | 35 | nb_layers = 20 36 | min_th = 50 37 | max_th = 200 38 | 39 | X_min = np.array([min_th] * nb_layers) 40 | X_max = np.array([max_th] * nb_layers) 41 | 42 | budget = 10000 43 | 44 | start = np.array( 45 | [np.random.random() * (max_th - min_th) + min_th for i in range(nb_layers)] 46 | ) 47 | best_b, cost_b = PM.QODE(bragg, budget, X_min, X_max, progression=True) 48 | 49 | # print(cost_b) 50 | 51 | # best, convergence = PM.differential_evolution(bragg, budget, X_min, X_max, f1=0.9, f2=0.8, cr=0.7) 52 | 53 | # print(convergence[-1]) 54 | 55 | 56 | # best_saqn, convergence = PM.SAQNDE(bragg, budget, X_min, X_max, f1=0.5, f2=[0.6, 0.8], cr=[0.5, 0.6, 0.7]) 57 | 58 | # plt.plot(best_b, label="bfgs solution") 59 | # plt.plot(best, label="de solution") 60 | # plt.plot(best_saqn, label="SAQNde solution") 61 | # plt.legend() 62 | # plt.show() 63 | -------------------------------------------------------------------------------- /new_examples/anisotropic_structures.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import PyMoosh as PM 3 | import PyMoosh.anisotropic as ani 4 | from numpy import linalg as la_np 5 | 6 | wl_nm = 640 7 | thickness_nm = 100 8 | 9 | theta_in_rad = np.pi / 4 # Incidence angle 10 | 11 | 12 | perm_1 = 1.2 13 | 14 | perm_2 = 2.2 15 | 16 | 17 | opt_ind = [perm_1, perm_2, perm_2] # biaxial material 18 | 19 | mat_1 = ani.AniMaterial(opt_ind, specialType="ANI") 20 | 21 | mat_2 = PM.Material(1.0) # regular, Isotropic material (vacuum / air) 22 | 23 | # Lists! 24 | material_list = [mat_1, mat_2] # The materials 25 | stack = [1, 0, 0, 1] # Which material when (as in the usual Structure function) 26 | thickness = [ 27 | 0, 28 | thickness_nm, 29 | 25, 30 | 0, 31 | ] # The thickness of each layer (as in the usual Structure function) 32 | ani_rot_angle = [ 33 | 0.0, 34 | 0.5, 35 | -0.2, 36 | 0.0, 37 | ] # The rotation angle for each layer. MUST be provided for isotropic layers too, will simply be overlooked 38 | ani_rot_axis = [ 39 | "z", 40 | "x", 41 | [0, 1, 1], 42 | "z", 43 | ] # The rotation axis for each layer. MUST be provided for isotropic layers too, will simply be overlooked 44 | 45 | 46 | structure1 = ani.AniStructure( 47 | material_list, stack, thickness, ani_rot_angle, ani_rot_axis, verbose=False 48 | ) 49 | 50 | thetas = np.linspace(0, 80, 80) * np.pi / 180 51 | l_rpp = [] 52 | l_rps = [] 53 | l_rsp = [] 54 | l_rss = [] 55 | l_tpp = [] 56 | l_tps = [] 57 | l_tsp = [] 58 | l_tss = [] 59 | 60 | 61 | for theta_in_rad in thetas: 62 | res = ani.coefficients_ani(structure1, wl_nm, theta_in_rad) 63 | l_tpp.append(res[0]) 64 | l_tps.append(res[1]) 65 | l_tsp.append(res[2]) 66 | l_tss.append(res[3]) 67 | l_rpp.append(res[4]) 68 | l_rps.append(res[5]) 69 | l_rsp.append(res[6]) 70 | l_rss.append(res[7]) 71 | 72 | import matplotlib.pyplot as plt 73 | 74 | plt.subplot(2, 2, 1) 75 | plt.plot(thetas * 180 / np.pi, l_tpp, label="tpp", linestyle="--") 76 | plt.plot(thetas * 180 / np.pi, l_tss, label="tss", linestyle="--") 77 | plt.legend() 78 | 79 | plt.subplot(2, 2, 2) 80 | plt.plot(thetas * 180 / np.pi, l_rpp, label="rpp", linestyle="--") 81 | plt.plot(thetas * 180 / np.pi, l_rss, label="rss", linestyle="--") 82 | plt.legend() 83 | 84 | plt.subplot(2, 2, 3) 85 | plt.plot(thetas * 180 / np.pi, l_tsp, label="tsp", linestyle="--") 86 | plt.plot(thetas * 180 / np.pi, l_tps, label="tps", linestyle="--") 87 | plt.legend() 88 | 89 | plt.subplot(2, 2, 4) 90 | plt.plot(thetas * 180 / np.pi, l_rsp, label="rsp", linestyle="--") 91 | plt.plot(thetas * 180 / np.pi, l_rps, label="rps", linestyle="--") 92 | plt.legend() 93 | plt.show() 94 | -------------------------------------------------------------------------------- /tests/speed_test_coefficients.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | import matplotlib.pyplot as plt 4 | from time import time 5 | 6 | ## Computation times 7 | 8 | nb_iter = 500 # averaging 9 | layers = np.arange(5, 181, 5) 10 | 11 | wav = 3.5 12 | ep1 = 2 13 | ep2 = 3 14 | materials = [1, 1.5**2, 2**2] 15 | incidence = 15 * np.pi / 180 16 | 17 | times_s_tm = np.zeros(len(layers), dtype=float) 18 | 19 | times_t_tm = np.zeros(len(layers), dtype=float) 20 | 21 | times_a_tm = np.zeros(len(layers), dtype=float) 22 | 23 | times_i_tm = np.zeros(len(layers), dtype=float) 24 | 25 | times_dn_tm = np.zeros(len(layers), dtype=float) 26 | 27 | for i in range(nb_iter): 28 | for j, nb_couches in enumerate(layers): 29 | 30 | ## Case 1: single layer, TE 31 | # structure = np.random.random(nb_couches*2+1)*w_mean 32 | structure = np.array([ep1, ep2] * nb_couches + [ep1]) 33 | 34 | stack = [0] + [1, 2] * nb_couches + [1, 0] 35 | 36 | epaisseurs = np.concatenate(([0], structure, [0])) 37 | 38 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 39 | a = time() 40 | r, t, R, T = PM.coefficient_S(chose, wav, incidence, 1) 41 | b = time() 42 | times_s_tm[j] += (b - a) / nb_iter 43 | 44 | chose1 = PM.Structure(materials, stack, epaisseurs, verbose=False) 45 | a = time() 46 | r_ab, t_ab, R_ab, T_ab = PM.coefficient_A(chose1, wav, incidence, 1) 47 | b = time() 48 | times_a_tm[j] += (b - a) / nb_iter 49 | 50 | chose1 = PM.Structure(materials, stack, epaisseurs, verbose=False) 51 | a = time() 52 | r_t, t_t, R_t, T_t = PM.coefficient_T(chose1, wav, incidence, 1) 53 | b = time() 54 | times_t_tm[j] += (b - a) / nb_iter 55 | 56 | chose1 = PM.Structure(materials, stack, epaisseurs, verbose=False) 57 | a = time() 58 | r_dn, t_dn, R_dn, T_dn = PM.coefficient_DN(chose1, wav, incidence, 1) 59 | b = time() 60 | times_dn_tm[j] += (b - a) / nb_iter 61 | 62 | chose1 = PM.Structure(materials, stack, epaisseurs, verbose=False) 63 | a = time() 64 | r_i, t_i, R_i, T_i = PM.coefficient_I(chose1, wav, incidence, 1) 65 | b = time() 66 | times_i_tm[j] += (b - a) / nb_iter 67 | 68 | 69 | plt.figure(figsize=(7, 7)) 70 | plt.plot(layers, times_a_tm, "o", label="abeles") 71 | plt.plot(layers, times_dn_tm, "o", label="D2N") 72 | plt.plot(layers, times_s_tm, "o", label="S") 73 | plt.plot(layers, times_t_tm, "o", label="T") 74 | plt.plot(layers, times_i_tm, "o", label="Impedance") 75 | plt.title("Computation times") 76 | plt.xlabel("Nb Layers") 77 | plt.legend() 78 | plt.show() 79 | -------------------------------------------------------------------------------- /tests/test_vectorize.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | 4 | from context import models 5 | import matplotlib.pyplot as plt 6 | import math 7 | 8 | mat1 = PM.Material([models.Drude, 1e10, 1e5], specialType="Model") 9 | mat2 = PM.Material(["main", "SiO2", "Malitson"], specialType="RII") 10 | materials = [1.0**2, "Gold", mat2, mat1] 11 | # materials = [1, 1.2**2, 1.5**2 ] 12 | 13 | incidence = 0 14 | nb_prob = 0 15 | prob = False 16 | 17 | 18 | single_stack = [0, 3, 2, 1] 19 | 20 | ## Case 1: single layer, TE 21 | 22 | stack = single_stack 23 | 24 | 25 | epaisseurs = np.array([0, 100, 150, 0]) 26 | 27 | interface = PM.Structure(materials, stack, epaisseurs, verbose=True, si_units=True) 28 | 29 | 30 | wavelength, sr, st, sR, sT = PM.spectrum_S(interface, 0, 0.0, 400.0, 800.0, 20) 31 | # # For TM polarization, same incidence angles 32 | # wavelength, sr_p, st_p, sR_p, sT_p = PM.spectrum_S(interface, 0, 1., 400., 800., 200) 33 | 34 | # wavelength, ar, at, aR, aT = PM.spectrum_A(interface, 0, 0., 400., 800., 200) 35 | # # For TM polarization, same incidence angles 36 | # wavelength, ar_p, at_p, aR_p, aT_p = PM.spectrum_A(interface, 0, 1., 400., 800., 200) 37 | 38 | # # Visualization of the result 39 | # import matplotlib.pyplot as plt 40 | # plt.rcParams['figure.dpi'] = 150 41 | 42 | # plt.figure(2) 43 | # plt.plot(wavelength, np.real(sr), label="Smat TE polarisation") 44 | # plt.plot(wavelength, np.real(sr_p), label="Smat TM polarisation") 45 | # plt.plot(wavelength, np.real(ar), "+", label="Amat TE polarisation") 46 | # plt.plot(wavelength, np.real(ar_p), "+", label="Amat TM polarisation") 47 | # plt.ylabel('r') 48 | # plt.ylim(-1,1) 49 | # plt.legend() 50 | # plt.show() 51 | 52 | 53 | wavelength = 600 54 | interface = PM.Structure([1.0, 2.25], [0, 1], [10 * wavelength, 10 * wavelength]) 55 | 56 | # For TE polarization 57 | angles, sr, st, sR, sT = PM.angular_S(interface, wavelength, 0.0, 0.0, 89.0, 20) 58 | # For TM polarization, same incidence angles 59 | angles, sr_p, st_p, sR_p, sT_p = PM.angular_S(interface, wavelength, 1.0, 0.0, 89.0, 20) 60 | 61 | angles, ar, at, aR, aT = PM.angular_A(interface, wavelength, 0, 0.0, 89.0, 20) 62 | # For TM polarization, same incidence angles 63 | angles, ar_p, at_p, aR_p, aT_p = PM.angular_A(interface, wavelength, 1.0, 0.0, 89.0, 20) 64 | 65 | # Visualization of the result 66 | import matplotlib.pyplot as plt 67 | 68 | plt.rcParams["figure.dpi"] = 150 69 | 70 | plt.figure(2) 71 | plt.plot(angles, np.abs(sR), label="Smat TE polarisation") 72 | plt.plot(angles, np.abs(sR_p), label="Smat TM polarisation") 73 | plt.plot(angles, np.abs(aR), "+", label="Amat TE polarisation") 74 | plt.plot(angles, np.abs(aR_p), "+", label="Amat TM polarisation") 75 | plt.ylabel("r") 76 | # plt.ylim(-1,1) 77 | plt.legend() 78 | plt.show() 79 | -------------------------------------------------------------------------------- /new_examples/optimising.py: -------------------------------------------------------------------------------- 1 | import PyMoosh as PM 2 | import numpy as np 3 | 4 | # The wavelength we are interested in 5 | wav = 600 6 | # The angle of incidence and polarization the structure is intended for 7 | angle = 25 * np.pi / 180 8 | polar = 0 # 0 for TE, 1 for TM 9 | 10 | 11 | def objective_function(layers, wavelength=wav, angle=angle, polar=polar): 12 | """ 13 | We want to maximise the reflectance of the structure for the chosen wavelength 14 | """ 15 | nb_lay = len(layers) // 2 16 | mat = [1, 1.5, 2] 17 | stack = [0] + [1, 2] * nb_lay + [0] 18 | thickness = [0] + [t for t in layers] + [0] 19 | structure = PM.Structure(mat, stack, thickness, verbose=False) 20 | r, t, R, T = PM.coefficient(structure, wavelength, angle, polar) 21 | return 1 - R 22 | 23 | 24 | budget = 1000 # 25 | 26 | nb_layers = 10 # We want a 10-layer Dielectric mirror 27 | min_lay = 10 # No layer thinner than 10 nm 28 | max_lay = 300 # No layer thicker than 800 nm (half the largestthe wavelength) 29 | 30 | X_min = np.array([min_lay] * nb_layers) 31 | X_max = np.array([max_lay] * nb_layers) 32 | 33 | best, convergence = PM.differential_evolution(objective_function, budget, X_min, X_max) 34 | 35 | import matplotlib.pyplot as plt 36 | 37 | plt.plot(convergence) 38 | plt.xlabel("Iteration") 39 | plt.ylabel("Cost function") 40 | plt.show() 41 | 42 | 43 | wav_beg = 400 44 | wav_end = 800 45 | nb_wav = 100 46 | reflectivity = np.zeros(nb_wav) 47 | wav_list = np.linspace(wav_beg, wav_end, nb_wav) 48 | mat = [1, 1.5, 2] 49 | stack = [0] + [1, 2] * (nb_layers // 2) + [0] 50 | thickness = [0] + [t for t in best] + [0] 51 | structure = PM.Structure(mat, stack, thickness, verbose=False) 52 | for i, wav in enumerate(wav_list): 53 | r, t, R, T = PM.coefficient(structure, wav, angle, polar) 54 | reflectivity[i] = R 55 | 56 | plt.figure(1) 57 | plt.plot(wav_list, reflectivity) 58 | plt.xlabel("Wavelength (nm)") 59 | plt.ylabel("Reflectivity") 60 | budget = 15000 61 | 62 | nb_layers = 30 # We want a 30-layer Dielectric mirror 63 | min_lay = 10 # No layer thinner than 10 nm 64 | max_lay = 300 # No layer thicker than 800 nm (half the wavelength) 65 | 66 | X_min = np.array([min_lay] * nb_layers) 67 | X_max = np.array([max_lay] * nb_layers) 68 | 69 | best, convergence = PM.differential_evolution(objective_function, budget, X_min, X_max) 70 | 71 | plt.figure(2) 72 | plt.plot(convergence) 73 | plt.xlabel("Iteration") 74 | plt.ylabel("Cost function") 75 | plt.show() 76 | 77 | 78 | reflectivity = np.zeros(nb_wav) 79 | wav_list = np.linspace(wav_beg, wav_end, nb_wav) 80 | mat = [1, 1.5, 2] 81 | stack = [0] + [1, 2] * (nb_layers // 2) + [0] 82 | thickness = [0] + [t for t in best] + [0] 83 | structure = PM.Structure(mat, stack, thickness, verbose=False) 84 | for i, wav in enumerate(wav_list): 85 | r, t, R, T = PM.coefficient(structure, wav, angle, polar) 86 | reflectivity[i] = R 87 | 88 | plt.plot(wav_list, reflectivity) 89 | plt.xlabel("Wavelength (nm)") 90 | plt.ylabel("Reflectivity") 91 | plt.show() 92 | -------------------------------------------------------------------------------- /new_examples/basic_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import PyMoosh as PM 3 | 4 | ############################################### 5 | 6 | ### Setting up the studied structure 7 | # Defining materials 8 | material_list = [1.0, "Si", "Water"] 9 | 10 | # Defining the vertical order of the materials 11 | stack = [0, 1, 2, 1] 12 | 13 | # Defining the thickness of each layer 14 | thickness = [300, 200, 500, 200] 15 | 16 | # Defining the structure 17 | struct = PM.Structure(material_list, stack, thickness) 18 | 19 | ### Simple calculations 20 | wavelength = 700 # nm ! 21 | 22 | angle_inc = np.pi / 4 # rad 23 | 24 | pol = 1 # 0 for TE, 1 for TM 25 | 26 | # Calculation 27 | r, t, R, T = PM.coefficient(struct, wavelength, angle_inc, pol) 28 | 29 | 30 | ### Spectrum 31 | wav_beg = 400 32 | wav_end = 800 33 | nb_wav = 150 34 | 35 | wavs, r, t, R, T = PM.spectrum(struct, wavelength, angle_inc, wav_beg, wav_end, nb_wav) 36 | 37 | import matplotlib.pyplot as plt 38 | 39 | plt.plot(wavs, R) 40 | plt.xlabel("wavelength (nm)") 41 | plt.ylabel("Reflectivity") 42 | plt.show() 43 | 44 | 45 | ################################################ 46 | ## More complex use cases 47 | 48 | # Accessing materials directly 49 | si = struct.materials[1] 50 | water = struct.materials[2] 51 | 52 | refractive_index_glass = np.sqrt(si.get_permittivity(600)) 53 | epsilon = water.get_permittivity(600) 54 | # You need to specify a wavelength at which to compute the permittivity, here 600 nm 55 | 56 | print(refractive_index_glass) 57 | print(np.sqrt(epsilon)) 58 | 59 | 60 | ## Studying a single interface 61 | 62 | interface = PM.Structure([1.0, 2.25], [0, 1], [10 * wavelength, 10 * wavelength]) 63 | 64 | # Wavelength 65 | wavelength = 600 66 | # Incidence angle 67 | angle_beg = 0.0 68 | angle_end = np.pi / 6.0 69 | nb_angle = 200 70 | # Polarization 71 | pol = 1.0 72 | 73 | # For TE polarization 74 | incidence, r, t, R, T = PM.angular( 75 | interface, wavelength, 0.0, angle_beg, angle_end, nb_angle 76 | ) 77 | # For TM polarization, same incidence angles 78 | incidence, r_p, t_p, R_p, T_p = PM.angular( 79 | interface, wavelength, 1.0, angle_beg, angle_end, nb_angle 80 | ) 81 | 82 | # Visualization of the result 83 | import matplotlib.pyplot as plt 84 | 85 | plt.figure(1) 86 | plt.plot(incidence, R, label="TE polarisation") 87 | plt.plot(incidence, R_p, label="TM polarisation") 88 | plt.ylabel("Reflectance") 89 | # plt.ylim(0,1) 90 | plt.legend() 91 | plt.show() 92 | 93 | 94 | # interface = PM.Structure([1.,"Si"],[0, 1],[10*wavelength, 10*wavelength]) 95 | # # For TE polarization 96 | # wavelength, r, t, R, T = PM.spectrum(interface, 0, 0., 400., 800., 200) 97 | # # For TM polarization, same incidence angles 98 | # wavelength, r_p, t_p, R_p, T_p = PM.spectrum(interface, 0, 1., 400., 800., 200) 99 | 100 | # # Visualization of the result 101 | # import matplotlib.pyplot as plt 102 | # plt.rcParams['figure.dpi'] = 150 103 | 104 | # plt.figure(2) 105 | # plt.plot(wavelength, R, label="TE polarisation") 106 | # plt.plot(wavelength, R_p, label="TM polarisation") 107 | # plt.ylabel('Reflectance') 108 | # plt.ylim(0,1) 109 | # plt.legend() 110 | # plt.show() 111 | -------------------------------------------------------------------------------- /notebooks/In-depth_examples/h70_2.dat: -------------------------------------------------------------------------------- 1 | # Created by Octave 4.2.2, Mon Mar 04 16:03:52 2024 CET 2 | # name: A 3 | # type: matrix 4 | # rows: 115 5 | # columns: 2 6 | 5000.827011767071 0.00704 7 | 5025.055807641667 0.00696 8 | 5049.520547014156 0.00694 9 | 5074.224667477558 0.00695 10 | 5099.171699727605 0.00695 11 | 5124.365217977532 0.00692 12 | 5149.808946465424 0.00691 13 | 5175.506604463978 0.00691 14 | 5201.461985323139 0.00681 15 | 5227.679040309373 0.00689 16 | 5254.161719041499 0.00682 17 | 5280.914051022016 0.00687 18 | 5307.940232189581 0.00686 19 | 5335.244460581069 0.00685 20 | 5362.831020476598 0.00676 21 | 5390.704371804643 0.00675 22 | 5418.86898081923 0.00675 23 | 5447.329406992717 0.00679 24 | 5476.090395423175 0.00681 25 | 5505.156702303674 0.00675 26 | 5534.533184706765 0.00672 27 | 5564.224896155009 0.00679 28 | 5594.236906646563 0.00681 29 | 5624.574395486212 0.00679 30 | 5655.242750192279 0.00685 31 | 5686.247380829162 0.00682 32 | 5717.593848742667 0.00688 33 | 5749.287805535767 0.00669 34 | 5781.335128353098 0.00668 35 | 5813.741726209798 0.00668 36 | 5846.513640603289 0.0067 37 | 5879.657152958058 0.00642 38 | 5913.178584621929 0.00673 39 | 5947.084401134815 0.00668 40 | 5981.381323694281 0.00664 41 | 6016.076122518137 0.00668 42 | 6051.175724987279 0.0068 43 | 6086.687331375936 0.00665 44 | 6122.618201261836 0.00665 45 | 6158.975765779177 0.0067 46 | 6195.767747878945 0.00674 47 | 6233.001941449212 0.00674 48 | 6270.686327935437 0.00663 49 | 6308.829201420324 0.00671 50 | 6347.438940097029 0.00671 51 | 6386.524127537738 0.00664 52 | 6426.093682909574 0.00676 53 | 6466.156624422598 0.00677 54 | 6506.722195554421 0.00691 55 | 6547.800000749068 0.00699 56 | 6589.399760439736 0.00696 57 | 6631.531486544655 0.00696 58 | 6674.205404144162 0.00701 59 | 6717.432137427322 0.00705 60 | 6761.222452818973 0.00709 61 | 6805.587398142816 0.00707 62 | 6850.538452664906 0.00706 63 | 6896.087260771113 0.00707 64 | 6942.245777354708 0.00708 65 | 6989.026424775064 0.0072 66 | 7036.441816605502 0.00717 67 | 7084.504909717762 0.00731 68 | 7133.229168689978 0.00739 69 | 7182.628279119406 0.00764 70 | 7232.716306929921 0.00784 71 | 7283.507870824454 0.008019999999999999 72 | 7335.017844631412 0.00818 73 | 7387.261524423469 0.008410000000000001 74 | 7440.254809680179 0.008659999999999999 75 | 7494.013894111592 0.008920000000000001 76 | 7548.55544124862 0.00932 77 | 7603.896775063434 0.009820000000000001 78 | 7660.05555868937 0.01037 79 | 7717.050038780106 0.01104 80 | 7774.898948666501 0.01173 81 | 7833.621769898108 0.01266 82 | 7893.238393882904 0.01365 83 | 7953.769320312154 0.01381 84 | 8015.235873323109 0.01421 85 | 8077.659849598335 0.01404 86 | 8141.063728157313 0.01368 87 | 8205.470899256428 0.01371 88 | 8270.905298308024 0.01478 89 | 8337.391628186617 0.01681 90 | 8404.955602166823 0.0197 91 | 8473.623563018555 0.02342 92 | 8543.422719149836 0.02819 93 | 8614.381403080466 0.03428 94 | 8686.528675078092 0.04202 95 | 8759.894574668793 0.05199 96 | 8834.510396381158 0.06318 97 | 8910.408277313076 0.0726 98 | 8987.621465794242 0.07607 99 | 9066.184616421788 0.07202 100 | 9146.133360996118 0.06325 101 | 9227.50459642766 0.0535 102 | 9310.336801433792 0.04457 103 | 9394.669590363001 0.03729 104 | 9480.54411269682 0.03164 105 | 9568.002944801674 0.0272 106 | 9657.090525093385 0.02385 107 | 9747.852684523536 0.02118 108 | 9840.336989818872 0.01912 109 | 9934.593122529546 0.01735 110 | 10030.67239188023 0.016 111 | 10128.62810750615 0.01473 112 | 10228.51599176821 0.01393 113 | 10330.39367518755 0.01314 114 | 10434.32110320992 0.01247 115 | 10540.3609869155 0.01193 116 | 10648.57828169217 0.01148 117 | 10759.04063356117 0.01105 118 | 10871.81887454083 0.01077 119 | 10986.98648583083 0.01055 120 | 11104.6200905142 0.01026 121 | 122 | 123 | -------------------------------------------------------------------------------- /notebooks/In-depth_examples/nlplot.data: -------------------------------------------------------------------------------- 1 | # Created by Octave 4.2.2, Mon Mar 04 16:03:52 2024 CET 2 | # name: A 3 | # type: matrix 4 | # rows: 115 5 | # columns: 2 6 | 5000.827011767071 0.00704 7 | 5025.055807641667 0.00696 8 | 5049.520547014156 0.00694 9 | 5074.224667477558 0.00695 10 | 5099.171699727605 0.00695 11 | 5124.365217977532 0.00692 12 | 5149.808946465424 0.00691 13 | 5175.506604463978 0.00691 14 | 5201.461985323139 0.00681 15 | 5227.679040309373 0.00689 16 | 5254.161719041499 0.00682 17 | 5280.914051022016 0.00687 18 | 5307.940232189581 0.00686 19 | 5335.244460581069 0.00685 20 | 5362.831020476598 0.00676 21 | 5390.704371804643 0.00675 22 | 5418.86898081923 0.00675 23 | 5447.329406992717 0.00679 24 | 5476.090395423175 0.00681 25 | 5505.156702303674 0.00675 26 | 5534.533184706765 0.00672 27 | 5564.224896155009 0.00679 28 | 5594.236906646563 0.00681 29 | 5624.574395486212 0.00679 30 | 5655.242750192279 0.00685 31 | 5686.247380829162 0.00682 32 | 5717.593848742667 0.00688 33 | 5749.287805535767 0.00669 34 | 5781.335128353098 0.00668 35 | 5813.741726209798 0.00668 36 | 5846.513640603289 0.0067 37 | 5879.657152958058 0.00642 38 | 5913.178584621929 0.00673 39 | 5947.084401134815 0.00668 40 | 5981.381323694281 0.00664 41 | 6016.076122518137 0.00668 42 | 6051.175724987279 0.0068 43 | 6086.687331375936 0.00665 44 | 6122.618201261836 0.00665 45 | 6158.975765779177 0.0067 46 | 6195.767747878945 0.00674 47 | 6233.001941449212 0.00674 48 | 6270.686327935437 0.00663 49 | 6308.829201420324 0.00671 50 | 6347.438940097029 0.00671 51 | 6386.524127537738 0.00664 52 | 6426.093682909574 0.00676 53 | 6466.156624422598 0.00677 54 | 6506.722195554421 0.00691 55 | 6547.800000749068 0.00699 56 | 6589.399760439736 0.00696 57 | 6631.531486544655 0.00696 58 | 6674.205404144162 0.00701 59 | 6717.432137427322 0.00705 60 | 6761.222452818973 0.00709 61 | 6805.587398142816 0.00707 62 | 6850.538452664906 0.00706 63 | 6896.087260771113 0.00707 64 | 6942.245777354708 0.00708 65 | 6989.026424775064 0.0072 66 | 7036.441816605502 0.00717 67 | 7084.504909717762 0.00731 68 | 7133.229168689978 0.00739 69 | 7182.628279119406 0.00764 70 | 7232.716306929921 0.00784 71 | 7283.507870824454 0.008019999999999999 72 | 7335.017844631412 0.00818 73 | 7387.261524423469 0.008410000000000001 74 | 7440.254809680179 0.008659999999999999 75 | 7494.013894111592 0.008920000000000001 76 | 7548.55544124862 0.00932 77 | 7603.896775063434 0.009820000000000001 78 | 7660.05555868937 0.01037 79 | 7717.050038780106 0.01104 80 | 7774.898948666501 0.01173 81 | 7833.621769898108 0.01266 82 | 7893.238393882904 0.01365 83 | 7953.769320312154 0.01381 84 | 8015.235873323109 0.01421 85 | 8077.659849598335 0.01404 86 | 8141.063728157313 0.01368 87 | 8205.470899256428 0.01371 88 | 8270.905298308024 0.01478 89 | 8337.391628186617 0.01681 90 | 8404.955602166823 0.0197 91 | 8473.623563018555 0.02342 92 | 8543.422719149836 0.02819 93 | 8614.381403080466 0.03428 94 | 8686.528675078092 0.04202 95 | 8759.894574668793 0.05199 96 | 8834.510396381158 0.06318 97 | 8910.408277313076 0.0726 98 | 8987.621465794242 0.07607 99 | 9066.184616421788 0.07202 100 | 9146.133360996118 0.06325 101 | 9227.50459642766 0.0535 102 | 9310.336801433792 0.04457 103 | 9394.669590363001 0.03729 104 | 9480.54411269682 0.03164 105 | 9568.002944801674 0.0272 106 | 9657.090525093385 0.02385 107 | 9747.852684523536 0.02118 108 | 9840.336989818872 0.01912 109 | 9934.593122529546 0.01735 110 | 10030.67239188023 0.016 111 | 10128.62810750615 0.01473 112 | 10228.51599176821 0.01393 113 | 10330.39367518755 0.01314 114 | 10434.32110320992 0.01247 115 | 10540.3609869155 0.01193 116 | 10648.57828169217 0.01148 117 | 10759.04063356117 0.01105 118 | 10871.81887454083 0.01077 119 | 10986.98648583083 0.01055 120 | 11104.6200905142 0.01026 121 | 122 | 123 | -------------------------------------------------------------------------------- /PyMoosh/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains easy-to-import functions for various 3 | material models (Drude, Lorentz, Brendel&Bormann...) 4 | """ 5 | 6 | import numpy as np 7 | from scipy.special import erfc 8 | 9 | """ 10 | From the old Material 11 | 12 | Models 13 | Drude case needs : gamma0 (damping) and omega_p 14 | Drude-Lorentz needs : gamma0 (damping) and omega_p + f, gamma and omega of all resonance 15 | BB case needs : gamma0 (damping) and omega_p + f, gamma, omega and sigma of all resonance 16 | ExpData / tuple(list(lambda), list(perm)) / 'ExpData' / 'ExpData' 17 | """ 18 | 19 | 20 | def BrendelBormann(wav, f0, omega_p, Gamma0, f, omega, gamma, sigma): 21 | """ 22 | Brendel & Bormann model, using Voigt functions to model lorentzian 23 | resonances potentially widened with a gaussian distribution. 24 | f0, Gamma0 and omega_p are the chi_f parameters (eps_inf, plasma frequency) 25 | f, gamma, omega, sigma are the chi_b parameters (Lorentz resonances) 26 | f, gamma, omega, sigma must be lists (np arrays) of the same lengths 27 | They are given in eV (wav in nm) 28 | """ 29 | # Brendel-Bormann model with n resonances 30 | w = 6.62606957e-25 * 299792458 / 1.602176565e-19 / wav 31 | chi_b = 0 32 | for i in range(len(f)): 33 | a = np.sqrt(w * (w + 1j * gamma[i])) 34 | x = (a - omega[i]) / (np.sqrt(2) * sigma[i]) 35 | y = (a + omega[i]) / (np.sqrt(2) * sigma[i]) 36 | # Polarizability due to bound electrons 37 | erx = np.exp(-(x**2)) * erfc(-1.0j * x) 38 | ery = np.exp(-(y**2)) * erfc(-1.0j * y) 39 | oscill_strength = ( 40 | 1j * np.sqrt(np.pi) * f[i] * omega_p**2 / (2 * np.sqrt(2) * a * sigma[i]) 41 | ) 42 | chi_b += oscill_strength * (erx + ery) 43 | # Equivalent polarizability linked to free electrons (Drude model) 44 | chi_f = -(omega_p**2) * f0 / (w * (w + 1j * Gamma0)) 45 | epsilon = 1 + chi_f + chi_b 46 | return epsilon 47 | 48 | 49 | def Drude(wav, omega_p, Gamma0): 50 | """ 51 | Drude model, with only the plasma frequency omega_p 52 | and damping Gamma0 and omega_p 53 | They are given in eV (wav in nm) 54 | """ 55 | w = 6.62606957e-25 * 299792458 / 1.602176565e-19 / wav 56 | chi_f = -(omega_p**2) / (w * (w + 1j * Gamma0)) 57 | return 1 + chi_f 58 | 59 | 60 | def Lorentz(wav, f, omega, gamma, eps): 61 | """ 62 | Lorentz model, with lorentzian resonances (elastically bound electrons) 63 | eps is eps_inf, the background permittivity 64 | f, gamma, omega, sigma are the chi_b parameters (Lorentz resonances) 65 | f, gamma, omega, sigma must be lists (np arrays) of the same lengths 66 | They are given in eV (wav in nm) 67 | """ 68 | w = 6.62606957e-25 * 299792458 / 1.602176565e-19 / wav 69 | chi = 0 70 | for i in range(len(f)): 71 | chi += f[i] / (omega[i] ** 2 - w**2 - 1.0j * gamma[i] * w) 72 | return eps + chi 73 | 74 | 75 | def DrudeLorentz(wav, omega_p, Gamma0, f, omega, gamma): 76 | """ 77 | Drude Lorentz model, using both lorentzian resonances and a plasma frequency 78 | f0, Gamma0 and omega_p are the chi_f parameters (eps_inf, plasma frequency) 79 | f, gamma, omega, sigma are the chi_b parameters (Lorentz resonances) 80 | f, gamma, omega, sigma must be lists (np arrays) of the same lengths 81 | They are given in eV (wav in nm) 82 | """ 83 | w = 6.62606957e-25 * 299792458 / 1.602176565e-19 / wav 84 | chi_f = -(omega_p**2) / (w * (w + 1j * Gamma0)) 85 | chi_b = 0 86 | for i in range(len(f)): 87 | chi_b += f[i] / (omega[i] ** 2 - w**2 - 1.0j * gamma[i] * w) 88 | return 1 + chi_f + chi_b 89 | 90 | 91 | def ExpData(wav, wav_list, permittivities): 92 | """ 93 | Interpolation from experimental data 94 | Wavelenght must be in nm 95 | """ 96 | return np.interp(wav, wav_list, permittivities) 97 | -------------------------------------------------------------------------------- /new_examples/non_locality.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import PyMoosh as PM 3 | import matplotlib.pyplot as plt 4 | import csv 5 | from PyMoosh.non_local import * 6 | 7 | # Lets read the external experimental data and the wavelength list. 8 | # This will be useful when we call the cost function. 9 | wavelength_list = np.linspace(5000, 8000, 3001) 10 | 11 | with open("nlplot.data", newline="") as csvfile: 12 | expdata = list(csv.reader(csvfile))[0] 13 | expdata = [float(p.strip()) for p in expdata] 14 | expdata = np.array(expdata) 15 | (refplot,) = plt.plot(wavelength_list, expdata, label="Reference") 16 | # plt.show() 17 | 18 | 19 | def dopedsc_basic(wavelength): 20 | """ 21 | Function returning the characteristics of the response of a 22 | doped semiconductor in the infra-red range. It is a non dispersive 23 | background permittivity with a Drude part. For nonlocality to be 24 | taken into account beta**2 is required, as well as a distinction between 25 | the permittivity of the background (chi_b) and the effective one of the 26 | electron gas (chi_f). The plasma frequency w_p is also required. 27 | 28 | Here beta**2 is complex, but does not change with the frequency. 29 | """ 30 | chi_b = 11.6644913 31 | w_p = 8.9e14 32 | gamma = 3.53201120e12 33 | beta2 = 1.92864068e30 34 | w = 2 * np.pi * 299792458 * 1e9 / wavelength 35 | # We put a negative imaginary part for the losses that are intrinsic 36 | # to the electron gas. Nothing dispersive here. 37 | eta2 = beta2 - 1.0j * 2.2881657413884294e29 38 | chi_f = -(w_p**2) / (w * (w + 1j * gamma)) 39 | return eta2, chi_b, chi_f, w_p 40 | 41 | 42 | # Now that we have defined the functions, we can instanciate (declare) 43 | # and create a material nSC (n-doped semiconductor) that is nonlocal. 44 | # You juste have to provide a function that provides the parameters. 45 | nSC = NLMaterial(dopedsc_basic) 46 | # Now we define the materials we use 47 | materials = [15.6816, nSC, 14.2129] 48 | # And how we stack them. 49 | stack = [0, 1, 2] 50 | # Their thickness. For the superstrate and the substrate, thickness makes 51 | # no sense so let's put it to 0. 52 | thickness = [0, 105, 0] 53 | theta = np.pi * 37 / 180 54 | pol = 1.0 55 | 56 | simple_nl = NLStructure(materials, stack, thickness) 57 | R_modelsimple = [] 58 | for wl in wavelength_list: 59 | _, _, R, _ = NLcoefficient(simple_nl, wl, theta, pol) 60 | R_modelsimple.append(R) 61 | R = np.array(R_modelsimple) 62 | R = R / max(R) * max(expdata) 63 | (simpleplot,) = plt.plot(wavelength_list, R, label="Simple NL") 64 | 65 | 66 | def sc_2ndviscosity(wavelength, chi_b, w_p, gamma, beta_0, tau): 67 | """ 68 | Function returning the characteristics of the response of a 69 | doped semiconductor in the infra-red range. It is a non dispersive 70 | background permittivity with a Drude part. For nonlocality to be 71 | taken into account beta**2 is required, as well as a distinction between 72 | the permittivity of the background (chi_b) and the effective one of the 73 | electron gas (chi_f). The plasma frequency w_p is also required. 74 | 75 | Here beta**2 is complex and the imaginary part is dispersive, because of 76 | the _second viscosity_ in electron gases. 77 | 78 | The parameters of the model have to be provided when declaring 79 | the material as nonlocal. 80 | """ 81 | w = 2 * np.pi * 299792458 * 1e9 / wavelength 82 | eta2 = beta_0**2 - 1.0j * w * tau 83 | chi_f = -(w_p**2) / (w * (w + 1j * gamma)) 84 | 85 | return eta2, chi_b, chi_f, w_p 86 | 87 | 88 | chi_b = 11.6644913 89 | w_p = 8.9e14 90 | gamma = 3.53201120e12 91 | beta2 = 1.92864068e30 92 | nSC_2nd = NLMaterial([sc_2ndviscosity, chi_b, w_p, gamma, np.sqrt(beta2), 9.718e14]) 93 | 94 | advanced_nl = NLStructure([15.6816, nSC_2nd, 14.2129], stack, thickness) 95 | R_advanced = [] 96 | for wl in wavelength_list: 97 | _, _, R, _ = NLcoefficient(advanced_nl, wl, theta, pol) 98 | R_advanced.append(R) 99 | R = np.array(R_advanced) 100 | R = R / max(R) * max(expdata) 101 | (advancedplot,) = plt.plot(wavelength_list, R, label="Advanced NL") 102 | plt.xlabel("Wavelength (nm)") 103 | plt.ylabel("Reflectivity") 104 | 105 | plt.legend() 106 | plt.show() 107 | 108 | 109 | plt.clf() 110 | 111 | (refplot,) = plt.plot(wavelength_list, expdata, label="Reference") 112 | 113 | 114 | def cost_function(X): 115 | chi_b, w_p, gamma, beta_0, tau, base, scale = X 116 | sc_nl = NLMaterial([sc_2ndviscosity, chi_b, w_p, gamma, beta_0, tau]) 117 | nb_lam = 50 118 | materials = [15.6816, sc_nl, 14.2129] 119 | stack = [0, 1, 2] 120 | thickness = [0, 105, 0] 121 | thing = NLStructure(materials, stack, thickness, verbose=False) 122 | new_wl = np.linspace(5000, 8000, nb_lam) 123 | R = np.zeros(nb_lam) 124 | for k in range(nb_lam): 125 | _, _, R[k], _ = NLcoefficient(thing, new_wl[k], np.pi * 37 / 180, 1.0) 126 | R = R * scale + base 127 | obj = np.interp(new_wl, wavelength_list, expdata) 128 | cost = np.mean(np.abs(R - obj)) + nb_lam * np.mean( 129 | np.abs(np.diff(R) - np.diff(obj)) 130 | ) 131 | return cost / nb_lam 132 | 133 | 134 | X_min = np.array([11.4, 1e14, 1e12, 1e14, 6e14, 0, 0.01]) 135 | X_max = np.array([11.8, 1e15, 1e13, 1.5e15, 1.2e15, 0.2, 2]) 136 | 137 | best, convergence = PM.QODE(cost_function, 2000, X_min, X_max, progression=10) 138 | chi_b, w_p, gamma, beta_0, tau, base, scale = best 139 | 140 | nSC_2nd = NLMaterial([sc_2ndviscosity, chi_b, w_p, gamma, beta_0, tau]) 141 | optimized_nl = NLStructure([15.6816, nSC_2nd, 14.2129], stack, thickness) 142 | R_optim = [] 143 | for wl in wavelength_list: 144 | _, _, R, _ = NLcoefficient(optimized_nl, wl, theta, pol) 145 | R_optim.append(R) 146 | R = np.array(R_optim) 147 | R = R * scale + base 148 | (optimizedplot,) = plt.plot(wavelength_list, R, label="Optimized NL") 149 | plt.xlabel("Wavelength (nm)") 150 | plt.ylabel("Reflectivity") 151 | 152 | plt.legend() 153 | plt.show() 154 | -------------------------------------------------------------------------------- /tests/test_anisotropic_with_Pyllama.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pyllama as pl 3 | import PyMoosh as PM 4 | import PyMoosh.anisotropic as ani 5 | from numpy import linalg as la_np 6 | 7 | wl_nm = 640 8 | 9 | rot_angle = 0 10 | 11 | rot_axis = "z" 12 | 13 | thickness_nm = 100 14 | 15 | theta_in_rad = np.pi / 6 # angle d'incidence 16 | 17 | perm_1 = 1.2 18 | 19 | perm_2 = 2.2 20 | 21 | 22 | opt_ind = [perm_1, perm_2, perm_2] 23 | mat_1 = ani.AniMaterial(opt_ind, specialType="ANI") 24 | 25 | mat_2 = PM.Material(1.0) 26 | 27 | material_list = [mat_1, mat_2] 28 | stack = [1, 0, 0, 1] 29 | thickness = [0, thickness_nm, 25, 0] # LES EPAISSEUR DU MILIEU EXT A 0 nm 30 | ani_rot_angle = [0.0, 0.5, -0.2, 0.0] 31 | ani_rot_axis = ["z", "x", [0, 1, 1], "z"] 32 | 33 | 34 | structure1 = ani.AniStructure( 35 | material_list, stack, thickness, ani_rot_angle, ani_rot_axis, verbose=False 36 | ) 37 | epsilon1 = structure1.rotate_permittivity_tensor( 38 | np.diag(opt_ind), ani_rot_angle=ani_rot_angle[1], ani_rot_axis=ani_rot_axis[1] 39 | ) 40 | epsilon2 = structure1.rotate_permittivity_tensor( 41 | np.diag(opt_ind), ani_rot_angle=ani_rot_angle[2], ani_rot_axis=ani_rot_axis[2] 42 | ) 43 | n_entry = np.sqrt(1.0) # ATTENTION CHEZ PYMOOSH ON FAIT MATERIAL(PERM) 44 | n_exit = np.sqrt(1.0) 45 | k0 = 2 * np.pi / wl_nm 46 | 47 | thetas = np.linspace(0, 80, 80) * np.pi / 180 48 | l_rpp_moosh = [] 49 | l_rpp_llama = [] 50 | l_rps_moosh = [] 51 | l_rps_llama = [] 52 | l_rsp_moosh = [] 53 | l_rsp_llama = [] 54 | l_rss_moosh = [] 55 | l_rss_llama = [] 56 | l_tpp_moosh = [] 57 | l_tpp_llama = [] 58 | l_tps_moosh = [] 59 | l_tps_llama = [] 60 | l_tsp_moosh = [] 61 | l_tsp_llama = [] 62 | l_tss_moosh = [] 63 | l_tss_llama = [] 64 | 65 | 66 | for theta_in_rad in thetas: 67 | print(theta_in_rad) 68 | Kx = n_entry * np.sin(theta_in_rad) 69 | Kz_entry = n_entry * np.cos(theta_in_rad) 70 | theta_out_rad = np.arcsin((n_entry / n_exit) * np.sin(theta_in_rad)) 71 | Kz_exit = n_exit * np.cos(theta_out_rad) 72 | 73 | epsilon_entry = np.array( 74 | [[n_entry**2, 0, 0], [0, n_entry**2, 0], [0, 0, n_entry**2]] 75 | ) 76 | 77 | epsilon_exit = np.array([[n_exit**2, 0, 0], [0, n_exit**2, 0], [0, 0, n_exit**2]]) 78 | 79 | entry = pl.HalfSpace(epsilon_entry, Kx, Kz_entry, k0) 80 | exit = pl.HalfSpace(epsilon_exit, Kx, Kz_exit, k0) 81 | 82 | Ky = 0 83 | Kz = n_entry * np.cos(theta_in_rad) 84 | my_stack_structure = pl.Structure( 85 | entry, exit, Kx, Ky, Kz_entry, Kz_exit, k0 86 | ) # at this point my_stack_structure represent only two half-spaces 87 | 88 | my_layer = pl.Layer(epsilon1, thickness_nm, Kx, k0) 89 | my_stack_structure.add_layer( 90 | my_layer 91 | ) # remove_layer and replace_layer() also exist 92 | my_layer = pl.Layer(epsilon2, 25, Kx, k0) 93 | my_stack_structure.add_layer(my_layer) 94 | 95 | my_stack_structure.build_scattering_matrix() # scattering matrix 96 | # print("PYLLAMA LAYERS", my_stack_structure.layers[0].D) 97 | # print("PYLLAMA LAYERS", my_stack_structure.layers[0].Q) 98 | 99 | # And we get refl / trans coefficients with 100 | J_refl_lin, J_trans_lin = my_stack_structure.get_fresnel() 101 | l_tpp_llama.append(J_trans_lin[0, 0]) 102 | l_tps_llama.append(J_trans_lin[0, 1]) 103 | l_tsp_llama.append(J_trans_lin[1, 0]) 104 | l_tss_llama.append(J_trans_lin[1, 1]) 105 | l_rpp_llama.append(J_refl_lin[0, 0]) 106 | l_rps_llama.append(J_refl_lin[0, 1]) 107 | l_rsp_llama.append(J_refl_lin[1, 0]) 108 | l_rss_llama.append(J_refl_lin[1, 1]) 109 | 110 | res = ani.coefficients_ani(structure1, wl_nm, theta_in_rad) 111 | l_tpp_moosh.append(res[0]) 112 | l_tps_moosh.append(res[1]) 113 | l_tsp_moosh.append(res[2]) 114 | l_tss_moosh.append(res[3]) 115 | l_rpp_moosh.append(res[4]) 116 | l_rps_moosh.append(res[5]) 117 | l_rsp_moosh.append(res[6]) 118 | l_rss_moosh.append(res[7]) 119 | 120 | import matplotlib.pyplot as plt 121 | 122 | plt.subplot(2, 2, 1) 123 | plt.plot(thetas * 180 / np.pi, l_tpp_llama, label="tpp llama") 124 | plt.plot(thetas * 180 / np.pi, l_tpp_moosh, label="tpp moosh", linestyle="--") 125 | plt.plot(thetas * 180 / np.pi, l_tss_llama, label="tss llama") 126 | plt.plot(thetas * 180 / np.pi, l_tss_moosh, label="tss moosh", linestyle="--") 127 | plt.legend() 128 | 129 | plt.subplot(2, 2, 2) 130 | plt.plot(thetas * 180 / np.pi, l_rpp_llama, label="rpp llama") 131 | plt.plot(thetas * 180 / np.pi, l_rpp_moosh, label="rpp moosh", linestyle="--") 132 | plt.plot(thetas * 180 / np.pi, l_rss_llama, label="rss llama") 133 | plt.plot(thetas * 180 / np.pi, l_rss_moosh, label="rss moosh", linestyle="--") 134 | plt.legend() 135 | 136 | plt.subplot(2, 2, 3) 137 | plt.plot(thetas * 180 / np.pi, l_tsp_llama, label="tsp llama") 138 | plt.plot(thetas * 180 / np.pi, l_tsp_moosh, label="tsp moosh", linestyle="--") 139 | plt.plot(thetas * 180 / np.pi, l_tps_llama, label="tps llama") 140 | plt.plot(thetas * 180 / np.pi, l_tps_moosh, label="tps moosh", linestyle="--") 141 | plt.legend() 142 | 143 | plt.subplot(2, 2, 4) 144 | plt.plot(thetas * 180 / np.pi, l_rsp_llama, label="rsp llama") 145 | plt.plot(thetas * 180 / np.pi, l_rsp_moosh, label="rsp moosh", linestyle="--") 146 | plt.plot(thetas * 180 / np.pi, l_rps_llama, label="rps llama") 147 | plt.plot(thetas * 180 / np.pi, l_rps_moosh, label="rps moosh", linestyle="--") 148 | plt.legend() 149 | plt.show() 150 | 151 | """ 152 | REFL PYLLAMA[[pp, ps],[sp, ss]] [[ 0.16188752-0.04516487j -0.05824699-0.0139216j ] 153 | [ 0.02671249+0.00853741j -0.62690456+0.17740569j]] 154 | TRANS PYLLAMA[[pp, ps],[sp, ss]] [[0.34817659+0.92131957j 0.02982136+0.00448614j] 155 | [0.02982136+0.00448614j 0.20989743+0.72592355j]] 156 | t_pp,t_ps,t_sp,t_ss, ((0.3481765881478537+0.9213195674475575j), (0.029821358152069155+0.004486139750694154j), (0.02982135815206921+0.00448613975069413j), (0.20989742722970214+0.7259235488651471j) 157 | r_pp,r_ps,r_sp,r_ss, (0.1618875201114852-0.04516487426543376j), (-0.05824699196060018-0.013921603350022954j), (0.026712485577587423+0.00853740774923072j), (-0.6269045611049386+0.177405694798719j)) 158 | """ 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyMoosh 2 | 3 | A Python-based Multilayer Optics Optimization and Simulation Hub ! 4 | 5 | 6 | ![PyMoosh logo](pymoosh_logo.png) 7 | 8 | ## A versatile tool for teaching and research in optics 9 | 10 | PyMoosh is a versatile numerical tool written in Python that simulates light interaction with multilayered structures. 11 | 12 | Much like a Swiss Army knife, PyMoosh offers a comprehensive set of functionalities while its code remains easy to understand and modify. Its user-friendly design makes it accessible to first-year students who can quickly compute basic properties like reflection coefficients, while advanced users (researchers) benefit from sophisticated features including complex material models and optimization tools. 13 | 14 | PyMoosh is particularly valuable for research in fields such as plasmonics, photovoltaics, and ellipsometry. The library comes with extensive Jupyter Notebooks that demonstrate all its capabilities and facilitate learning. 15 | 16 | We are continuously developing PyMoosh, adding new features while prioritizing backward compatibility. Our team provides support and welcomes suggestions for improvement from the user community. 17 | 18 | 19 | ## Installation and Setup 20 | 21 | ### Requirements 22 | - **Python 3.10 or higher** is required for RefractiveIndex library integration 23 | - If your Python version is older than this, PyMoosh will be installed without the RefractiveIndex library. 24 | 25 | ### Basic Installation 26 | 27 | **For Python <= 3.10:** 28 | 29 | ```bash 30 | pip install pymoosh 31 | ``` 32 | 33 | **For Python 3.11+:** 34 | 35 | Python 3.11+ requires using virtual environments. For this, you may use either `venv` or [conda](https://docs.conda.io/projects/conda/en/latest/index.html). We recommend the latter. 36 | 37 | ### `venv` installation (Linux mostly) 38 | ```bash 39 | # Create and activate a virtual environment 40 | python -m venv pymoosh-env 41 | # On Linux/macOS: 42 | source pymoosh-env/bin/activate 43 | # On Windows: 44 | pymoosh-env\Scripts\activate 45 | 46 | # Install PyMoosh 47 | pip install pymoosh 48 | ``` 49 | 50 | ### Conda Installation (any OS) 51 | ```bash 52 | # Create and activate environment 53 | conda create -n pymoosh python=X.XX # change for the Python version you want. If not important, 3.11 works fine 54 | conda activate pymoosh # activate the environment 55 | 56 | # Install dependencies first, then PyMoosh 57 | conda install -c conda-forge numpy scipy matplotlib jupyter 58 | pip install pymoosh 59 | ``` 60 | 61 | Remember to activate your environment each time you use PyMoosh. 62 | 63 | ### Local installation 64 | 65 | Maybe you don't want to use `pip`, maybe you want to better control what parts of PyMoosh you use. In this case, you can copy the PyMoosh folder of this repository on your personal computer, and then any script saved in this folder can call PyMoosh directly! 66 | 67 | ## Quick Start Example 68 | 69 | PyMoosh is designed to be simple and intuitive. Here's a minimal working example to get you started: 70 | 71 | ```python 72 | from PyMoosh import * 73 | import matplotlib.pyplot as plt 74 | # Define a simple interface (air/glass) 75 | wl = 600 # wavelength in nm 76 | interface = Structure([1., 2.25], [0, 1], [20*wl, 20*wl]) 77 | # Compute and represent its reflection coefficient 78 | incidence, r, t, R, T = angular(interface, wl, 0, 0., 80., 400) 79 | plt.plot(incidence, R) 80 | plt.savefig('fresnel.png') 81 | # Define the window and the incident beam of light 82 | # Arguments : width, relative position of the beam, resolution in x and y 83 | domain = Window(100*wl, 0.2, 10., 10.) 84 | # Define the incident Beam 85 | # Wavelength, incidence angle, polarization, 86 | beam = Beam(wl, 56.3/180*np.pi, 0, 10*wl) 87 | # Compute the field 88 | E_field = field(interface, beam, domain) 89 | # Visualize the result 90 | plt.figure() 91 | plt.imshow(np.abs(E_field), cmap='jet', extent=[0, domain.width, 0, sum(interface.thickness)]) 92 | plt.axis('off') 93 | plt.savefig('refraction.png') 94 | plt.show() 95 | ``` 96 | 97 | The code above produces these results: 98 | 99 | **Reflection coefficient vs. incidence angle:** 100 | 101 | ![Fresnel reflection coefficient](fresnel.png) 102 | 103 | **Light refraction at an air/glass interface:** 104 | 105 | ![Refraction field visualization](refraction.png) 106 | 107 | With just these few lines, you can calculate and visualize electromagnetic fields and reflection coefficients for multilayered structures. The examples above demonstrate a simple air/glass interface, but PyMoosh can handle complex stacks with many materials and layers. 108 | 109 | 110 | 111 | ## References and Scientific Publications 112 | 113 | ### List of functions 114 | 115 | For a quick and handy list of all functions defined in PyMoosh, with their inputs and outputs, have a look at the `PyMoosh_function_list` pdf on this repository. 116 | 117 | ### Technical Documentation 118 | For a detailed and regularly updated technical description of PyMoosh, see our [arXiv paper](https://arxiv.org/abs/2309.00654). 119 | 120 | ### Tutorial Papers 121 | PyMoosh has been released with a trilogy of tutorial papers, that are open access: 122 | 123 | 1. **Official PyMoosh Presentation Paper** 124 | [Journal of the Optical Society of America B, 41(2), A67-A78 (2024)](https://opg.optica.org/josab/fulltext.cfm?uri=josab-41-2-A67) 125 | 126 | And the [up-to-date version on ArXiv](https://arxiv.org/abs/2309.00654), with minor typo corrections. 127 | 128 | If you use PyMoosh in your research, please cite: 129 | ```bibtex 130 | @article{langevin2024pymoosh, 131 | title={PyMoosh: a comprehensive numerical toolkit for computing the optical properties of multilayered structures}, 132 | author={Langevin, Denis and Bennet, Pauline and Khaireh-Walieh, Abdourahman and Wiecha, Peter and Teytaud, Olivier and Moreau, Antoine}, 133 | journal={Journal of the Optical Society of America B}, 134 | volume={41}, 135 | number={2}, 136 | pages={A67--A78}, 137 | year={2024}, 138 | publisher={Optica Publishing Group} 139 | } 140 | ``` 141 | 142 | 2. **Global Optimization in Nanophotonics Tutorial** 143 | [Journal of the Optical Society of America B, 41(2), A126 (2024)](https://opg.optica.org/josab/fulltext.cfm?uri=josab-41-2-A126) 144 | And a bunch of informative Jupyter notebooks can be found in this [repository](https://github.com/Ellawin/tuto_global_optimization_photonics) 145 | 146 | 3. **Challenges of Deep Learning for Inverse Design** 147 | [Nanophotonics (2023)](https://www.degruyterbrill.com/document/doi/10.1515/nanoph-2023-0527/pdf?licenseType=open-access) 148 | 149 | ### Research Applications 150 | PyMoosh is a research-grade program. PyMoosh and its predecessor Moosh have been used in various research projects: 151 | 152 | - [Comparing evolutionary algorithms and real evolution](https://www.nature.com/articles/s41598-020-68719-3) - The first direct comparison of its kind 153 | - [Universal features of beam reflection by multilayers](https://arxiv.org/abs/1609.08473) - Demonstrating unintuitive optical phenomena 154 | - [Simulating exotic "non-specular" phenomena](https://jeos.edpsciences.org/articles/jeos/pdf/2010/01/jeos20100510025.pdf) 155 | 156 | ## Illustrations 157 | 158 | A punctual source inside a dielectric layer : 159 | 160 | ![A punctual source inside a dielectric layer](field.png) 161 | 162 | ![Excitation of a surface plasmon](spr.png) 163 | 164 | Excitation of a surface plasmon using a prism coupler in the Kretschman Raether configuration. 165 | 166 | ## Contributors 167 | 168 | Here is a list of contributors to PyMoosh (one way or another) so far: 169 | 170 | * Sarah Abdul-salam 171 | * Pauline Bennet (@Ellawin) 172 | * Tristan Berthelot (@Tilmedor) 173 | * Anorld Capo-Chichi 174 | * Pierre Chevalier 175 | * Aidan Costard (@Dratsa) 176 | * Hassan Fawaz (@hbfawaz) 177 | * Denis Langevin (@Milloupe) 178 | * Demetrio Macias 179 | * Téo Mottin (@HawhawPdB) 180 | * Amine Sakkali 181 | * Olivier Teytaud (@teytaud) 182 | * Antoine Vezon 183 | * Peter R. Wiecha 184 | 185 | and the contributors to the original Moosh program should not be forgotten : Josselin Defrance, Rémi Pollès, Fabien Krayzel, Paul-Henri Tichit, Jessica Benedicto mainly, but David R. Smith and Cristian Ciraci too ! Special thanks to Gérard Granet and Jean-Pierre Plumey. 186 | -------------------------------------------------------------------------------- /new_examples/matrix_formalism.py: -------------------------------------------------------------------------------- 1 | import PyMoosh as PM 2 | 3 | # PM.alt_methods DOES NOT WORK at this point (and shouldn't) 4 | # but at least now you can import other methods 5 | import PyMoosh.alt_methods as alt 6 | 7 | material_list = [1.0, 1.5**2, "Water"] 8 | stack = [0, 2, 1, 0] 9 | thickness = [0, 500, 500, 0] 10 | 11 | multilayer = PM.Structure(material_list, stack, thickness) 12 | # Incidence angle 13 | angle_inc = 0.0 14 | # Polarization 15 | pol = 1.0 16 | # Wavelength 17 | wavelength = 2.5 18 | r, t, R, T = PM.coefficient(multilayer, wavelength, angle_inc, pol) 19 | 20 | print(f"Reflection coefficient: {r}, Reflectance coefficient: {R}") 21 | print(f"Transmission coefficient: {t}, Transmittance coefficient: {T}") 22 | 23 | 24 | import numpy as np 25 | 26 | # materials = [1.513**2, 1.455**2, 2.079**2, (1.9+4.8j)**2, 1.0003**2] 27 | materials = [4.0, 1.5**2, 2.0 + 1.0j] 28 | wav = 45.2 29 | 30 | 31 | print("Intermediate incidence:") 32 | incidence = 15 * np.pi / 180 33 | prob = False 34 | 35 | ## Case 1: single layer, TE 36 | # structure = np.random.random(nb_couches*2+1)*w_mean 37 | structure = np.array([1.1]) 38 | 39 | # stack = [0]+[1,2]*nb_couches+[1,0] 40 | stack = [0, 2, 0] 41 | 42 | 43 | epaisseurs = np.concatenate(([0], structure, [0])) 44 | 45 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 46 | r, t, R, T = PM.coefficient_S(chose, wav, incidence, 0) 47 | 48 | r_ab, t_ab, R_ab, T_ab = alt.coefficient_A(chose, wav, incidence, 0) 49 | 50 | r_t, t_t, R_t, T_t = alt.coefficient_T(chose, wav, incidence, 0) 51 | 52 | r_dn, t_dn, R_dn, T_dn = alt.coefficient_DN(chose, wav, incidence, 0) 53 | 54 | r_i, t_i, R_i, T_i = alt.coefficient_I(chose, wav, incidence, 0) 55 | print( 56 | f"single interface and abeles coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_ab)/r), 2)}" 57 | ) 58 | 59 | 60 | print( 61 | f"single interface and TMatrix coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_t)/r), 2)}" 62 | ) 63 | 64 | 65 | print( 66 | f"single interface and Dirichlet_to_Neumann coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_dn)/r), 2)}" 67 | ) 68 | 69 | 70 | print( 71 | f"single interface and Impedance coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_i)/r), 2)}" 72 | ) 73 | 74 | 75 | print( 76 | f"single interface and abeles coeff trans in TE error = {np.format_float_scientific(abs(abs(t-t_ab)/t), 2)}" 77 | ) 78 | 79 | 80 | print( 81 | f"single interface and TMatrix coeff trans in TE error = {np.format_float_scientific(abs(abs(t-t_t)/t), 2)}" 82 | ) 83 | 84 | 85 | print( 86 | f"single interface and Dirichlet_to_Neumann coeff trans in TE error = {np.format_float_scientific(abs(abs(t-t_dn)/t), 2)}" 87 | ) 88 | 89 | print() 90 | 91 | 92 | ## Case 2: single layer, TM 93 | # structure = np.random.random(nb_couches*2+1)*w_mean 94 | structure = np.array([1.1]) 95 | 96 | # stack = [0]+[1,2]*nb_couches+[1,0] 97 | stack = [0, 2, 0] 98 | 99 | 100 | epaisseurs = np.concatenate(([0], structure, [0])) 101 | 102 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 103 | r, t, R, T = PM.coefficient_S(chose, wav, incidence, 1) 104 | 105 | r_ab, t_ab, R_ab, T_ab = alt.coefficient_A(chose, wav, incidence, 1) 106 | 107 | r_t, t_t, R_t, T_t = alt.coefficient_T(chose, wav, incidence, 1) 108 | 109 | r_dn, t_dn, R_dn, T_dn = alt.coefficient_DN(chose, wav, incidence, 1) 110 | 111 | r_i, t_i, R_i, T_i = alt.coefficient_I(chose, wav, incidence, 1) 112 | 113 | print( 114 | f"single interface and abeles coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_ab)/r), 2)}" 115 | ) 116 | 117 | 118 | print( 119 | f"single interface and TMatrix coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_t)/r), 2)}" 120 | ) 121 | 122 | 123 | print( 124 | f"single interface and Dirichlet_to_Neumann coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_dn)/r), 2)}" 125 | ) 126 | 127 | 128 | print( 129 | f"single interface and Impedance coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_i)/r), 2)}" 130 | ) 131 | 132 | 133 | print( 134 | f"single interface and abeles coeff trans in TM error = {np.format_float_scientific(abs(abs(t-t_ab)/t), 2)}" 135 | ) 136 | 137 | 138 | print( 139 | f"single interface and TMatrix coeff trans in TM error = {np.format_float_scientific(abs(abs(t-t_t)/t), 2)}" 140 | ) 141 | 142 | 143 | print( 144 | f"single interface and Dirichlet_to_Neumann coeff trans in TM error = {np.format_float_scientific(abs(abs(t-t_dn)/t), 2)}" 145 | ) 146 | 147 | print() 148 | 149 | 150 | ## Case 3: two layers, TE 151 | # structure = np.random.random(nb_couches*2+1)*w_mean 152 | structure = np.array([1.1, 1.5]) 153 | 154 | # stack = [0]+[1,2]*nb_couches+[1,0] 155 | stack = [0, 2, 1, 0] 156 | 157 | 158 | epaisseurs = np.concatenate(([0], structure, [0])) 159 | 160 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 161 | r, t, R, T = PM.coefficient_S(chose, wav, incidence, 0) 162 | 163 | r_ab, t_ab, R_ab, T_ab = alt.coefficient_A(chose, wav, incidence, 0) 164 | 165 | r_t, t_t, R_t, T_t = alt.coefficient_T(chose, wav, incidence, 0) 166 | 167 | r_dn, t_dn, R_dn, T_dn = alt.coefficient_DN(chose, wav, incidence, 0) 168 | 169 | r_i, t_i, R_i, T_i = alt.coefficient_I(chose, wav, incidence, 0) 170 | 171 | print( 172 | f"two layers and abeles coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_ab)/r), 2)}" 173 | ) 174 | 175 | 176 | print( 177 | f"two layers and TMatrix coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_t)/r), 2)}" 178 | ) 179 | 180 | 181 | print( 182 | f"two layers and Dirichlet_to_Neumann coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_dn)/r), 2)}" 183 | ) 184 | 185 | 186 | print( 187 | f"two layers and Impedance coeff refl in TE error = {np.format_float_scientific(abs(abs(r-r_i)/r), 2)}" 188 | ) 189 | 190 | 191 | print( 192 | f"two layers and abeles coeff trans in TE error = {np.format_float_scientific(abs(abs(t-t_ab)/t), 2)}" 193 | ) 194 | 195 | 196 | print( 197 | f"two layers and TMatrix coeff trans in TE error = {np.format_float_scientific(abs(abs(t-t_t)/t), 2)}" 198 | ) 199 | 200 | 201 | print( 202 | f"two layers and Dirichlet_to_Neumann coeff trans in TE error = {np.format_float_scientific(abs(abs(t-t_dn)/t), 2)}" 203 | ) 204 | 205 | print( 206 | f"two layers and Impedance coeff trans in TE error = {np.format_float_scientific(abs(abs(t-t_i)/t), 2)}" 207 | ) 208 | 209 | print() 210 | 211 | 212 | ## Case 4: two layers, TM 213 | # structure = np.random.random(nb_couches*2+1)*w_mean 214 | structure = np.array([1.1, 1.5]) 215 | 216 | # stack = [0]+[1,2]*nb_couches+[1,0] 217 | stack = [0, 2, 1, 0] 218 | 219 | 220 | epaisseurs = np.concatenate(([0], structure, [0])) 221 | 222 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 223 | r, t, R, T = PM.coefficient_S(chose, wav, incidence, 1) 224 | 225 | r_ab, t_ab, R_ab, T_ab = alt.coefficient_A(chose, wav, incidence, 1) 226 | 227 | r_t, t_t, R_t, T_t = alt.coefficient_T(chose, wav, incidence, 1) 228 | 229 | r_dn, t_dn, R_dn, T_dn = alt.coefficient_DN(chose, wav, incidence, 1) 230 | 231 | r_i, t_i, R_i, T_i = alt.coefficient_I(chose, wav, incidence, 1) 232 | 233 | 234 | print( 235 | f"two layers and abeles coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_ab)/r), 2)}" 236 | ) 237 | 238 | 239 | print( 240 | f"two layers and TMatrix coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_t)/r), 2)}" 241 | ) 242 | 243 | 244 | print( 245 | f"two layers and Dirichlet_to_Neumann coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_dn)/r), 2)}" 246 | ) 247 | 248 | 249 | print( 250 | f"two layers and Impedance coeff refl in TM error = {np.format_float_scientific(abs(abs(r-r_i)/r), 2)}" 251 | ) 252 | 253 | 254 | print( 255 | f"two layers and abeles coeff trans in TM error = {np.format_float_scientific(abs(abs(t-t_ab)/t), 2)}" 256 | ) 257 | 258 | 259 | print( 260 | f"two layers and TMatrix coeff trans in TM error = {np.format_float_scientific(abs(abs(t-t_t)/t), 2)}" 261 | ) 262 | 263 | 264 | print( 265 | f"two layers and Dirichlet_to_Neumann coeff trans in TM error = {np.format_float_scientific(abs(abs(t-t_dn)/t), 2)}" 266 | ) 267 | 268 | print( 269 | f"two layers and Impedance coeff trans in TM error = {np.format_float_scientific(abs(abs(t-t_i)/t), 2)}" 270 | ) 271 | 272 | print() 273 | -------------------------------------------------------------------------------- /PyMoosh/green.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains all functions linked to computing green functions 3 | """ 4 | 5 | from PyMoosh.core import cascade 6 | from PyMoosh.classes import conv_to_nm 7 | import numpy as np 8 | 9 | 10 | def green(struct, window, lam, source_interface): 11 | """ 12 | Computes the electric (TE polarization) field inside 13 | a multilayered structure illuminated by punctual source placed inside 14 | the structure. 15 | 16 | Args: 17 | struct (Structure): description (materials,thicknesses)of the multilayer 18 | window (Window): description of the simulation domain 19 | lam (float): wavelength in vacuum 20 | source_interface (int): # of the interface where the source is located. 21 | The medium should be the same on both sides. 22 | Returns: 23 | En (np.array): a matrix with the complex amplitude of the field 24 | 25 | Afterwards the matrix may be used to represent either the modulus or the 26 | real part of the field. 27 | """ 28 | 29 | # Computation of all the permittivities/permeabilities 30 | if struct.unit != "nm": 31 | wavelength = conv_to_nm(wavelength, struct.unit) 32 | Epsilon, Mu = struct.polarizability(lam) 33 | thickness = np.array(struct.thickness) 34 | pol = 0 35 | d = window.width 36 | C = window.C 37 | ny = np.floor(thickness / window.py) 38 | nx = window.nx 39 | Type = struct.layer_type 40 | print("Pixels vertically:", int(sum(ny))) 41 | 42 | # Check it's ready for the Green function : 43 | # Type of the layer is supposed to be the same 44 | # on both sides of the Interface 45 | 46 | if Type[source_interface - 1] != Type[source_interface]: 47 | print( 48 | "Error: there should be the same material on both sides " 49 | + "of the interface where the source is located." 50 | ) 51 | return 0 52 | 53 | # Number of modes retained for the description of the field 54 | # so that the last mode has an amplitude < 1e-3 - you may want 55 | # to change it if the structure present reflexion coefficients 56 | # that are subject to very swift changes with the angle of incidence. 57 | 58 | # nmod = int(np.floor(0.83660 * d / w)) 59 | nmod = 100 60 | 61 | # ----------- Do not touch this part --------------- 62 | l = lam / d 63 | thickness = thickness / d 64 | 65 | if pol == 0: 66 | f = Mu 67 | else: 68 | f = Epsilon 69 | # Wavevector in vacuum, no dimension 70 | k_0 = 2 * np.pi / l 71 | # Initialization of the field component 72 | En = np.zeros((int(sum(ny)), int(nx))) 73 | # Total number of layers 74 | # g=Type.size-1 75 | g = len(struct.layer_type) - 1 76 | 77 | # Scattering matrix corresponding to no interface. 78 | T = np.zeros((2 * g + 2, 2, 2), dtype=complex) 79 | T[0] = [[0, 1], [1, 0]] 80 | for nm in np.arange(2 * nmod + 1): 81 | # horizontal wavevector 82 | alpha = 2 * np.pi * (nm - nmod) 83 | gamma = np.sqrt(Epsilon[Type] * Mu[Type] * k_0**2 - np.ones(g + 1) * alpha**2) 84 | 85 | if np.real(Epsilon[Type[0]]) < 0 and np.real(Mu[Type[0]]) < 0: 86 | gamma[0] = -gamma[0] 87 | 88 | if g > 2: 89 | gamma[1 : g - 1] = gamma[1 : g - 1] * ( 90 | 1 - 2 * (np.imag(gamma[1 : g - 1]) < 0) 91 | ) 92 | if ( 93 | np.real(Epsilon[Type[g]]) < 0 94 | and np.real(Mu[Type[g]]) < 0 95 | and np.real(np.sqrt(Epsilon[Type[g]] * k_0**2 - alpha**2)) != 0 96 | ): 97 | gamma[g] = -np.sqrt(Epsilon[Type[g]] * Mu[Type[g]] * k_0**2 - alpha**2) 98 | else: 99 | gamma[g] = np.sqrt(Epsilon[Type[g]] * Mu[Type[g]] * k_0**2 - alpha**2) 100 | 101 | gf = gamma / f[Type] 102 | for k in range(g): 103 | t = np.exp(1j * gamma[k] * thickness[k]) 104 | T[2 * k + 1] = np.array([[0, t], [t, 0]]) 105 | b1 = gf[k] 106 | b2 = gf[k + 1] 107 | T[2 * k + 2] = np.array([[b1 - b2, 2 * b2], [2 * b1, b2 - b1]]) / (b1 + b2) 108 | t = np.exp(1j * gamma[g] * thickness[g]) 109 | T[2 * g + 1] = np.array([[0, t], [t, 0]]) 110 | 111 | Ampl = np.zeros((2 * g + 2, 2), dtype=complex) 112 | 113 | # -----------------------------> Above the source 114 | # calculation of the scattering matrices above the source (*_up) 115 | H_up = np.zeros((2 * source_interface, 2, 2), dtype=complex) 116 | A_up = np.zeros((2 * source_interface, 2, 2), dtype=complex) 117 | 118 | # T[2*source_interface] should be a neutral matrix for cascading 119 | # if the two media are the same on each side of the source. 120 | 121 | H_up[0] = [[0, 1], [1, 0]] 122 | A_up[0] = [[0, 1], [1, 0]] 123 | 124 | for k in range(2 * source_interface - 1): 125 | A_up[k + 1] = cascade(A_up[k], T[k + 1]) 126 | H_up[k + 1] = cascade(T[2 * source_interface - 1 - k], H_up[k]) 127 | 128 | I_up = np.zeros((2 * source_interface, 2, 2), dtype=complex) 129 | for k in range(2 * source_interface - 1): 130 | I_up[k] = np.array( 131 | [ 132 | [ 133 | A_up[k][1, 0], 134 | A_up[k][1, 1] * H_up[2 * source_interface - 1 - k][0, 1], 135 | ], 136 | [ 137 | A_up[k][1, 0] * H_up[2 * source_interface - 1 - k][0, 0], 138 | H_up[2 * source_interface - 1 - k][0, 1], 139 | ], 140 | ] 141 | / (1 - A_up[k][1, 1] * H_up[2 * source_interface - 1 - k][0, 0]) 142 | ) 143 | 144 | # ----------------------------> Below the source 145 | # Calculation of the scattering matrices below the source (*_d) 146 | 147 | H_d = np.zeros((-2 * source_interface + 2 * g + 2, 2, 2), dtype=complex) 148 | A_d = np.zeros((-2 * source_interface + 2 * g + 2, 2, 2), dtype=complex) 149 | 150 | H_d[0] = [[0, 1], [1, 0]] 151 | A_d[0] = [[0, 1], [1, 0]] 152 | 153 | for k in range(2 * g + 1 - 2 * source_interface): 154 | A_d[k + 1] = cascade(A_d[k], T[2 * source_interface + k + 1]) 155 | H_d[k + 1] = cascade(T[2 * g + 1 - k], H_d[k]) 156 | 157 | I_d = np.zeros((-2 * source_interface + 2 * g + 2, 2, 2), dtype=complex) 158 | for k in range(2 * g + 1 - 2 * source_interface): 159 | I_d[k] = np.array( 160 | [ 161 | [ 162 | A_d[k][1, 0], 163 | A_d[k][1, 1] * H_d[2 * (g - source_interface) + 1 - k][0, 1], 164 | ], 165 | [ 166 | A_d[k][1, 0] * H_d[2 * (g - source_interface) + 1 - k][0, 0], 167 | H_d[2 * (g - source_interface) + 1 - k][0, 1], 168 | ], 169 | ] 170 | / (1 - A_d[k][1, 1] * H_d[2 * (g - source_interface) + 1 - k][0, 0]) 171 | ) 172 | 173 | # >>> Inside the layer containing the source <<< 174 | 175 | r_up = A_up[2 * source_interface - 1][1, 1] 176 | r_d = H_d[2 * g + 1 - 2 * source_interface][0, 0] 177 | ex = -1j * np.exp(1j * alpha * window.C) 178 | # Multiply by -omega µ_0 179 | M = ( 180 | ex 181 | / (1 - r_up + (1 + r_up) * (1 - r_d) / (1 + r_d)) 182 | / gamma[source_interface] 183 | ) 184 | D = ( 185 | ex 186 | / (1 - r_d + (1 - r_up) / (1 + r_up) * (1 + r_d)) 187 | / gamma[source_interface] 188 | ) 189 | 190 | # Starting with the intermediary matrices, compute the right coefficients 191 | 192 | Ampl = np.zeros(2 * g + 2, dtype=complex) 193 | for k in range(source_interface): 194 | # Celui qui descend. 195 | Ampl[2 * k] = I_up[2 * k][0, 1] * M 196 | # Celui qui monte. 197 | Ampl[2 * k + 1] = I_up[2 * k + 1][1, 1] * M 198 | 199 | for k in range(source_interface, g + 1): 200 | Ampl[2 * k] = I_d[2 * (k - source_interface)][0, 0] * D 201 | Ampl[2 * k + 1] = I_d[2 * (k - source_interface) + 1][1, 0] * D 202 | 203 | Ampl[2 * source_interface - 1] = M 204 | Ampl[2 * source_interface] = D 205 | 206 | # >>> Calculation of the fields <<< 207 | 208 | h = 0 209 | t = 0 210 | E = np.zeros((int(np.sum(ny)), 1), dtype=complex) 211 | 212 | for k in range(g + 1): 213 | for m in range(int(ny[k])): 214 | h = h + float(thickness[k]) / ny[k] 215 | 216 | E[t, 0] = Ampl[2 * k] * np.exp(1j * gamma[k] * h) + Ampl[ 217 | 2 * k + 1 218 | ] * np.exp(1j * gamma[k] * (thickness[k] - h)) 219 | t += 1 220 | h = 0 221 | E = E * np.exp(1j * alpha * np.arange(0, nx) / nx) 222 | En = En + E 223 | 224 | return En 225 | 226 | 227 | # return r_up,r_d 228 | -------------------------------------------------------------------------------- /notebooks/In-depth_examples/How_materials_works.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "d2f7ff7f", 6 | "metadata": {}, 7 | "source": [ 8 | "# Materials\n", 9 | "\n", 10 | "In PyMoosh, the part dealing with materials is a bit specific, to manage all the data necessary for the computations. \n", 11 | "\n", 12 | "Here we show a little bit how this works and what you can do with it. \n", 13 | "\n", 14 | "In order to make things simple for the user, when a Structure is defined the informations which are given are used to generate an object which belongs to the `Material` class." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 13, 20 | "id": "1d54b3d8", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "from PyMoosh import *\n", 25 | "\n", 26 | "air = Material(1.)" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "id": "742a718a", 32 | "metadata": {}, 33 | "source": [ 34 | "`air`is thus a material, which means you can access its permittivity and permeability easily..." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 14, 40 | "id": "f4f14bdb", 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "name": "stdout", 45 | "output_type": "stream", 46 | "text": [ 47 | "epsilon = (1+0j) \n", 48 | "mu = 1.0\n" 49 | ] 50 | } 51 | ], 52 | "source": [ 53 | "# Wavelength in nanometer\n", 54 | "wavelength = 600\n", 55 | "epsilon = air.get_permittivity(wavelength)\n", 56 | "mu = air.get_permeability(wavelength)\n", 57 | "print(\"epsilon = \",epsilon,\"\\nmu =\",mu)" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "id": "f8c8e123", 63 | "metadata": {}, 64 | "source": [ 65 | "This was easy. Now we can declare another material but may want to specify its permeability, because it is different from one. It is still a non-dispersive material which we could call `metamaterial` for instance. It has a magnetic response, but it is not dispersive.\n", 66 | "\n", 67 | "To do so, simply give two values to `Material` : `(epsilon, mu)`" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 15, 73 | "id": "b4e23fcd", 74 | "metadata": {}, 75 | "outputs": [ 76 | { 77 | "name": "stdout", 78 | "output_type": "stream", 79 | "text": [ 80 | "epsilon = -1.0 \n", 81 | "mu = -2.0\n" 82 | ] 83 | } 84 | ], 85 | "source": [ 86 | "metamaterial = Material([-1.,-2.])\n", 87 | "epsilon = metamaterial.get_permittivity(wavelength)\n", 88 | "mu = metamaterial.get_permeability(wavelength)\n", 89 | "print(\"epsilon = \",epsilon,\"\\nmu =\",mu)" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "id": "94ab6142", 95 | "metadata": {}, 96 | "source": [ 97 | "Now, you may want to be able to define a material, like an ideal metal which has a permittivity described by a simple Drude model. What is important, is that the permittivity can be defined by a function that only depends on the wavelength." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 16, 103 | "id": "6d37b149", 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "def drude(wavelength):\n", 108 | " return 1 - wavelength**2 / 125 **2" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "id": "2322157c", 114 | "metadata": {}, 115 | "source": [ 116 | "And then define a material by giving a function as an argument to `Material`. You can check that in this case the material is dispersive. " 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 17, 122 | "id": "775fa323", 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "name": "stdout", 127 | "output_type": "stream", 128 | "text": [ 129 | "epsilon = -9.24 \n", 130 | "mu = 1.0\n" 131 | ] 132 | } 133 | ], 134 | "source": [ 135 | "metal = Material(drude)\n", 136 | "wavelength = 400\n", 137 | "epsilon = metal.get_permittivity(wavelength)\n", 138 | "mu = metal.get_permeability(wavelength)\n", 139 | "print(\"epsilon = \",epsilon,\"\\nmu =\",mu)" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "id": "b8bac440", 145 | "metadata": {}, 146 | "source": [ 147 | "These types, in addition to the simple access to the local database, are the default types for the Material class.\n", 148 | "\n", 149 | "**Any of those materials can be provided to declare a class `Structure`, it will be used as is.**" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "id": "54ce10b6", 155 | "metadata": {}, 156 | "source": [ 157 | "# Models\n", 158 | "\n", 159 | "There are several cases where you want to have a more complex function to define your material's permittivity.\n", 160 | "\n", 161 | "Many typical such functions (usual models like Drude or Brendel&Bormann, but also experimental data interpolation) can be found in the `model` module.\n", 162 | "\n", 163 | "The difference with the simple `drude` functions used above, is now that these functions can take extra parameters.\n", 164 | "\n", 165 | "The syntax now is `Material([function, params], specialType=\"Model\")`" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 18, 171 | "id": "78880515", 172 | "metadata": {}, 173 | "outputs": [ 174 | { 175 | "name": "stdout", 176 | "output_type": "stream", 177 | "text": [ 178 | "The interpolation provides : \n", 179 | "epsilon = -15.000391796755434 \n", 180 | "mu = 1.0\n", 181 | "Drude model provides : epsilon = -15.0\n" 182 | ] 183 | } 184 | ], 185 | "source": [ 186 | "import numpy as np \n", 187 | "wavelength_list = np.linspace(300,800,100)\n", 188 | "# Let us use our Drude model declared above to generate the corresponding permittivities :\n", 189 | "permittivities = drude(wavelength_list)\n", 190 | "\n", 191 | "from PyMoosh.models import ExpData\n", 192 | "# You can import it directly if you know how it works, or copy/paste the function to your code\n", 193 | "\n", 194 | "metal2 = Material([ExpData, wavelength_list, permittivities], specialType=\"Model\")\n", 195 | "\n", 196 | "wavelength = 500\n", 197 | "epsilon = metal2.get_permittivity(wavelength)\n", 198 | "mu = metal2.get_permeability(wavelength)\n", 199 | "print(\"The interpolation provides : \\nepsilon = \",epsilon,\"\\nmu =\",mu)\n", 200 | "print(\"Drude model provides : epsilon =\",metal.get_permittivity(500))" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "id": "2fe61585", 206 | "metadata": {}, 207 | "source": [ 208 | "As you can see, the values are not exactly the same. First for experimental data, it is always assumed epsilon is complex. Then, it is an interpolation between experimental points, so this is not as accurate as an analytic formula." 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "id": "3e8ed7ab", 214 | "metadata": {}, 215 | "source": [ 216 | "## Importing files\n", 217 | "\n", 218 | "It is very common to have a file with experimental data of optical indices that you would want to use for your structure.\n", 219 | "\n", 220 | "This is now directly feasible in PyMoosh:\n", 221 | "` mat_from_file = Material(\"file_name\", specialType=\"File\") `\n", 222 | "\n", 223 | "Your file has to be in three columns: lambdas (nm), n, and k" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "id": "f9ba3f7a", 229 | "metadata": {}, 230 | "source": [ 231 | "# Refractive Index Database integration\n", 232 | "\n", 233 | "Now, one massively used source of permittivity data is the RefractiveIndex.info database. PyMoosh can interact with it now!\n", 234 | "\n", 235 | "Materials in the RII database are stored on shelves (general type of material), then books (material), then page (source of refractive index for this material (experiment/model...).)\n", 236 | "\n", 237 | "So the syntax is now `Material([\"shelf\", \"book\", \"page\"], specialType=\"RII\")`" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 20, 243 | "id": "7730e743", 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "name": "stdout", 248 | "output_type": "stream", 249 | "text": [ 250 | "epsilon = 2.0186092372978477\n" 251 | ] 252 | } 253 | ], 254 | "source": [ 255 | "# Example for SiO2\n", 256 | "\n", 257 | "shelf = \"main\"\n", 258 | "book = \"SiO2\"\n", 259 | "page = \"Nyakuchena\" #latest to date\n", 260 | "\n", 261 | "silica = Material([shelf, book, page], specialType=\"RII\")\n", 262 | "epsilon = silica.get_permittivity(1500)\n", 263 | "# Important: The RII database will give off errors if the wavelength is not within\n", 264 | "# the stored range (no extrapolation is allowed)\n", 265 | "print(\"epsilon = \",epsilon)\n" 266 | ] 267 | } 268 | ], 269 | "metadata": { 270 | "kernelspec": { 271 | "display_name": "Python 3", 272 | "language": "python", 273 | "name": "python3" 274 | }, 275 | "language_info": { 276 | "codemirror_mode": { 277 | "name": "ipython", 278 | "version": 3 279 | }, 280 | "file_extension": ".py", 281 | "mimetype": "text/x-python", 282 | "name": "python", 283 | "nbconvert_exporter": "python", 284 | "pygments_lexer": "ipython3", 285 | "version": "3.10.12" 286 | }, 287 | "vscode": { 288 | "interpreter": { 289 | "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" 290 | } 291 | } 292 | }, 293 | "nbformat": 4, 294 | "nbformat_minor": 5 295 | } 296 | -------------------------------------------------------------------------------- /PyMoosh/data/material_data.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "BK7": 4 | { 5 | "model":"ExpData", 6 | "wavelength_list":[190.75, 193.73, 196.8, 199.98, 203.25, 206.64, 210.14, 213.77, 217.52, 221.4, 225.43, 229.6, 233.93, 238.43, 243.11, 247.97, 253.03, 258.3, 263.8, 269.53, 275.52, 281.78, 288.34, 295.2, 302.4, 309.96, 317.91, 326.28, 335.1, 344.4, 354.24, 364.66, 375.71, 387.45, 399.95, 413.28, 427.54, 442.8, 459.2, 476.87, 495.94, 516.6, 539.0700000000001, 563.5700000000001, 590.41, 619.9299999999999, 652.55, 688.8099999999999, 729.3200000000001, 774.91, 826.5700000000001, 885.61, 953.73, 1033.21, 1127.14, 1239.85], 7 | "permittivities":[2.8406742849, 2.804261715649, 2.770563579001, 2.739329528464, 2.710376127684, 2.683571461921, 2.658732435844, 2.635706792196, 2.614339739664, 2.594480126121, 2.575996110081, 2.558755351321, 2.542647106624, 2.527572147556, 2.513432915161, 2.500146004225, 2.487638700625, 2.475842457361, 2.464692764356, 2.4541415649, 2.444132010384, 2.434623467584, 2.425575745476, 2.416958387649, 2.4087350401, 2.400878973529, 2.393369890704, 2.386181504529, 2.379296995009, 2.372696606736, 2.366369966601, 2.360300650929, 2.354472356041, 2.348881151236, 2.343513969316, 2.3383608889, 2.333415112704, 2.328666844009, 2.324109397009, 2.319733086489, 2.315534369344, 2.311503651769, 2.307628351744, 2.303905051044, 2.300324289124, 2.296867553764, 2.2935285136, 2.290285730161, 2.287120856329, 2.284009554436, 2.2809154729, 2.277793303696, 2.274585797929, 2.271202716601, 2.267514953929, 2.263330686969] 8 | }, 9 | 10 | "Water": 11 | { 12 | "model":"ExpData", 13 | "wavelength_list":[404.7, 435.8, 467.8, 480, 508.5, 546.1, 577, 579.1, 14 | 589.1, 643.8, 700, 750, 800, 850, 900, 950, 1000, 1050, 1100], 15 | "permittivities":[1.8056640625, 1.7988442641, 1.7932691569, 1.7914216336, 1.7875957401, 1.7833999936, 1.7804632356, 1.7802764329, 16 | 1.7794226025, 1.7753164081, 1.7718005881, 1.766241, 1.763584, 1.760929, 1.763584, 1.760929, 1.758276, 1.755625, 1.752976] 17 | }, 18 | 19 | "SiA": 20 | { 21 | "model":"ExpData", 22 | "wavelength_list":[103.3, 107.8, 112.7, 118.1, 124, 130.5, 137.8, 145.9, 155, 165.3, 177.1, 190.7, 206.6, 225.4, 248, 258.3, 269.5, 281.8, 295.2, 310, 326.3, 344.4, 354.3, 364.7, 387.5, 413.3, 442.8, 476.9, 496, 516.6, 563.6, 619.9, 652.6, 688.8, 729.3, 774.9, 826.6, 885.6, 953.8, 1033, 1127, 1240, 1378, 1550, 1771, 2066], 23 | "permittivities":[-0.420147, -0.5856399999999999, -0.7412519999999999, -0.9026710000000001, -1.088919, -1.290591, -1.527651, -1.804491, -2.124400000000001, -2.487375, -2.879876, -3.380498999999999, -3.966299999999999, -4.477599999999999, -4.761499999999999, -4.6629, -4.300000000000002, -3.650100000000002, -2.688, -1.1267, 1.3041, 4.3081, 6.1288, 8.1344, 11.7245, 15.104, 17.2913, 18.5217, 18.7265, 18.952639, 18.5335, 17.68037900000001, 17.257131, 16.654659, 16.040499, 15.426404, 14.89300656, 14.21129199, 13.5424, 13.0321, 12.7449, 12.5316, 12.25, 12.1104, 11.9025, 11.8336], 24 | "permittivities_imag":[0.474804, 0.614922, 0.741664, 0.87984, 1.04652, 1.23256, 1.4661, 1.75518, 2.112, 2.5578, 3.14496, 3.937139999999999, 5.0616, 6.777, 9.3288, 10.602, 12.1302, 13.754, 15.5648, 17.5644, 19.26, 20.448, 20.8134, 25 | 20.748, 19.8492, 17.6952, 14.6616, 11.4944, 10.0128, 8.64348, 6.0168, 3.900060000000001, 3.02742, 2.21678, 1.59598, 1.06896, 0.626864, 0.302354, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 26 | }, 27 | 28 | "Silver": 29 | { 30 | "model":"BrendelBormann", 31 | "f0": 0.821, 32 | "Gamma0": 0.049, 33 | "omega_p": 9.01, 34 | "f": [0.050, 0.133, 0.051, 0.467, 4.000], 35 | "Gamma": [0.189, 0.067, 0.019, 0.117, 0.052], 36 | "omega": [2.025, 5.185, 4.343, 9.809, 18.56], 37 | "sigma": [1.894, 0.665, 0.189, 1.170, 0.516], 38 | "info": "Rakic et al., APPLIED OPTICS Vol. 37, No. 22, P. 5271 https://cdn.optiwave.com/wp-content/uploads/2015/06/4.pdf" 39 | }, 40 | 41 | "Aluminium": 42 | { 43 | "model":"BrendelBormann", 44 | "f0": 0.526, 45 | "Gamma0": 0.047, 46 | "omega_p": 14.98, 47 | "f": [0.213, 0.060, 0.182, 0.014], 48 | "Gamma": [0.312, 0.315, 1.587, 2.145], 49 | "omega": [0.163, 1.561, 1.827, 4.495], 50 | "sigma": [0.013, 0.042, 0.256, 1.735], 51 | "info": "Rakic et al., APPLIED OPTICS Vol. 37, No. 22, P. 5271 https://cdn.optiwave.com/wp-content/uploads/2015/06/4.pdf" 52 | }, 53 | "Gold": 54 | { 55 | "model":"BrendelBormann", 56 | "f0" : 0.770, 57 | "Gamma0" : 0.050, 58 | "omega_p" : 9.03, 59 | "f" : [0.054, 0.050, 0.312, 0.719, 1.648], 60 | "Gamma" : [0.074, 0.035, 0.083, 0.125, 0.179], 61 | "omega" : [0.218, 2.885, 4.069, 6.137, 27.97], 62 | "sigma" : [0.742, 0.349, 0.830, 1.246, 1.795], 63 | "info": "Rakic et al., APPLIED OPTICS Vol. 37, No. 22, P. 5271 https://cdn.optiwave.com/wp-content/uploads/2015/06/4.pdf" 64 | }, 65 | "Nickel": 66 | { 67 | "model":"BrendelBormann", 68 | "f0" : 0.083, 69 | "Gamma0" : 0.022, 70 | "omega_p" : 15.92, 71 | "f" : [0.357, 0.039, 0.127, 0.654], 72 | "Gamma" : [2.820, 0.120, 1.822, 6.637], 73 | "omega" : [0.317, 1.059, 4.583, 8.825], 74 | "sigma" : [0.606, 1.454, 0.379, 0.510], 75 | "info": "Rakic et al., APPLIED OPTICS Vol. 37, No. 22, P. 5271 https://cdn.optiwave.com/wp-content/uploads/2015/06/4.pdf" 76 | }, 77 | 78 | "Platinum": 79 | { 80 | "model":"BrendelBormann", 81 | "f0" : 0.333, 82 | "Gamma0" : 0.080, 83 | "omega_p" : 9.59, 84 | "f" : [0.186, 0.665, 0.551, 2.214], 85 | "Gamma" : [0.498, 1.851, 2.604, 2.891], 86 | "omega" : [0.782, 1.317, 3.189, 8.236], 87 | "sigma" : [0.031, 0.096, 0.766, 1.146], 88 | "info": "Rakic et al., APPLIED OPTICS Vol. 37, No. 22, P. 5271 https://cdn.optiwave.com/wp-content/uploads/2015/06/4.pdf" 89 | }, 90 | 91 | "Copper": 92 | { 93 | "model":"BrendelBormann", 94 | "f0" : 0.562, 95 | "Gamma0" : 0.03, 96 | "omega_p" : 10.83, 97 | "f" : [0.076, 0.081, 0.324, 0.726], 98 | "Gamma" : [0.056, 0.047, 0.113, 0.172], 99 | "omega" : [0.416, 2.849, 4.819, 8.136], 100 | "sigma" : [0.562, 0.469, 1.131, 1.719], 101 | "info": "Rakic et al., APPLIED OPTICS Vol. 37, No. 22, P. 5271 https://cdn.optiwave.com/wp-content/uploads/2015/06/4.pdf" 102 | }, 103 | 104 | 105 | "Si": 106 | 107 | { 108 | "model":"ExpData", 109 | "wavelength_list" : [250, 260, 270, 280, 290, 300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420, 430, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, 750, 760, 770, 780, 790, 800, 810, 820, 830, 840, 850, 860, 870, 880, 890, 900, 910, 920, 930, 940, 950, 960, 970, 980, 990, 1000, 1010, 1020, 1030, 1040, 1050, 1060, 1070, 1080, 1090, 1100, 1110, 1120, 1130, 1140, 1150, 1160, 1170, 1180, 1190, 1200, 1210, 1220, 1230, 1240, 1250, 1260, 1270, 1280, 1290, 1300, 1310, 1320, 1330, 1340, 1350, 1360, 1370, 1380, 1390, 1400, 1410, 1420, 1430, 1440, 1450], 110 | "permittivities":[-10.20043421, -12.92847724, -17.00047764, -18.93385521, -10.70736876, 7.088301000000005, 12.78562979, 15.08307524, 16.79848275, 18.76440171, 20.95362816, 26.48691375, 41.93151591000001, 42.4203326799, 36.0475355136, 31.5116768871, 28.4680697871, 26.0807922364, 24.3056737244, 22.9315537599, 21.827835088956, 20.896926398975, 20.1102913424, 19.462165926511, 18.9109054839, 18.393164674236, 17.933303843439, 17.529406300039, 17.179813681584, 16.833715229184, 16.588542870556, 16.304739044399, 16.047472127484, 15.816049565184, 15.633712954224, 15.452417972559, 15.272166195951, 15.116261457519, 14.968896352176, 14.829985115751, 14.707016284191, 14.569303822336, 14.477860980751, 14.371535917975, 14.258047925511, 14.175112151871, 14.084909778479, 13.99499388577775, 13.91282376614656, 13.83089438502076, 13.77888600263664, 13.69735067232636, 13.63820536869084, 13.57181837649756, 13.52029663755456, 13.46153327450975, 13.410220341504, 13.35900489733104, 13.29329899674775, 13.25686669042416, 13.22048402493975, 13.16237403777031, 13.11887576482191, 13.08268223947999, 13.05376349440704, 13.03209555127536, 12.98881244149504, 12.94560118196631, 12.93840682230951, 12.88809829410279, 12.84505470199551, 12.8450550348313, 12.80208329339164, 12.83072349114604, 12.8092406443309, 12.78062475970396, 12.75918384305726, 12.7306239011715, 12.70922494216937, 12.68784396774743, 12.66648098298802, 12.645135991453, 12.62380899540045, 12.5954009972785, 12.58120899841835, 12.56702499908693, 12.54576399947499, 12.53159999970868, 12.51036899984669, 12.48915599992568, 12.48208899996765, 12.46089999998825, 12.43972899999689, 12.43267599999969, 12.41857599999995, 12.40448399999998, 12.39039999999999, 12.376324, 12.369289, 12.355225, 12.341169, 12.334144, 12.313081, 12.313081, 12.292036, 12.285025, 12.271009, 12.264004, 12.257001, 12.25, 12.243001, 12.229009, 12.222016, 12.222016, 12.222016, 12.201049, 12.194064, 12.194064, 12.1801, 12.166144, 12.159169], 111 | "permittivities_imag":[11.7500586, 13.8723768, 18.658948, 29.513848, 44.464788, 43.32042000000001, 36.8985498, 33.382008, 31.770245, 31.022077, 31.5379988, 34.460878, 29.193692, 12.96308426, 6.062114, 3.66923242, 2.57724614, 1.8083268, 1.35787824, 1.0730558, 0.8912532520000001, 0.7233361199999999, 0.6300528, 0.527825208, 0.46803938, 0.416393276, 0.3712485700000001, 0.331032594, 0.28852516, 0.245326576, 0.228397548, 0.214425876, 0.190252952, 0.174160784, 0.158761008, 0.145612102, 0.134880712, 0.130706784, 0.125881784, 0.113165486, 0.11080849, 0.103883472, 0.09746127, 0.09132519, 0.08546598399999999, 0.07999119, 0.074767266, 0.069833247, 0.065134752, 0.0607074684, 0.0565382144, 0.05251126840000001, 0.0487874844, 0.04519383840000001, 0.0418354352, 0.038638239, 0.035623936, 0.032775116, 0.030068562, 0.0275463496, 0.025164756, 0.0229021128, 0.0207881068, 0.0188091234, 0.0169550864, 0.015228424, 0.0135971712, 0.0120799252, 0.0106161858, 0.009377798, 0.008166502400000001, 0.00704205824, 0.006015333600000001, 0.00511036776, 0.00426888804, 0.00350493, 0.00283016704, 0.00224334432, 0.0017146224, 0.00127939916, 0.0009284007400000001, 0.0006575044, 0.00048192892, 0.000370288464, 0.00028212838, 0.00021423853, 0.000162315692, 0.00012084144, 8.759026799999999e-05, 6.093322799999999e-05, 4.01885816e-05, 2.419815e-05, 1.24524262e-05, 3.91816172e-06, 1.63182344e-06, 9.7939776e-07, 5.6927552e-07, 3.373058399999999e-07, 1.90846488e-07, 1.0065554e-07, 4.13114748e-08, 1.64024448e-08, 8.922685199999999e-09, 5.28343112e-09, 3.14130588e-09, 1.9086828e-09, 1.11087136e-09, 6.10720784e-10, 2.94357078e-10, 1.26896e-10, 7.2975144e-11, 4.39999534e-11, 2.7289776e-11, 1.84358064e-11, 1.21499984e-11, 7.285000799999999e-12, 4.21987248e-12, 2.9957868e-12, 1.4225938e-12, 9.616415999999998e-13, 7.6023574e-13], 112 | "info": "C. Schinke, P. C. Peest, J. Schmidt, R. Brendel, K. Bothe, M. R. Vogt, I. Kröger, S. Winter, A. Schirmacher, S. Lim, H. T. Nguyen, D. MacDonald. Uncertainty analysis for the coefficient of band-to-band absorption of crystalline silicon. AIP Advances 5, 67168 (2015)" 113 | } 114 | 115 | 116 | } 117 | -------------------------------------------------------------------------------- /PyMoosh/optim_algo.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | 4 | 5 | def differential_evolution( 6 | f_cout, budget, X_min, X_max, f1=0.9, f2=0.8, cr=0.5, population=30 7 | ): 8 | """This is Differentiel Evolution in its current to best version. 9 | 10 | Args: 11 | f_cout (function): cost function taking a numpy vector as argument 12 | budget (integer): number of times the cost function can be computed 13 | X_min (numpy array): lower boundaries of the optimization domain, 14 | a vector with the same size as the argument of 15 | the cost function. 16 | X_max (numpy array): upper boundaries of the optimization domain. 17 | KwArgs: 18 | cr (float): value for crossover operation 19 | f1 (float): value for f1 mutation operation 20 | f2 (float): value for f2 mutation operation 21 | population (integer): size of the population (30 by default) 22 | 23 | Returns: 24 | best (numpy array): best solution found 25 | convergence (array): lowest value of the cost function for each 26 | generation 27 | """ 28 | 29 | # Hyperparameters 30 | # Cross-over : cr, 31 | # Mutation : f1 and f2 32 | n = X_min.size 33 | 34 | # Population initialization 35 | omega = np.zeros((population, n)) 36 | cost = np.zeros(population) 37 | for k in range(0, population): 38 | omega[k] = X_min + (X_max - X_min) * np.random.random(n) 39 | cost[k] = f_cout(omega[k]) 40 | 41 | # Who's the best ? 42 | who = np.argmin(cost) 43 | best = omega[who] 44 | 45 | # initialization of the rest 46 | evaluation = population 47 | convergence = [] 48 | generation = 0 49 | convergence.append(cost[who]) 50 | 51 | # Differential Evolution loop. 52 | while evaluation < budget - population: 53 | for k in range(0, population): 54 | 55 | # Choosing which parameters will be taken from the new individual 56 | crossover = np.random.random(n) < cr 57 | 58 | # Choosing 2 random individuals 59 | pop_1 = omega[np.random.randint(population)] 60 | pop_2 = omega[np.random.randint(population)] 61 | rand_step = pop_1 - pop_2 62 | best_step = best - omega[k] 63 | 64 | new_param = omega[k] + f1 * rand_step + f2 * best_step 65 | 66 | X = new_param * (1 - crossover) + omega[k] * crossover 67 | 68 | if np.prod((X >= X_min) * (X <= X_max)): 69 | # If the individual is in the parameter domain, proceed 70 | tmp = f_cout(X) 71 | evaluation = evaluation + 1 72 | if tmp < cost[k]: 73 | # If the new individual is better than the parent, 74 | # we keep it 75 | cost[k] = tmp 76 | omega[k] = X 77 | 78 | generation = generation + 1 79 | who = np.argmin(cost) 80 | best = omega[who] 81 | convergence.append(cost[who]) 82 | 83 | convergence = convergence[0 : generation + 1] 84 | 85 | return [best, convergence] 86 | 87 | 88 | def bfgs(f_cout, npas, start, *args): 89 | """This is a wrapper for the L-BFGS-B method encoded in scipy 90 | 91 | Args: 92 | f_cout (function): cost function taking a numpy vector as argument 93 | npas (integer): maximum number of iterations of the BFGS algorithm 94 | start (numpy array): initial guess for the structure 95 | *args contains nothing or: 96 | X_min (numpy array): lower boundaries of the optimization domain, 97 | a vector with the same size as the argument of 98 | the cost function. 99 | X_max (numpy array): upper boundaries of the optimization domain. 100 | 101 | Returns: 102 | best (numpy array): best solution found 103 | """ 104 | if len(args) == 2: 105 | xmin = args[0] 106 | xmax = args[1] 107 | assert ( 108 | len(xmin) == len(xmax) == len(start) 109 | ), f"starting array and boundary arrays should be of same length, but have lengths {len(start)}, {len(xmin)} and {len(xmax)}" 110 | limites = True 111 | else: 112 | limites = False 113 | 114 | x = np.array(start) 115 | epsilon = 1.0e-7 116 | 117 | def jac(x): 118 | """Jacobian defined with a finite difference method. 119 | It uses a global convergence variable to be able to 120 | retrieve the cost function value evolution. 121 | """ 122 | n = len(x) 123 | grad = np.zeros(n) 124 | 125 | val = f_cout(x) 126 | for i in range(n): 127 | xp = np.array(x) 128 | xp[i] = xp[i] + epsilon 129 | grad[i] = (f_cout(xp) - val) / epsilon 130 | return grad 131 | 132 | if limites: 133 | res = minimize( 134 | f_cout, 135 | start, 136 | method="L-BFGS-B", 137 | jac=jac, 138 | tol=1e-99, 139 | options={"disp": False, "maxiter": npas}, 140 | bounds=[(xmin[i], xmax[i]) for i in range(len(xmin))], 141 | ) 142 | else: 143 | res = minimize( 144 | f_cout, 145 | start, 146 | method="BFGS", 147 | jac=jac, 148 | tol=1e-99, 149 | options={"disp": False, "maxiter": npas}, 150 | ) 151 | 152 | best = res.x 153 | 154 | return best, f_cout(best) 155 | 156 | 157 | def QODE( 158 | f_cout, 159 | budget, 160 | X_min, 161 | X_max, 162 | f1=0.9, 163 | f2=0.8, 164 | cr=0.5, 165 | population=30, 166 | progression=False, 167 | ): 168 | """This is Quasi Opposite Differential Evolution. 169 | 170 | Args: 171 | f_cout (function): cost function taking a numpy vector as argument 172 | budget (integer): number of times the cost function can be computed 173 | X_min (numpy array): lower boundaries of the optimization domain, 174 | a vector with the same size as the argument of 175 | the cost function. 176 | X_max (numpy array): upper boundaries of the optimization domain. 177 | population (integer): size of the population (30 by default), should be even! 178 | 179 | Returns: 180 | best (numpy array): best solution found 181 | convergence (array): lowest value of the cost function for each 182 | generation 183 | """ 184 | 185 | n = X_min.size 186 | 187 | if progression is True: 188 | # By default, show for every 10% of progress 189 | progression = 10 190 | 191 | # Population initialization 192 | omega = np.zeros((population, n)) 193 | cost = np.zeros(population) 194 | # center of optimization domain 195 | c = (X_max + X_min) / 2 196 | for k in range(0, population, 2): 197 | omega[k] = X_min + (X_max - X_min) * np.random.random(n) 198 | cost[k] = f_cout(omega[k]) 199 | delta = 2 * (c - omega[k]) * np.random.random(n) 200 | omega[k + 1] = omega[k] + delta 201 | cost[k + 1] = f_cout(omega[k + 1]) 202 | # The specifity of QODE (the initialisation) is done, the rest is usual DE 203 | 204 | # Who's the best ? 205 | who = np.argmin(cost) 206 | best = omega[who] 207 | 208 | # initialization of the rest 209 | evaluation = population 210 | convergence = [] 211 | generation = 0 212 | convergence.append(cost[who]) 213 | 214 | # Differential Evolution loop. 215 | while evaluation < budget - population: 216 | for k in range(0, population): 217 | 218 | # Choosing which parameters will be taken from the new individual 219 | crossover = np.random.random(n) < cr 220 | 221 | # Choosing 2 random individuals 222 | pop_1 = omega[np.random.randint(population)] 223 | pop_2 = omega[np.random.randint(population)] 224 | rand_step = pop_1 - pop_2 225 | best_step = best - omega[k] 226 | 227 | new_param = omega[k] + f1 * rand_step + f2 * best_step 228 | 229 | X = new_param * (1 - crossover) + omega[k] * crossover 230 | 231 | if np.prod((X >= X_min) * (X <= X_max)): 232 | # If the individual is in the parameter domain, proceed 233 | tmp = f_cout(X) 234 | evaluation = evaluation + 1 235 | if progression and ((evaluation * progression) % budget == 0): 236 | print( 237 | f"Progression : {np.round(evaluation*100/(budget - population), 2)}%. Current best cost : {np.round(cost[who],6)}" 238 | ) 239 | if tmp < cost[k]: 240 | # If the new individual is better than the parent, 241 | # we keep it 242 | cost[k] = tmp 243 | omega[k] = X 244 | 245 | generation = generation + 1 246 | who = np.argmin(cost) 247 | best = omega[who] 248 | convergence.append(cost[who]) 249 | # if (evaluation % 50 == 0) and progression: 250 | # print( 251 | # f"Progression : {np.round(evaluation*100/(budget - population), 2)}%. Current best cost : {np.round(cost[who],6)}" 252 | # ) 253 | 254 | convergence = convergence[0 : generation + 1] 255 | 256 | return [best, convergence] 257 | 258 | 259 | def QNDE( 260 | f_cout, 261 | budget, 262 | X_min, 263 | X_max, 264 | f1=0.9, 265 | f2=0.8, 266 | cr=0.5, 267 | budget_bfgs=None, 268 | population=30, 269 | progression=False, 270 | ): 271 | """This is Quasi Newton Differential Evolution. 272 | 273 | Args: 274 | f_cout (function): cost function taking a numpy vector as argument 275 | budget (integer): number of times the cost function can be computed 276 | X_min (numpy array): lower boundaries of the optimization domain, 277 | a vector with the same size as the argument of 278 | the cost function. 279 | X_max (numpy array): upper boundaries of the optimization domain. 280 | population (integer): size of the population (30 by default) 281 | 282 | Returns: 283 | best (numpy array): best solution found 284 | convergence (array): lowest value of the cost function for each 285 | generation 286 | """ 287 | if budget_bfgs is None: 288 | budget_bfgs = int(0.1 * budget) 289 | cut_budget = budget - budget_bfgs 290 | first_best, first_convergence = QODE( 291 | f_cout, 292 | cut_budget, 293 | X_min, 294 | X_max, 295 | f1=f1, 296 | f2=f2, 297 | cr=cr, 298 | population=population, 299 | progression=progression, 300 | ) 301 | print("Switching to bfgs gradient descent...") if progression else None 302 | best, last_convergence = bfgs(f_cout, budget_bfgs, first_best, X_min, X_max) 303 | convergence = np.append(np.asarray(first_convergence), last_convergence) 304 | return [best, convergence] 305 | -------------------------------------------------------------------------------- /tests/test_units.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | from context import alt_methods 4 | import matplotlib.pyplot as plt 5 | from time import time 6 | 7 | ## Bragg mirror with increasing number of layers 8 | mat1 = 1.5 9 | mat2 = 1 10 | 11 | 12 | unit = "um" 13 | wav = 0.600 14 | 15 | ep1 = 0.1 16 | ep2 = 0.2 17 | 18 | layers = np.arange(5, 181, 5) 19 | 20 | 21 | print("Normal incidence, Bragg Mirror") 22 | incidence = 0 23 | materials = [1, mat1**2, mat2**2] 24 | 25 | rs_s_te_um = [] 26 | ts_s_te_um = [] 27 | 28 | rs_t_te_um = [] 29 | ts_t_te_um = [] 30 | 31 | rs_a_te_um = [] 32 | ts_a_te_um = [] 33 | 34 | rs_i_te_um = [] 35 | 36 | rs_dn_te_um = [] 37 | ts_dn_te_um = [] 38 | 39 | rs_s_tm_um = [] 40 | ts_s_tm_um = [] 41 | 42 | rs_t_tm_um = [] 43 | ts_t_tm_um = [] 44 | 45 | rs_a_tm_um = [] 46 | ts_a_tm_um = [] 47 | 48 | rs_i_tm_um = [] 49 | 50 | rs_dn_tm_um = [] 51 | ts_dn_tm_um = [] 52 | 53 | for nb_couches in layers: 54 | 55 | ## Case 1: single layer, TE 56 | # structure = np.random.random(nb_couches*2+1)*w_mean 57 | structure = np.array([ep1, ep2] * nb_couches + [ep1]) 58 | 59 | stack = [0] + [1, 2] * nb_couches + [1, 0] 60 | 61 | epaisseurs = np.concatenate(([0], structure, [0])) 62 | multi_stack = PM.Structure( 63 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 64 | ) 65 | a = time() 66 | r, t, R, T = PM.coefficient_S(multi_stack, wav, incidence, 0) 67 | b = time() 68 | rs_s_te_um.append(R) 69 | ts_s_te_um.append(T) 70 | 71 | multi_stack1 = PM.Structure( 72 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 73 | ) 74 | a = time() 75 | r_ab, t_ab, R_ab, T_ab = alt_methods.coefficient_A(multi_stack1, wav, incidence, 0) 76 | b = time() 77 | rs_a_te_um.append(R_ab) 78 | ts_a_te_um.append(T_ab) 79 | 80 | multi_stack1 = PM.Structure( 81 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 82 | ) 83 | a = time() 84 | r_t, t_t, R_t, T_t = alt_methods.coefficient_T(multi_stack1, wav, incidence, 0) 85 | b = time() 86 | rs_t_te_um.append(R_t) 87 | ts_t_te_um.append(T_t) 88 | 89 | multi_stack1 = PM.Structure( 90 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 91 | ) 92 | a = time() 93 | r_dn, t_dn, R_dn, T_dn = alt_methods.coefficient_DN(multi_stack1, wav, incidence, 0) 94 | b = time() 95 | rs_dn_te_um.append(R_dn) 96 | ts_dn_te_um.append(T_dn) 97 | 98 | multi_stack1 = PM.Structure( 99 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 100 | ) 101 | a = time() 102 | r_i, t_i, R_i, T_i = alt_methods.coefficient_I(multi_stack1, wav, incidence, 0) 103 | b = time() 104 | rs_i_te_um.append(R_i) 105 | 106 | multi_stack = PM.Structure( 107 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 108 | ) 109 | a = time() 110 | r, t, R, T = PM.coefficient_S(multi_stack, wav, incidence, 1) 111 | b = time() 112 | rs_s_tm_um.append(R) 113 | ts_s_tm_um.append(T) 114 | 115 | multi_stack1 = PM.Structure( 116 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 117 | ) 118 | a = time() 119 | r_ab, t_ab, R_ab, T_ab = alt_methods.coefficient_A(multi_stack1, wav, incidence, 1) 120 | b = time() 121 | rs_a_tm_um.append(R_ab) 122 | ts_a_tm_um.append(T_ab) 123 | 124 | multi_stack1 = PM.Structure( 125 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 126 | ) 127 | a = time() 128 | r_t, t_t, R_t, T_t = alt_methods.coefficient_T(multi_stack1, wav, incidence, 1) 129 | b = time() 130 | rs_t_tm_um.append(R_t) 131 | ts_t_tm_um.append(T_t) 132 | 133 | multi_stack1 = PM.Structure( 134 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 135 | ) 136 | a = time() 137 | r_dn, t_dn, R_dn, T_dn = alt_methods.coefficient_DN(multi_stack1, wav, incidence, 1) 138 | b = time() 139 | rs_dn_tm_um.append(R_dn) 140 | ts_dn_tm_um.append(T_dn) 141 | 142 | multi_stack1 = PM.Structure( 143 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 144 | ) 145 | a = time() 146 | r_i, t_i, R_i, T_i = alt_methods.coefficient_I(multi_stack1, wav, incidence, 1) 147 | b = time() 148 | rs_i_tm_um.append(R_i) 149 | 150 | rs_a_te_um = np.array(rs_a_te_um) 151 | rs_a_tm_um = np.array(rs_a_tm_um) 152 | ts_a_te_um = np.array(ts_a_te_um) 153 | ts_a_tm_um = np.array(ts_a_tm_um) 154 | 155 | rs_s_te_um = np.array(rs_s_te_um) 156 | rs_s_tm_um = np.array(rs_s_tm_um) 157 | ts_s_te_um = np.array(ts_s_te_um) 158 | ts_s_tm_um = np.array(ts_s_tm_um) 159 | 160 | rs_t_te_um = np.array(rs_t_te_um) 161 | rs_t_tm_um = np.array(rs_t_tm_um) 162 | ts_t_te_um = np.array(ts_t_te_um) 163 | ts_t_tm_um = np.array(ts_t_tm_um) 164 | 165 | rs_dn_te_um = np.array(rs_dn_te_um) 166 | rs_dn_tm_um = np.array(rs_dn_tm_um) 167 | ts_dn_te_um = np.array(ts_dn_te_um) 168 | ts_dn_tm_um = np.array(ts_dn_tm_um) 169 | 170 | rs_i_te_um = np.array(rs_i_te_um) 171 | rs_i_tm_um = np.array(rs_i_tm_um) 172 | 173 | 174 | unit = "nm" 175 | wav = 600 176 | 177 | ep1 = 100 178 | ep2 = 200 179 | 180 | layers = np.arange(5, 181, 5) 181 | 182 | 183 | print("Normal incidence, Bragg Mirror") 184 | incidence = 0 185 | materials = [1, mat1**2, mat2**2] 186 | 187 | rs_s_te_nm = [] 188 | ts_s_te_nm = [] 189 | 190 | rs_t_te_nm = [] 191 | ts_t_te_nm = [] 192 | 193 | rs_a_te_nm = [] 194 | ts_a_te_nm = [] 195 | 196 | rs_i_te_nm = [] 197 | 198 | rs_dn_te_nm = [] 199 | ts_dn_te_nm = [] 200 | 201 | rs_s_tm_nm = [] 202 | ts_s_tm_nm = [] 203 | 204 | rs_t_tm_nm = [] 205 | ts_t_tm_nm = [] 206 | 207 | rs_a_tm_nm = [] 208 | ts_a_tm_nm = [] 209 | 210 | rs_i_tm_nm = [] 211 | 212 | rs_dn_tm_nm = [] 213 | ts_dn_tm_nm = [] 214 | 215 | for nb_couches in layers: 216 | 217 | ## Case 1: single layer, TE 218 | # structure = np.random.random(nb_couches*2+1)*w_mean 219 | structure = np.array([ep1, ep2] * nb_couches + [ep1]) 220 | 221 | stack = [0] + [1, 2] * nb_couches + [1, 0] 222 | 223 | epaisseurs = np.concatenate(([0], structure, [0])) 224 | multi_stack = PM.Structure( 225 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 226 | ) 227 | a = time() 228 | r, t, R, T = PM.coefficient_S(multi_stack, wav, incidence, 0) 229 | b = time() 230 | rs_s_te_nm.append(R) 231 | ts_s_te_nm.append(T) 232 | 233 | multi_stack1 = PM.Structure( 234 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 235 | ) 236 | a = time() 237 | r_ab, t_ab, R_ab, T_ab = alt_methods.coefficient_A(multi_stack1, wav, incidence, 0) 238 | b = time() 239 | rs_a_te_nm.append(R_ab) 240 | ts_a_te_nm.append(T_ab) 241 | 242 | multi_stack1 = PM.Structure( 243 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 244 | ) 245 | a = time() 246 | r_t, t_t, R_t, T_t = alt_methods.coefficient_T(multi_stack1, wav, incidence, 0) 247 | b = time() 248 | rs_t_te_nm.append(R_t) 249 | ts_t_te_nm.append(T_t) 250 | 251 | multi_stack1 = PM.Structure( 252 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 253 | ) 254 | a = time() 255 | r_dn, t_dn, R_dn, T_dn = alt_methods.coefficient_DN(multi_stack1, wav, incidence, 0) 256 | b = time() 257 | rs_dn_te_nm.append(R_dn) 258 | ts_dn_te_nm.append(T_dn) 259 | 260 | multi_stack1 = PM.Structure( 261 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 262 | ) 263 | a = time() 264 | r_i, t_i, R_i, T_i = alt_methods.coefficient_I(multi_stack1, wav, incidence, 0) 265 | b = time() 266 | rs_i_te_nm.append(R_i) 267 | 268 | multi_stack = PM.Structure( 269 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 270 | ) 271 | a = time() 272 | r, t, R, T = PM.coefficient_S(multi_stack, wav, incidence, 1) 273 | b = time() 274 | rs_s_tm_nm.append(R) 275 | ts_s_tm_nm.append(T) 276 | 277 | multi_stack1 = PM.Structure( 278 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 279 | ) 280 | a = time() 281 | r_ab, t_ab, R_ab, T_ab = alt_methods.coefficient_A(multi_stack1, wav, incidence, 1) 282 | b = time() 283 | rs_a_tm_nm.append(R_ab) 284 | ts_a_tm_nm.append(T_ab) 285 | 286 | multi_stack1 = PM.Structure( 287 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 288 | ) 289 | a = time() 290 | r_t, t_t, R_t, T_t = alt_methods.coefficient_T(multi_stack1, wav, incidence, 1) 291 | b = time() 292 | rs_t_tm_nm.append(R_t) 293 | ts_t_tm_nm.append(T_t) 294 | 295 | multi_stack1 = PM.Structure( 296 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 297 | ) 298 | a = time() 299 | r_dn, t_dn, R_dn, T_dn = alt_methods.coefficient_DN(multi_stack1, wav, incidence, 1) 300 | b = time() 301 | rs_dn_tm_nm.append(R_dn) 302 | ts_dn_tm_nm.append(T_dn) 303 | 304 | multi_stack1 = PM.Structure( 305 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 306 | ) 307 | a = time() 308 | r_i, t_i, R_i, T_i = alt_methods.coefficient_I(multi_stack1, wav, incidence, 1) 309 | b = time() 310 | rs_i_tm_nm.append(R_i) 311 | 312 | rs_a_te_nm = np.array(rs_a_te_nm) 313 | rs_a_tm_nm = np.array(rs_a_tm_nm) 314 | ts_a_te_nm = np.array(ts_a_te_nm) 315 | ts_a_tm_nm = np.array(ts_a_tm_nm) 316 | 317 | rs_s_te_nm = np.array(rs_s_te_nm) 318 | rs_s_tm_nm = np.array(rs_s_tm_nm) 319 | ts_s_te_nm = np.array(ts_s_te_nm) 320 | ts_s_tm_nm = np.array(ts_s_tm_nm) 321 | 322 | rs_t_te_nm = np.array(rs_t_te_nm) 323 | rs_t_tm_nm = np.array(rs_t_tm_nm) 324 | ts_t_te_nm = np.array(ts_t_te_nm) 325 | ts_t_tm_nm = np.array(ts_t_tm_nm) 326 | 327 | rs_dn_te_nm = np.array(rs_dn_te_nm) 328 | rs_dn_tm_nm = np.array(rs_dn_tm_nm) 329 | ts_dn_te_nm = np.array(ts_dn_te_nm) 330 | ts_dn_tm_nm = np.array(ts_dn_tm_nm) 331 | 332 | rs_i_te_nm = np.array(rs_i_te_nm) 333 | rs_i_tm_nm = np.array(rs_i_tm_nm) 334 | 335 | 336 | fig, axs = plt.subplots(2, 2, sharex=True, figsize=(10, 10)) 337 | axs[0, 0].plot( 338 | layers, np.abs(rs_s_te_um - rs_s_te_nm), "b-v", label="abeles", markersize=4 339 | ) 340 | axs[0, 0].plot( 341 | layers, np.abs(rs_dn_te_um - rs_dn_te_nm), "r-o", label="D2N", markersize=4 342 | ) 343 | axs[0, 0].plot(layers, np.abs(rs_t_te_um - rs_t_te_nm), "g-^", label="T", markersize=4) 344 | axs[0, 0].plot( 345 | layers, np.abs(rs_i_te_um - rs_i_te_nm), "c-+", label="Impedance", markersize=4 346 | ) 347 | axs[0, 0].set_ylabel("Reflection relative error TE Normal incidence") 348 | axs[0, 0].set_xlabel("Nb Layers") 349 | ##axs[0,0].set_ylim([0-.0001,.15]) 350 | # axs[0,0].set_yscale("log") 351 | axs[0, 0].legend() 352 | 353 | 354 | axs[0, 1].plot( 355 | layers, np.abs(rs_s_tm_um - rs_s_tm_nm), "b-v", label="abeles", markersize=4 356 | ) 357 | axs[0, 1].plot( 358 | layers, np.abs(rs_dn_tm_um - rs_dn_tm_nm), "r-o", label="D2N", markersize=4 359 | ) 360 | axs[0, 1].plot(layers, np.abs(rs_t_tm_um - rs_t_tm_nm), "g-^", label="T", markersize=4) 361 | axs[0, 1].plot( 362 | layers, np.abs(rs_i_tm_um - rs_i_tm_nm), "c-+", label="Impedance", markersize=4 363 | ) 364 | axs[0, 1].set_ylabel("Reflection relative error TM Normal incidence") 365 | axs[0, 1].set_xlabel("Nb Layers") 366 | ##axs[0,1].set_ylim([-0.001,.15]) 367 | # axs[0,1].set_yscale("log") 368 | axs[0, 1].legend() 369 | 370 | 371 | axs[1, 0].plot( 372 | layers, np.abs(ts_s_te_um - ts_s_te_nm), "b-v", label="abeles", markersize=4 373 | ) 374 | axs[1, 0].plot( 375 | layers, np.abs(ts_dn_te_um - ts_dn_te_nm), "r-o", label="D2N", markersize=4 376 | ) 377 | axs[1, 0].plot(layers, np.abs(ts_t_te_um - ts_t_te_nm), "g-^", label="T", markersize=4) 378 | axs[1, 0].set_ylabel("Transmission relative error TE Normal incidence") 379 | axs[1, 0].set_xlabel("Nb Layers") 380 | # axs[1,0].set_ylim([-0.001,.15]) 381 | # axs[1,0].set_yscale("log") 382 | axs[1, 0].legend() 383 | 384 | 385 | axs[1, 1].plot( 386 | layers, np.abs(ts_s_tm_um - ts_s_tm_nm), "b-v", label="abeles", markersize=4 387 | ) 388 | axs[1, 1].plot( 389 | layers, np.abs(ts_dn_tm_um - ts_dn_tm_nm), "r-o", label="D2N", markersize=4 390 | ) 391 | axs[1, 1].plot(layers, np.abs(ts_t_tm_um - ts_t_tm_nm), "g-^", label="T", markersize=4) 392 | axs[1, 1].set_ylabel("Transmission relative error TM Normal incidence") 393 | axs[1, 1].set_xlabel("Nb Layers") 394 | # axs[1,1].set_ylim([-0.001,.15]) 395 | # axs[1,1].set_yscale("log") 396 | axs[1, 1].legend() 397 | plt.tight_layout() 398 | plt.show() 399 | -------------------------------------------------------------------------------- /tests/test_non_local.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | import matplotlib.pyplot as plt 4 | import csv 5 | 6 | from context import non_local as nl 7 | 8 | # Xbest = [11.6644913, 8.9e+14, 3.53201120e+12, 1.92864068e+30, 9.718*10**14] 9 | # [chi_b, w_p, gamma, beta_0, tau ] 10 | 11 | # wavelength_list = np.linspace(5000, 8000, 3001) 12 | 13 | # with open('nlplot.data', newline='') as csvfile: 14 | # plot = list(csv.reader(csvfile))[0] 15 | # plot = [float(p.strip()) for p in plot] 16 | # plot = np.array(plot) 17 | 18 | """ 19 | def wrapper_exemple(P, wl) : 20 | fonction de conversion entre les elements donnees sous la forme [chi_b, w_p, gamma, beta_p, beta_s, gamma, base, scale] a [chi_b, chi_f, w_p, beta] 21 | 22 | #[chi_b, w_p, gamma, beta_p, beta_s, base, scale] 23 | w = 2 * np.pi * 299792458 / (wl * 10**(-9)) 24 | chi_f = -P[1]**2 / (w * (w + 1j * P[2])) 25 | 26 | #[chi_b, chi_f, w_p, beta] 27 | return P[0], chi_f, P[1], np.sqrt(P[3] - 1j * P[4] * w) 28 | """ 29 | 30 | 31 | def nl_function(wavelength, chi_b): 32 | w_p = 8.9e14 33 | gamma = 3.53201120e12 34 | beta_0 = np.sqrt(1.92864068e30) 35 | tau = 9.718 * 10**14 36 | 37 | w = 2 * np.pi * 299792458 * 1e9 / wavelength 38 | beta2 = beta_0**2 - 1.0j * w * tau 39 | chi_f = -(w_p**2) / (w * (w + 1j * gamma)) 40 | 41 | return beta2, chi_b, chi_f, w_p 42 | 43 | 44 | mat_non_local = nl.NLMaterial([nl_function, 11.6644913]) 45 | materials = [14.2129, 15.6816, mat_non_local] 46 | 47 | stack = [1, 2, 0] 48 | thickness = [30, 105, 30] 49 | theta = np.pi * 37 / 180 50 | pol = 1.0 51 | 52 | chose = nl.NLStructure(materials, stack, thickness, verbose=False) 53 | 54 | wl, r, t, R, T = PM.spectrum(chose, theta, pol, 5000, 8000, 300) 55 | 56 | import matplotlib.pyplot as plt 57 | 58 | # plt.plot(wavelength_list, plot, label="ref") 59 | # plt.plot(wl, R*0.2, label="R plot with parameters de tic et tac") 60 | # plt.legend() 61 | # plt.show() 62 | 63 | # Test de la permittivité du modèle. 64 | # eps = [] 65 | # for w in wl: 66 | # beta2, chi_b, chi_f, w_p = nl_function(w, 11.6644913) 67 | # eps.append(1+chi_b+chi_f) 68 | # eps = np.array(eps) 69 | # plt.plot(wl, eps) 70 | # plt.ylabel("Eps(NL)") 71 | # plt.show() 72 | 73 | 74 | # %% Optimization 75 | 76 | 77 | # wavelength_list = np.linspace(5000, 8000, 3001) 78 | # # plot = 79 | 80 | # nb_lam = 50 81 | 82 | # def nl_function(wavelength, chi_b, w_p, gamma, beta_0, tau): 83 | 84 | # w = 2*np.pi*299792458*1e9 / wavelength 85 | # beta2 = beta_0**2 - 1.0j * w * tau 86 | # chi_f = - w_p**2/(w * (w + 1j * gamma)) 87 | 88 | # return beta2, chi_b, chi_f, w_p 89 | 90 | 91 | # def cost_function(X): 92 | # chi_b, w_p, gamma, beta_0, tau, base, scale = X 93 | # mat_non_local = PM.Material([nl_function, chi_b, w_p, gamma, beta_0, tau], specialType="NonLocal") 94 | # materials = [14.2129, 15.6816, mat_non_local] 95 | 96 | # stack = [1, 2, 0] 97 | # thickness = [0, 105, 0] 98 | # theta = np.pi * 37 / 180 99 | # pol = 1.0 100 | 101 | # chose = PM.Structure(materials,stack,thickness, verbose=False) 102 | 103 | # wl, r, t, R, T = PM.spectrum(chose, theta, pol, 5000, 8000, nb_lam) 104 | # R = R*scale + base 105 | # new_wl = np.linspace(5000, 8000, nb_lam) 106 | # obj = np.interp(new_wl, wavelength_list, plot) 107 | # cost = np.mean(np.abs(R - obj))+10*np.mean(np.abs(np.diff(R)-np.diff(obj))) 108 | # return cost/nb_lam 109 | 110 | # budget = 2000 # 111 | # nb_runs = 1 112 | 113 | 114 | # X_min = np.array([11.4, 1e14, 1e12, 1e14, 5e14, 0, 0.01]) 115 | # X_max = np.array([11.8, 1e15, 1e13, 1.5e15, 1.2e15, 1, 2]) 116 | 117 | # bests = [] 118 | # convergences = [] 119 | 120 | # for i in range(nb_runs): 121 | # print("RUN ", i+1 ,"/", nb_runs) 122 | # best, convergence = PM.QODE(cost_function, budget, X_min, X_max, progression=10) 123 | # bests.append(best) 124 | # convergences.append(convergence) 125 | 126 | # chi_b, w_p, gamma, beta_0, tau, base, scale = best 127 | # mat_non_local = PM.Material([nl_function, chi_b, w_p, gamma, beta_0, tau], specialType="NonLocal") 128 | # materials = [14.2129, 15.6816, mat_non_local] 129 | 130 | # stack = [1, 2, 0] 131 | # thickness = [0, 105, 0] 132 | # theta = np.pi * 37 / 180 133 | # pol = 1.0 134 | 135 | # chose = PM.Structure(materials,stack,thickness, verbose=False) 136 | # wl, r, t, R, T = PM.spectrum(chose, theta, pol, 5000, 8000, 500) 137 | # R = R*scale + base 138 | # print(f"best parameters found : chi_b={chi_b:e}, w_p={w_p:e}, gamma={gamma:e}, beta_0={beta_0:e}, tau={tau:e}") 139 | # plt.plot(np.linspace(5000,8000,500), R, label="optimized") 140 | # plt.plot(wavelength_list, plot, label="ref") 141 | # plt.legend() 142 | # plt.show() 143 | 144 | 145 | # %% 146 | 147 | 148 | wavelength_list = np.linspace(5000, 8000, 3001) 149 | Rs = np.zeros_like(wavelength_list) 150 | 151 | for i, wav in enumerate(wavelength_list): 152 | r, t, R, T = nl.NLcoefficient(chose, wav, np.pi * 37 / 180, 1) 153 | Rs[i] = R 154 | 155 | plt.figure(1) 156 | plt.plot(wavelength_list, Rs) 157 | plt.show() 158 | 159 | wavelength = 6683 160 | window = PM.Window(70 * wavelength, 0.4, 10, 2) 161 | beam = PM.Beam(wavelength, 38.7 / 180 * np.pi, 1, 10 * wavelength) 162 | chose = PM.Structure(materials, stack, thickness, verbose=False) 163 | Hyn_t, Hyn_l, Exn_t, Exn_l, Ezn_t, Ezn_l, rhon, jfx_n, jfz_n = nl.fields_NL_TL( 164 | chose, beam, window 165 | ) 166 | 167 | # plt.figure(5) 168 | # plt.subplot(2, 1, 1) 169 | # plt.plot(np.abs(Ezn_l[:, 0])) 170 | # plt.title("abs(Ezn_l)") 171 | # plt.subplot(2, 1, 2) 172 | # plt.plot(np.real(rhon[:, 0])) 173 | # plt.title("real(rho)") 174 | # plt.tight_layout() 175 | 176 | plt.figure(2, figsize=(7, 10)) 177 | plt.subplot(5, 2, 1) 178 | plt.imshow( 179 | np.real(Hyn_l), 180 | cmap="seismic", 181 | extent=[0, window.width, 0, sum(chose.thickness)], 182 | aspect="auto", 183 | ) 184 | plt.title("Re(Hyn_l)") 185 | plt.colorbar() 186 | plt.subplot(5, 2, 2) 187 | plt.imshow( 188 | np.real(Hyn_t), 189 | cmap="seismic", 190 | extent=[0, window.width, 0, sum(chose.thickness)], 191 | aspect="auto", 192 | ) 193 | plt.title("Re(Hyn_t)") 194 | plt.colorbar() 195 | 196 | plt.subplot(5, 2, 3) 197 | plt.imshow( 198 | np.real(Exn_l), 199 | cmap="seismic", 200 | extent=[0, window.width, 0, sum(chose.thickness)], 201 | aspect="auto", 202 | ) 203 | plt.title("Re(Exn_l)") 204 | plt.colorbar() 205 | plt.subplot(5, 2, 4) 206 | plt.imshow( 207 | np.real(Exn_t), 208 | cmap="seismic", 209 | extent=[0, window.width, 0, sum(chose.thickness)], 210 | aspect="auto", 211 | ) 212 | plt.title("Re(Exn_t)") 213 | plt.colorbar() 214 | 215 | plt.subplot(5, 2, 5) 216 | plt.imshow( 217 | np.real(Ezn_l), 218 | cmap="seismic", 219 | extent=[0, window.width, 0, sum(chose.thickness)], 220 | aspect="auto", 221 | ) 222 | plt.title("Re(Ezn_l)") 223 | plt.colorbar() 224 | plt.subplot(5, 2, 6) 225 | plt.imshow( 226 | np.real(Ezn_t), 227 | cmap="seismic", 228 | extent=[0, window.width, 0, sum(chose.thickness)], 229 | aspect="auto", 230 | ) 231 | plt.title("Re(Ezn_t)") 232 | plt.colorbar() 233 | 234 | plt.subplot(5, 2, 7) 235 | plt.imshow( 236 | np.real(rhon), 237 | cmap="seismic", 238 | extent=[0, window.width, 0, sum(chose.thickness)], 239 | aspect="auto", 240 | ) 241 | plt.title("Re(rhon)") 242 | plt.colorbar() 243 | plt.subplot(5, 2, 8) 244 | plt.imshow( 245 | np.real(jfx_n), 246 | cmap="seismic", 247 | extent=[0, window.width, 0, sum(chose.thickness)], 248 | aspect="auto", 249 | ) 250 | plt.title("Re(jfx_n)") 251 | plt.colorbar() 252 | plt.subplot(5, 2, 9) 253 | plt.imshow( 254 | np.real(jfz_n), 255 | cmap="seismic", 256 | extent=[0, window.width, 0, sum(chose.thickness)], 257 | aspect="auto", 258 | ) 259 | plt.title("Re(jfz_n)") 260 | plt.colorbar() 261 | 262 | 263 | plt.tight_layout() 264 | 265 | # plt.show() 266 | plt.savefig(f"mine_PyMoosh/stagiaire_a_integrer/non_local_order2_reals.pdf") 267 | 268 | plt.figure(3, figsize=(7, 10)) 269 | plt.subplot(5, 2, 1) 270 | plt.imshow( 271 | np.abs(Hyn_l), 272 | cmap="seismic", 273 | extent=[0, window.width, 0, sum(chose.thickness)], 274 | aspect="auto", 275 | ) 276 | plt.title("Re(Hyn_l)") 277 | plt.colorbar() 278 | plt.subplot(5, 2, 2) 279 | plt.imshow( 280 | np.abs(Hyn_t), 281 | cmap="seismic", 282 | extent=[0, window.width, 0, sum(chose.thickness)], 283 | aspect="auto", 284 | ) 285 | plt.title("Re(Hyn_t)") 286 | plt.colorbar() 287 | 288 | plt.subplot(5, 2, 3) 289 | plt.imshow( 290 | np.abs(Exn_l), 291 | cmap="seismic", 292 | extent=[0, window.width, 0, sum(chose.thickness)], 293 | aspect="auto", 294 | ) 295 | plt.title("Re(Exn_l)") 296 | plt.colorbar() 297 | plt.subplot(5, 2, 4) 298 | plt.imshow( 299 | np.abs(Exn_t), 300 | cmap="seismic", 301 | extent=[0, window.width, 0, sum(chose.thickness)], 302 | aspect="auto", 303 | ) 304 | plt.title("Re(Exn_t)") 305 | plt.colorbar() 306 | 307 | plt.subplot(5, 2, 5) 308 | plt.imshow( 309 | np.abs(Ezn_l), 310 | cmap="seismic", 311 | extent=[0, window.width, 0, sum(chose.thickness)], 312 | aspect="auto", 313 | ) 314 | plt.title("Re(Ezn_l)") 315 | plt.colorbar() 316 | plt.subplot(5, 2, 6) 317 | plt.imshow( 318 | np.abs(Ezn_t), 319 | cmap="seismic", 320 | extent=[0, window.width, 0, sum(chose.thickness)], 321 | aspect="auto", 322 | ) 323 | plt.title("Re(Ezn_t)") 324 | plt.colorbar() 325 | 326 | plt.subplot(5, 2, 7) 327 | plt.imshow( 328 | np.abs(rhon), 329 | cmap="seismic", 330 | extent=[0, window.width, 0, sum(chose.thickness)], 331 | aspect="auto", 332 | ) 333 | plt.title("Re(rhon)") 334 | plt.colorbar() 335 | plt.subplot(5, 2, 8) 336 | plt.imshow( 337 | np.abs(jfx_n), 338 | cmap="seismic", 339 | extent=[0, window.width, 0, sum(chose.thickness)], 340 | aspect="auto", 341 | ) 342 | plt.title("Re(jfx_n)") 343 | plt.colorbar() 344 | plt.subplot(5, 2, 9) 345 | plt.imshow( 346 | np.abs(jfz_n), 347 | cmap="seismic", 348 | extent=[0, window.width, 0, sum(chose.thickness)], 349 | aspect="auto", 350 | ) 351 | plt.title("Re(jfz_n)") 352 | plt.colorbar() 353 | 354 | 355 | plt.tight_layout() 356 | 357 | # plt.show() 358 | plt.savefig(f"mine_PyMoosh/stagiaire_a_integrer/non_local_order2_abs.pdf") 359 | 360 | 361 | wavelength = 7451 362 | window = PM.Window(2, 0.4, 1.0, 0.1) 363 | beam = PM.Beam(wavelength, 38.7 / 180 * np.pi, 1, 100 * wavelength) 364 | chose = PM.Structure(materials, stack, thickness, verbose=False) 365 | Hyn_t, Hyn_l, Exn_t, Exn_l, Ezn_t, Ezn_l, rhon, jfx_n, jfz_n = fields_NL_TL( 366 | chose, beam, window 367 | ) 368 | 369 | 370 | plt.figure(6) 371 | plt.subplot(2, 1, 1) 372 | plt.plot(np.abs(Ezn_l[:, 0])) 373 | plt.title("abs(Ezn_l)") 374 | plt.subplot(2, 1, 2) 375 | plt.plot(np.real(rhon[:, 0])) 376 | plt.title("real(rho)") 377 | plt.tight_layout() 378 | plt.show() 379 | 380 | # plt.figure(4, figsize=(7, 10)) 381 | # plt.subplot(5,2,1) 382 | # plt.imshow(np.real(Hyn_l),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 383 | # plt.title("Re(Hyn_l)") 384 | # plt.colorbar() 385 | # plt.subplot(5,2,2) 386 | # plt.imshow(np.real(Hyn_t),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 387 | # plt.title("Re(Hyn_t)") 388 | # plt.colorbar() 389 | 390 | # plt.subplot(5,2,3) 391 | # plt.imshow(np.real(Exn_l),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 392 | # plt.title("Re(Exn_l)") 393 | # plt.colorbar() 394 | # plt.subplot(5,2,4) 395 | # plt.imshow(np.real(Exn_t),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 396 | # plt.title("Re(Exn_t)") 397 | # plt.colorbar() 398 | 399 | # plt.subplot(5,2,5) 400 | # plt.imshow(np.real(Ezn_l),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 401 | # plt.title("Re(Ezn_l)") 402 | # plt.colorbar() 403 | # plt.subplot(5,2,6) 404 | # plt.imshow(np.real(Ezn_t),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 405 | # plt.title("Re(Ezn_t)") 406 | # plt.colorbar() 407 | 408 | # plt.subplot(5,2,7) 409 | # plt.imshow(np.real(rhon),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 410 | # plt.title("Re(rhon)") 411 | # plt.colorbar() 412 | # plt.subplot(5,2,8) 413 | # plt.imshow(np.real(jfx_n),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 414 | # plt.title("Re(jfx_n)") 415 | # plt.colorbar() 416 | # plt.subplot(5,2,9) 417 | # plt.imshow(np.real(jfz_n),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 418 | # plt.title("Re(jfz_n)") 419 | # plt.colorbar() 420 | 421 | 422 | # plt.tight_layout() 423 | 424 | # # plt.show() 425 | # plt.savefig(f"mine_PyMoosh/stagiaire_a_integrer/non_local_order1_reals.pdf") 426 | 427 | # plt.figure(5, figsize=(7,10)) 428 | # plt.subplot(5,2,1) 429 | # plt.imshow(np.abs(Hyn_l),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 430 | # plt.title("Re(Hyn_l)") 431 | # plt.colorbar() 432 | # plt.subplot(5,2,2) 433 | # plt.imshow(np.abs(Hyn_t),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 434 | # plt.title("Re(Hyn_t)") 435 | # plt.colorbar() 436 | 437 | # plt.subplot(5,2,3) 438 | # plt.imshow(np.abs(Exn_l),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 439 | # plt.title("Re(Exn_l)") 440 | # plt.colorbar() 441 | # plt.subplot(5,2,4) 442 | # plt.imshow(np.abs(Exn_t),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 443 | # plt.title("Re(Exn_t)") 444 | # plt.colorbar() 445 | 446 | # plt.subplot(5,2,5) 447 | # plt.imshow(np.abs(Ezn_l),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 448 | # plt.title("Re(Ezn_l)") 449 | # plt.colorbar() 450 | # plt.subplot(5,2,6) 451 | # plt.imshow(np.abs(Ezn_t),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 452 | # plt.title("Re(Ezn_t)") 453 | # plt.colorbar() 454 | 455 | # plt.subplot(5,2,7) 456 | # plt.imshow(np.abs(rhon),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 457 | # plt.title("Re(rhon)") 458 | # plt.colorbar() 459 | # plt.subplot(5,2,8) 460 | # plt.imshow(np.abs(jfx_n),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 461 | # plt.title("Re(jfx_n)") 462 | # plt.colorbar() 463 | # plt.subplot(5,2,9) 464 | # plt.imshow(np.abs(jfz_n),cmap='seismic',extent=[0,window.width,0,sum(chose.thickness)],aspect='auto') 465 | # plt.title("Re(jfz_n)") 466 | # plt.colorbar() 467 | 468 | 469 | # plt.tight_layout() 470 | 471 | # # plt.show() 472 | # plt.savefig(f"mine_PyMoosh/stagiaire_a_integrer/non_local_order1_abs.pdf") 473 | -------------------------------------------------------------------------------- /tests/test_abs_coefficients.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | from context import alt_methods 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | # materials = [1.513**2, 1.455**2, 2.079**2, (1.9+4.8j)**2, 1.0003**2] 8 | materials = [4.0, 1.5**2 + 0.1j, 2.0 + 0.2j] 9 | 10 | unit = "um" 11 | wav = 0.200 12 | eps = 1e-10 13 | 14 | 15 | print("Normal incidence:") 16 | incidence = 0 * np.pi / 180 17 | nb_prob = 0 18 | prob = False 19 | 20 | ## Case 1: single layer, TE 21 | # structure = np.random.random(nb_couches*2+1)*w_mean 22 | structure = np.array([100]) 23 | if unit == "um": 24 | structure = structure * 1e-3 25 | lay = [0, 1] 26 | 27 | # stack = [0]+[1,2]*nb_couches+[1,0] 28 | stack = [0, 2, 0] 29 | 30 | print("1 layer TE") 31 | 32 | epaisseurs = np.concatenate(([0], structure, [0])) 33 | 34 | chose = PM.Structure( 35 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 36 | ) 37 | 38 | # print(a, R, T) 39 | # print() 40 | # 41 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 0) 42 | if R + T + np.sum(a) != 1: 43 | print("total energy (should be one)", R + T + np.sum(a)) 44 | # 45 | # print(a, R, T) 46 | # 47 | # # 48 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 0) 49 | 50 | 51 | if np.sum(abs(r - r_a)) > eps: 52 | print("Problem with single interface and Abeles coeff refl in TE") 53 | print(f"r = {r}, r_AMat={r_a}") 54 | nb_prob += 1 55 | 56 | if np.sum(abs(t - t_a)) > eps: 57 | print("Problem with single interface and Abeles coeff trans in TE") 58 | print(f"t = {t}, t_AMat={t_a}") 59 | nb_prob += 1 60 | if np.sum(abs(a - a_a) > eps): 61 | print("Problem with single interface and Abeles coeff abs in TE") 62 | print(f"a = {a}, a_AMat = {a_a}") 63 | nb_prob += 1 64 | if nb_prob: 65 | print() 66 | prob = True 67 | nb_prob = 0 68 | # 69 | # ## Case 2: single layer, TM 70 | # structure = np.random.random(nb_couches*2+1)*w_mean 71 | structure = np.array([100]) 72 | if unit == "um": 73 | structure = structure * 1e-3 74 | 75 | print("1 layer TM") 76 | # stack = [0]+[1,2]*nb_couches+[1,0] 77 | stack = [0, 2, 0] 78 | # 79 | # 80 | # epaisseurs = np.concatenate(([0],structure,[0])) 81 | # 82 | # chose = PM.Structure(materials,stack,epaisseurs, verbose=False, unit=unit, si_units=True) 83 | # 84 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 1) 85 | if R + T + np.sum(a) != 1: 86 | print("total energy (should be one)", R + T + np.sum(a)) 87 | print(a, R, T) 88 | print() 89 | 90 | 91 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 1) 92 | 93 | 94 | if np.sum(abs(r - r_a)) > eps: 95 | print("Problem with single interface and Abeles coeff refl in TM") 96 | print(f"r = {r}, r_AMat={r_a}") 97 | 98 | if np.sum(abs(t - t_a)) > eps: 99 | print("Problem with single interface and Abeles coeff trans in TM") 100 | print(f"t = {t}, t_AMat={t_a}") 101 | nb_prob += 1 102 | if np.sum(abs(a - a_a) > eps): 103 | print("Problem with single interface and Abeles coeff abs in TM") 104 | print(f"a = {a}, a_AMat = {a_a}") 105 | nb_prob += 1 106 | if nb_prob: 107 | print() 108 | prob = True 109 | nb_prob = 0 110 | 111 | ## Case 3: two layers, TE 112 | # structure = np.random.random(nb_couches*2+1)*w_mean 113 | structure = np.array([100, 150]) 114 | if unit == "um": 115 | structure = structure * 1e-3 116 | 117 | print("2 layers TE") 118 | 119 | # stack = [0]+[1,2]*nb_couches+[1,0] 120 | stack = [0, 2, 1, 0] 121 | 122 | 123 | epaisseurs = np.concatenate(([0], structure, [0])) 124 | 125 | chose = PM.Structure( 126 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 127 | ) 128 | 129 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 0) 130 | if R + T + np.sum(a) != 1: 131 | print("total energy (should be one)", R + T + np.sum(a)) 132 | 133 | 134 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 0) 135 | 136 | 137 | if np.sum(abs(r - r_a)) > eps: 138 | print("Problem with two layers and Abeles coeff refl in TE") 139 | print(f"r = {r}, r_AMat={r_a}") 140 | nb_prob += 1 141 | 142 | if np.sum(abs(t - t_a)) > eps: 143 | print("Problem with two layers and Abeles coeff trans in TE") 144 | print(f"t = {t}, t_AMat={t_a}") 145 | nb_prob += 1 146 | if np.sum(abs(a - a_a) > eps): 147 | print("Problem with two layers and Abeles coeff abs inTE.") 148 | print(f"a = {a}, a_AMat = {a_a}") 149 | nb_prob += 1 150 | if nb_prob: 151 | # print() 152 | prob = True 153 | nb_prob = 0 154 | # 155 | # ## Case 4: two layers, TM 156 | # structure = np.random.random(nb_couches*2+1)*w_mean 157 | structure = np.array([100, 150]) 158 | if unit == "um": 159 | structure = structure * 1e-3 160 | 161 | print("2 layers TM") 162 | 163 | # stack = [0]+[1,2]*nb_couches+[1,0] 164 | stack = [0, 2, 1, 0] 165 | 166 | 167 | epaisseurs = np.concatenate(([0], structure, [0])) 168 | 169 | chose = PM.Structure( 170 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 171 | ) 172 | 173 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 1) 174 | if R + T + np.sum(a) != 1: 175 | print("total energy (should be one)", R + T + np.sum(a)) 176 | print(a, R, T) 177 | 178 | 179 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 1) 180 | 181 | 182 | if np.sum(abs(r - r_a)) > eps: 183 | print("Problem with two layers and Abeles coeff refl in TM") 184 | print(f"r = {r}, r_AMat={r_a}") 185 | nb_prob += 1 186 | 187 | if np.sum(abs(t - t_a)) > eps: 188 | print("Problem with two layers and Abeles coeff trans in TM") 189 | print(f"t = {t}, t_AMat={t_a}") 190 | nb_prob += 1 191 | if np.sum(abs(a - a_a) > eps): 192 | print("Problem with two layers and Abeles coeff abs in TM") 193 | print(f"a = {a}, a_AMat = {a_a}") 194 | nb_prob += 1 195 | if nb_prob: 196 | print() 197 | prob = True 198 | # nb_prob = 0 199 | # 200 | # if not(prob): 201 | # print("OK!") 202 | # print() 203 | # 204 | # 205 | print("Large incidence:") 206 | incidence = 60 * np.pi / 180 207 | prob = False 208 | 209 | ## Case 1: single layer, TE 210 | # structure = np.random.random(nb_couches*2+1)*w_mean 211 | structure = np.array([100]) 212 | if unit == "um": 213 | structure = structure * 1e-3 214 | 215 | # stack = [0]+[1,2]*nb_couches+[1,0] 216 | stack = [0, 2, 0] 217 | 218 | 219 | epaisseurs = np.concatenate(([0], structure, [0])) 220 | 221 | chose = PM.Structure( 222 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 223 | ) 224 | 225 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 0) 226 | if R + T + np.sum(a) != 1: 227 | print("total energy (should be one)", R + T + np.sum(a)) 228 | 229 | 230 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 0) 231 | 232 | 233 | if np.sum(abs(r - r_a)) > eps: 234 | print("Problem with single interface and Abeles coeff refl in TE") 235 | print(f"r = {r}, r_AMat={r_a}") 236 | nb_prob += 1 237 | 238 | if np.sum(abs(t - t_a)) > eps: 239 | print("Problem with single interface and Abeles coeff trans in TE") 240 | print(f"t = {t}, t_AMat={t_a}") 241 | nb_prob += 1 242 | if np.sum(abs(a - a_a) > eps): 243 | print("Problem with single interface and Abeles coeff abs in TE") 244 | print(f"a = {a}, a_AMat = {a_a}") 245 | nb_prob += 1 246 | if nb_prob: 247 | print() 248 | prob = True 249 | nb_prob = 0 250 | # 251 | ## Case 2: single layer, TM 252 | # structure = np.random.random(nb_couches*2+1)*w_mean 253 | structure = np.array([100]) 254 | if unit == "um": 255 | structure = structure * 1e-3 256 | 257 | # stack = [0]+[1,2]*nb_couches+[1,0] 258 | stack = [0, 2, 0] 259 | 260 | 261 | epaisseurs = np.concatenate(([0], structure, [0])) 262 | 263 | chose = PM.Structure( 264 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 265 | ) 266 | 267 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 1) 268 | if R + T + np.sum(a) != 1: 269 | print("total energy (should be one)", R + T + np.sum(a)) 270 | 271 | 272 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 1) 273 | 274 | 275 | if np.sum(abs(r - r_a)) > eps: 276 | print("Problem with single interface and Abeles coeff refl in TM") 277 | print(f"r = {r}, r_AMat={r_a}") 278 | nb_prob += 1 279 | 280 | if np.sum(abs(t - t_a)) > eps: 281 | print("Problem with single interface and Abeles coeff trans in TM") 282 | print(f"t = {t}, t_AMat={t_a}") 283 | nb_prob += 1 284 | if np.sum(abs(a - a_a) > eps): 285 | print("Problem with single interface and Abeles coeff abs in TM") 286 | print(f"a = {a}, a_AMat = {a_a}") 287 | nb_prob += 1 288 | if nb_prob: 289 | print() 290 | prob = True 291 | nb_prob = 0 292 | 293 | ## Case 3: two layers, TE 294 | # structure = np.random.random(nb_couches*2+1)*w_mean 295 | structure = np.array([100, 150]) 296 | if unit == "um": 297 | structure = structure * 1e-3 298 | 299 | # stack = [0]+[1,2]*nb_couches+[1,0] 300 | stack = [0, 2, 1, 0] 301 | 302 | 303 | epaisseurs = np.concatenate(([0], structure, [0])) 304 | 305 | chose = PM.Structure( 306 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 307 | ) 308 | 309 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 0) 310 | if R + T + np.sum(a) != 1: 311 | print("total energy (should be one)", R + T + np.sum(a)) 312 | 313 | 314 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 0) 315 | 316 | 317 | if np.sum(abs(r - r_a)) > eps: 318 | print("Problem with two layers and Abeles coeff refl in TE") 319 | print(f"r = {r}, r_AMat={r_a}") 320 | nb_prob += 1 321 | 322 | if np.sum(abs(t - t_a)) > eps: 323 | print("Problem with two layers and Abeles coeff trans in TE") 324 | print(f"t = {t}, t_AMat={t_a}") 325 | nb_prob += 1 326 | if np.sum(abs(a - a_a) > eps): 327 | print("Problem with two layers and Abeles coeff abs inTE.") 328 | print(f"a = {a}, a_AMat = {a_a}") 329 | nb_prob += 1 330 | if nb_prob: 331 | print() 332 | prob = True 333 | nb_prob = 0 334 | 335 | ## Case 4: two layers, TM 336 | # structure = np.random.random(nb_couches*2+1)*w_mean 337 | structure = np.array([100, 150]) 338 | if unit == "um": 339 | structure = structure * 1e-3 340 | 341 | # stack = [0]+[1,2]*nb_couches+[1,0] 342 | stack = [0, 2, 1, 0] 343 | 344 | 345 | epaisseurs = np.concatenate(([0], structure, [0])) 346 | 347 | chose = PM.Structure( 348 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 349 | ) 350 | 351 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 1) 352 | if R + T + np.sum(a) != 1: 353 | print("total energy (should be one)", R + T + np.sum(a)) 354 | 355 | 356 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 1) 357 | 358 | 359 | if np.sum(abs(r - r_a)) > eps: 360 | print("Problem with two layers and Abeles coeff refl in TM") 361 | print(f"r = {r}, r_AMat={r_a}") 362 | nb_prob += 1 363 | 364 | if np.sum(abs(t - t_a)) > eps: 365 | print("Problem with two layers and Abeles coeff trans in TM") 366 | print(f"t = {t}, t_AMat={t_a}") 367 | nb_prob += 1 368 | if np.sum(abs(a - a_a) > eps): 369 | print("Problem with two layers and Abeles coeff abs in TM") 370 | print(f"a = {a}, a_AMat = {a_a}") 371 | nb_prob += 1 372 | if nb_prob: 373 | print() 374 | prob = True 375 | nb_prob = 0 376 | 377 | if not (prob): 378 | print("OK!") 379 | print() 380 | 381 | 382 | print("Intermediate incidence:") 383 | incidence = 15 * np.pi / 180 384 | prob = False 385 | 386 | ## Case 1: single layer, TE 387 | # structure = np.random.random(nb_couches*2+1)*w_mean 388 | structure = np.array([100]) 389 | if unit == "um": 390 | structure = structure * 1e-3 391 | 392 | # stack = [0]+[1,2]*nb_couches+[1,0] 393 | stack = [0, 2, 0] 394 | 395 | 396 | epaisseurs = np.concatenate(([0], structure, [0])) 397 | 398 | chose = PM.Structure( 399 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 400 | ) 401 | 402 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 0) 403 | if R + T + np.sum(a) != 1: 404 | print("total energy (should be one)", R + T + np.sum(a)) 405 | 406 | 407 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 0) 408 | 409 | 410 | if np.sum(abs(r - r_a)) > eps: 411 | print("Problem with single interface and Abeles coeff refl in TE") 412 | print(f"r = {r}, r_AMat={r_a}") 413 | nb_prob += 1 414 | 415 | if np.sum(abs(t - t_a)) > eps: 416 | print("Problem with single interface and Abeles coeff trans in TE") 417 | print(f"t = {t}, t_AMat={t_a}") 418 | nb_prob += 1 419 | if np.sum(abs(a - a_a) > eps): 420 | print("Problem with single interface and Abeles coeff abs in TE") 421 | print(f"a = {a}, a_AMat = {a_a}") 422 | nb_prob += 1 423 | if nb_prob: 424 | print() 425 | prob = True 426 | nb_prob = 0 427 | 428 | ## Case 2: single layer, TM 429 | # structure = np.random.random(nb_couches*2+1)*w_mean 430 | structure = np.array([100]) 431 | if unit == "um": 432 | structure = structure * 1e-3 433 | 434 | # stack = [0]+[1,2]*nb_couches+[1,0] 435 | stack = [0, 2, 0] 436 | 437 | 438 | epaisseurs = np.concatenate(([0], structure, [0])) 439 | 440 | chose = PM.Structure( 441 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 442 | ) 443 | 444 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 1) 445 | if R + T + np.sum(a) != 1: 446 | print("total energy (should be one)", R + T + np.sum(a)) 447 | 448 | 449 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 1) 450 | 451 | 452 | if np.sum(abs(r - r_a)) > eps: 453 | print("Problem with single interface and Abeles coeff refl in TM") 454 | print(f"r = {r}, r_AMat={r_a}") 455 | nb_prob += 1 456 | 457 | if np.sum(abs(t - t_a)) > eps: 458 | print("Problem with single interface and Abeles coeff trans in TM") 459 | print(f"t = {t}, t_AMat={t_a}") 460 | nb_prob += 1 461 | if np.sum(abs(a - a_a) > eps): 462 | print("Problem with single interface and Abeles coeff abs in TM") 463 | print(f"a = {a}, a_AMat = {a_a}") 464 | nb_prob += 1 465 | if nb_prob: 466 | print() 467 | prob = True 468 | nb_prob = 0 469 | 470 | ## Case 3: two layers, TE 471 | # structure = np.random.random(nb_couches*2+1)*w_mean 472 | structure = np.array([100, 150]) 473 | if unit == "um": 474 | structure = structure * 1e-3 475 | 476 | # stack = [0]+[1,2]*nb_couches+[1,0] 477 | stack = [0, 2, 1, 0] 478 | 479 | 480 | epaisseurs = np.concatenate(([0], structure, [0])) 481 | 482 | chose = PM.Structure( 483 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 484 | ) 485 | 486 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 0) 487 | if R + T + np.sum(a) != 1: 488 | print("total energy (should be one)", R + T + np.sum(a)) 489 | 490 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 0) 491 | 492 | if np.sum(abs(r - r_a)) > eps: 493 | print("Problem with two layers and Abeles coeff refl in TE") 494 | print(f"r = {r}, r_AMat={r_a}") 495 | nb_prob += 1 496 | 497 | if np.sum(abs(t - t_a)) > eps: 498 | print("Problem with two layers and Abeles coeff trans in TE") 499 | print(f"t = {t}, t_AMat={t_a}") 500 | nb_prob += 1 501 | if np.sum(abs(a - a_a) > eps): 502 | print("Problem with two layers and Abeles coeff abs inTE.") 503 | print(f"a = {a}, a_AMat = {a_a}") 504 | nb_prob += 1 505 | if nb_prob: 506 | print() 507 | prob = True 508 | nb_prob = 0 509 | 510 | ## Case 4: two layers, TM 511 | # structure = np.random.random(nb_couches*2+1)*w_mean 512 | structure = np.array([100, 150]) 513 | if unit == "um": 514 | structure = structure * 1e-3 515 | 516 | # stack = [0]+[1,2]*nb_couches+[1,0] 517 | stack = [0, 2, 1, 0] 518 | 519 | 520 | epaisseurs = np.concatenate(([0], structure, [0])) 521 | 522 | chose = PM.Structure( 523 | materials, stack, epaisseurs, verbose=False, unit=unit, si_units=True 524 | ) 525 | 526 | a, r, t, R, T = PM.absorption_S(chose, wav, incidence, 1) 527 | if R + T + np.sum(a) != 1: 528 | print("total energy (should be one)", R + T + np.sum(a)) 529 | 530 | 531 | a_a, r_a, t_a, R_a, T_a = alt_methods.absorption_A(chose, wav, incidence, 1) 532 | 533 | 534 | if np.sum(abs(r - r_a)) > eps: 535 | print("Problem with two layers and Abeles coeff refl in TM") 536 | print(f"r = {r}, r_AMat={r_a}") 537 | nb_prob += 1 538 | 539 | if np.sum(abs(t - t_a)) > eps: 540 | print("Problem with two layers and Abeles coeff trans in TM") 541 | print(f"t = {t}, t_AMat={t_a}") 542 | nb_prob += 1 543 | if np.sum(abs(a - a_a) > eps): 544 | print("Problem with two layers and Abeles coeff abs in TM") 545 | print(f"a = {a}, a_AMat = {a_a}") 546 | nb_prob += 1 547 | if nb_prob: 548 | print() 549 | prob = True 550 | nb_prob = 0 551 | 552 | if not (prob): 553 | print("OK!") 554 | print() 555 | -------------------------------------------------------------------------------- /tests/test_diff_coefficients.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from context import PM 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | # materials = [1.513**2, 1.455**2, 2.079**2, (1.9+4.8j)**2, 1.0003**2] 7 | materials = [4.0, 1.5**2, 2.0 + 10.2j] 8 | wav = 20 9 | eps = 1e-10 10 | pas = 0.001 11 | 12 | 13 | print("Normal incidence:") 14 | incidence = 0 15 | nb_prob = 0 16 | prob = False 17 | 18 | ## Case 1: single layer, TE 19 | # structure = np.random.random(nb_couches*2+1)*w_mean 20 | structure = np.array([1]) 21 | 22 | # stack = [0]+[1,2]*nb_couches+[1,0] 23 | stack = [0, 2, 0] 24 | 25 | 26 | epaisseurs = np.concatenate(([0], structure, [0])) 27 | 28 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 29 | 30 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 0, pas=pas) 31 | 32 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 33 | chose, wav, incidence, 0, method="A", pas=pas 34 | ) 35 | 36 | 37 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 0, method="T", pas=pas) 38 | 39 | 40 | if np.sum(abs(r - r_ab)) > eps: 41 | print("Problem with single interface and abeles coeff refl in TE") 42 | print(f"r = {r}, r_abeles={r_ab}") 43 | nb_prob += 1 44 | if np.sum(abs(r - r_t)) > eps: 45 | print("Problem with single interface and TMatrix coeff refl in TE") 46 | print(f"r = {r}, r_TMat={r_t}") 47 | nb_prob += 1 48 | if np.sum(abs(t - t_ab)) > eps: 49 | print("Problem with single interface and abeles coeff trans in TE") 50 | print(f"t = {t}, t_abeles={t_ab}") 51 | nb_prob += 1 52 | if np.sum(abs(t - t_t)) > eps: 53 | print("Problem with single interface and TMatrix coeff trans in TE") 54 | print(f"t = {t}, t_TMat={t_t}") 55 | nb_prob += 1 56 | 57 | if nb_prob: 58 | print() 59 | prob = True 60 | nb_prob = 0 61 | 62 | ## Case 2: single layer, TM 63 | # structure = np.random.random(nb_couches*2+1)*w_mean 64 | structure = np.array([1]) 65 | 66 | # stack = [0]+[1,2]*nb_couches+[1,0] 67 | stack = [0, 2, 0] 68 | 69 | 70 | epaisseurs = np.concatenate(([0], structure, [0])) 71 | 72 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 73 | 74 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 1, pas=pas) 75 | 76 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 77 | chose, wav, incidence, 1, method="A", pas=pas 78 | ) 79 | 80 | 81 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 1, method="T", pas=pas) 82 | 83 | 84 | if np.sum(abs(r - r_ab)) > eps: 85 | print("Problem with single interface and abeles coeff refl in TM") 86 | print(f"r = {r}, r_abeles={r_ab}") 87 | nb_prob += 1 88 | if np.sum(abs(r - r_t)) > eps: 89 | print("Problem with single interface and TMatrix coeff refl in TM") 90 | print(f"r = {r}, r_TMat={r_t}") 91 | if np.sum(abs(t - t_ab)) > eps: 92 | print("Problem with single interface and abeles coeff trans in TM") 93 | print(f"t = {t}, t_abeles={t_ab}") 94 | nb_prob += 1 95 | if np.sum(abs(t - t_t)) > eps: 96 | print("Problem with single interface and TMatrix coeff trans in TM") 97 | print(f"t = {t}, t_TMat={t_t}") 98 | nb_prob += 1 99 | 100 | if nb_prob: 101 | print() 102 | prob = True 103 | nb_prob = 0 104 | 105 | ## Case 3: two layers, TE 106 | # structure = np.random.random(nb_couches*2+1)*w_mean 107 | structure = np.array([1, 1.5]) 108 | 109 | # stack = [0]+[1,2]*nb_couches+[1,0] 110 | stack = [0, 2, 1, 0] 111 | 112 | 113 | epaisseurs = np.concatenate(([0], structure, [0])) 114 | 115 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 116 | 117 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 0, pas=pas) 118 | 119 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 120 | chose, wav, incidence, 0, method="A", pas=pas 121 | ) 122 | 123 | 124 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 0, method="T", pas=pas) 125 | 126 | 127 | if np.sum(abs(r - r_ab)) > eps: 128 | print("Problem with two layers and abeles coeff refl in TE") 129 | print(f"r = {r}, r_abeles={r_ab}") 130 | nb_prob += 1 131 | if np.sum(abs(r - r_t)) > eps: 132 | print("Problem with two layers and TMatrix coeff refl in TE") 133 | print(f"r = {r}, r_TMat={r_t}") 134 | nb_prob += 1 135 | if np.sum(abs(t - t_ab)) > eps: 136 | print("Problem with two layers and abeles coeff trans in TE") 137 | print(f"t = {t}, t_abeles={t_ab}") 138 | nb_prob += 1 139 | if np.sum(abs(t - t_t)) > eps: 140 | print("Problem with two layers and TMatrix coeff trans in TE") 141 | print(f"t = {t}, t_TMat={t_t}") 142 | nb_prob += 1 143 | 144 | if nb_prob: 145 | print() 146 | prob = True 147 | nb_prob = 0 148 | 149 | ## Case 4: two layers, TM 150 | # structure = np.random.random(nb_couches*2+1)*w_mean 151 | structure = np.array([1, 1.5]) 152 | 153 | # stack = [0]+[1,2]*nb_couches+[1,0] 154 | stack = [0, 2, 1, 0] 155 | 156 | 157 | epaisseurs = np.concatenate(([0], structure, [0])) 158 | 159 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 160 | 161 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 1, pas=pas) 162 | 163 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 164 | chose, wav, incidence, 1, method="A", pas=pas 165 | ) 166 | 167 | 168 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 1, method="T", pas=pas) 169 | 170 | 171 | if np.sum(abs(r - r_ab)) > eps: 172 | print("Problem with two layers and abeles coeff refl in TM") 173 | print(f"r = {r}, r_abeles={r_ab}") 174 | nb_prob += 1 175 | if np.sum(abs(r - r_t)) > eps: 176 | print("Problem with two layers and TMatrix coeff refl in TM") 177 | print(f"r = {r}, r_TMat={r_t}") 178 | nb_prob += 1 179 | if np.sum(abs(t - t_ab)) > eps: 180 | print("Problem with two layers and abeles coeff trans in TM") 181 | print(f"t = {t}, t_abeles={t_ab}") 182 | nb_prob += 1 183 | if np.sum(abs(t - t_t)) > eps: 184 | print("Problem with two layers and TMatrix coeff trans in TM") 185 | print(f"t = {t}, t_TMat={t_t}") 186 | nb_prob += 1 187 | 188 | if nb_prob: 189 | print() 190 | prob = True 191 | nb_prob = 0 192 | 193 | if not (prob): 194 | print("OK!") 195 | print() 196 | 197 | 198 | print("Large incidence:") 199 | incidence = 60 * np.pi / 180 200 | prob = False 201 | 202 | ## Case 1: single layer, TE 203 | # structure = np.random.random(nb_couches*2+1)*w_mean 204 | structure = np.array([1]) 205 | 206 | # stack = [0]+[1,2]*nb_couches+[1,0] 207 | stack = [0, 2, 0] 208 | 209 | 210 | epaisseurs = np.concatenate(([0], structure, [0])) 211 | 212 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 213 | 214 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 0, pas=pas) 215 | 216 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 217 | chose, wav, incidence, 0, method="A", pas=pas 218 | ) 219 | 220 | 221 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 0, method="T", pas=pas) 222 | 223 | if np.sum(abs(r - r_ab)) > eps: 224 | print("Problem with single interface and abeles coeff refl in TE") 225 | print(f"r = {r}, r_abeles={r_ab}") 226 | nb_prob += 1 227 | if np.sum(abs(r - r_t)) > eps: 228 | print("Problem with single interface and TMatrix coeff refl in TE") 229 | print(f"r = {r}, r_TMat={r_t}") 230 | nb_prob += 1 231 | if np.sum(abs(t - t_ab)) > eps: 232 | print("Problem with single interface and abeles coeff trans in TE") 233 | print(f"t = {t}, t_abeles={t_ab}") 234 | nb_prob += 1 235 | if np.sum(abs(t - t_t)) > eps: 236 | print("Problem with single interface and TMatrix coeff trans in TE") 237 | print(f"t = {t}, t_TMat={t_t}") 238 | nb_prob += 1 239 | 240 | if nb_prob: 241 | print() 242 | prob = True 243 | nb_prob = 0 244 | 245 | ## Case 2: single layer, TM 246 | # structure = np.random.random(nb_couches*2+1)*w_mean 247 | structure = np.array([1]) 248 | 249 | # stack = [0]+[1,2]*nb_couches+[1,0] 250 | stack = [0, 2, 0] 251 | 252 | 253 | epaisseurs = np.concatenate(([0], structure, [0])) 254 | 255 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 256 | 257 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 1, pas=pas) 258 | 259 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 260 | chose, wav, incidence, 1, method="A", pas=pas 261 | ) 262 | 263 | 264 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 1, method="T", pas=pas) 265 | 266 | if np.sum(abs(r - r_ab)) > eps: 267 | print("Problem with single interface and abeles coeff refl in TM") 268 | print(f"r = {r}, r_abeles={r_ab}") 269 | nb_prob += 1 270 | if np.sum(abs(r - r_t)) > eps: 271 | print("Problem with single interface and TMatrix coeff refl in TM") 272 | print(f"r = {r}, r_TMat={r_t}") 273 | nb_prob += 1 274 | if np.sum(abs(t - t_ab)) > eps: 275 | print("Problem with single interface and abeles coeff trans in TM") 276 | print(f"t = {t}, t_abeles={t_ab}") 277 | nb_prob += 1 278 | if np.sum(abs(t - t_t)) > eps: 279 | print("Problem with single interface and TMatrix coeff trans in TM") 280 | print(f"t = {t}, t_TMat={t_t}") 281 | nb_prob += 1 282 | 283 | if nb_prob: 284 | print() 285 | prob = True 286 | nb_prob = 0 287 | 288 | ## Case 3: two layers, TE 289 | # structure = np.random.random(nb_couches*2+1)*w_mean 290 | structure = np.array([1, 1.5]) 291 | 292 | # stack = [0]+[1,2]*nb_couches+[1,0] 293 | stack = [0, 2, 1, 0] 294 | 295 | 296 | epaisseurs = np.concatenate(([0], structure, [0])) 297 | 298 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 299 | 300 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 0, pas=pas) 301 | 302 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 303 | chose, wav, incidence, 0, method="A", pas=pas 304 | ) 305 | 306 | 307 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 0, method="T", pas=pas) 308 | 309 | if np.sum(abs(r - r_ab)) > eps: 310 | print("Problem with two layers and abeles coeff refl in TE") 311 | print(f"r = {r}, r_abeles={r_ab}") 312 | nb_prob += 1 313 | if np.sum(abs(r - r_t)) > eps: 314 | print("Problem with two layers and TMatrix coeff refl in TE") 315 | print(f"r = {r}, r_TMat={r_t}") 316 | nb_prob += 1 317 | if np.sum(abs(t - t_ab)) > eps: 318 | print("Problem with two layers and abeles coeff trans in TE") 319 | print(f"t = {t}, t_abeles={t_ab}") 320 | nb_prob += 1 321 | if np.sum(abs(t - t_t)) > eps: 322 | print("Problem with two layers and TMatrix coeff trans in TE") 323 | print(f"t = {t}, t_TMat={t_t}") 324 | nb_prob += 1 325 | 326 | if nb_prob: 327 | print() 328 | prob = True 329 | nb_prob = 0 330 | 331 | ## Case 4: two layers, TM 332 | # structure = np.random.random(nb_couches*2+1)*w_mean 333 | structure = np.array([1, 1.5]) 334 | 335 | # stack = [0]+[1,2]*nb_couches+[1,0] 336 | stack = [0, 2, 1, 0] 337 | 338 | 339 | epaisseurs = np.concatenate(([0], structure, [0])) 340 | 341 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 342 | 343 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 1, pas=pas) 344 | 345 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 346 | chose, wav, incidence, 1, method="A", pas=pas 347 | ) 348 | 349 | 350 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 1, method="T", pas=pas) 351 | 352 | if np.sum(abs(r - r_ab)) > eps: 353 | print("Problem with two layers and abeles coeff refl in TM") 354 | print(f"r = {r}, r_abeles={r_ab}") 355 | nb_prob += 1 356 | if np.sum(abs(r - r_t)) > eps: 357 | print("Problem with two layers and TMatrix coeff refl in TM") 358 | print(f"r = {r}, r_TMat={r_t}") 359 | nb_prob += 1 360 | if np.sum(abs(t - t_ab)) > eps: 361 | print("Problem with two layers and abeles coeff trans in TM") 362 | print(f"t = {t}, t_abeles={t_ab}") 363 | nb_prob += 1 364 | if np.sum(abs(t - t_t)) > eps: 365 | print("Problem with two layers and TMatrix coeff trans in TM") 366 | print(f"t = {t}, t_TMat={t_t}") 367 | nb_prob += 1 368 | 369 | if nb_prob: 370 | print() 371 | prob = True 372 | nb_prob = 0 373 | 374 | if not (prob): 375 | print("OK!") 376 | print() 377 | 378 | 379 | print("Intermediate incidence:") 380 | incidence = 15 * np.pi / 180 381 | prob = False 382 | 383 | ## Case 1: single layer, TE 384 | # structure = np.random.random(nb_couches*2+1)*w_mean 385 | structure = np.array([1]) 386 | 387 | # stack = [0]+[1,2]*nb_couches+[1,0] 388 | stack = [0, 2, 0] 389 | 390 | 391 | epaisseurs = np.concatenate(([0], structure, [0])) 392 | 393 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 394 | 395 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 0, pas=pas) 396 | 397 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 398 | chose, wav, incidence, 0, method="A", pas=pas 399 | ) 400 | 401 | 402 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 0, method="T", pas=pas) 403 | 404 | if np.sum(abs(r - r_ab)) > eps: 405 | print("Problem with single interface and abeles coeff refl in TE") 406 | print(f"r = {r}, r_abeles={r_ab}") 407 | nb_prob += 1 408 | if np.sum(abs(r - r_t)) > eps: 409 | print("Problem with single interface and TMatrix coeff refl in TE") 410 | print(f"r = {r}, r_TMat={r_t}") 411 | nb_prob += 1 412 | if np.sum(abs(t - t_ab)) > eps: 413 | print("Problem with single interface and abeles coeff trans in TE") 414 | print(f"t = {t}, t_abeles={t_ab}") 415 | nb_prob += 1 416 | if np.sum(abs(t - t_t)) > eps: 417 | print("Problem with single interface and TMatrix coeff trans in TE") 418 | print(f"t = {t}, t_TMat={t_t}") 419 | nb_prob += 1 420 | 421 | if nb_prob: 422 | print() 423 | prob = True 424 | nb_prob = 0 425 | 426 | ## Case 2: single layer, TM 427 | # structure = np.random.random(nb_couches*2+1)*w_mean 428 | structure = np.array([1]) 429 | 430 | # stack = [0]+[1,2]*nb_couches+[1,0] 431 | stack = [0, 2, 0] 432 | 433 | 434 | epaisseurs = np.concatenate(([0], structure, [0])) 435 | 436 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 437 | 438 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 1, pas=pas) 439 | 440 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 441 | chose, wav, incidence, 1, method="A", pas=pas 442 | ) 443 | 444 | 445 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 1, method="T", pas=pas) 446 | 447 | if np.sum(abs(r - r_ab)) > eps: 448 | print("Problem with single interface and abeles coeff refl in TM") 449 | print(f"r = {r}, r_abeles={r_ab}") 450 | nb_prob += 1 451 | if np.sum(abs(r - r_t)) > eps: 452 | print("Problem with single interface and TMatrix coeff refl in TM") 453 | print(f"r = {r}, r_TMat={r_t}") 454 | nb_prob += 1 455 | if np.sum(abs(t - t_ab)) > eps: 456 | print("Problem with single interface and abeles coeff trans in TM") 457 | print(f"t = {t}, t_abeles={t_ab}") 458 | nb_prob += 1 459 | if np.sum(abs(t - t_t)) > eps: 460 | print("Problem with single interface and TMatrix coeff trans in TM") 461 | print(f"t = {t}, t_TMat={t_t}") 462 | nb_prob += 1 463 | 464 | if nb_prob: 465 | print() 466 | prob = True 467 | nb_prob = 0 468 | 469 | ## Case 3: two layers, TE 470 | # structure = np.random.random(nb_couches*2+1)*w_mean 471 | structure = np.array([1, 1.5]) 472 | 473 | # stack = [0]+[1,2]*nb_couches+[1,0] 474 | stack = [0, 2, 1, 0] 475 | 476 | 477 | epaisseurs = np.concatenate(([0], structure, [0])) 478 | 479 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 480 | 481 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 0, pas=pas) 482 | 483 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 484 | chose, wav, incidence, 0, method="A", pas=pas 485 | ) 486 | 487 | 488 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 0, method="T", pas=pas) 489 | 490 | if np.sum(abs(r - r_ab)) > eps: 491 | print("Problem with two layers and abeles coeff refl in TE") 492 | print(f"r = {r}, r_abeles={r_ab}") 493 | nb_prob += 1 494 | if np.sum(abs(r - r_t)) > eps: 495 | print("Problem with two layers and TMatrix coeff refl in TE") 496 | print(f"r = {r}, r_TMat={r_t}") 497 | nb_prob += 1 498 | if np.sum(abs(t - t_ab)) > eps: 499 | print("Problem with two layers and abeles coeff trans in TE") 500 | print(f"t = {t}, t_abeles={t_ab}") 501 | nb_prob += 1 502 | if np.sum(abs(t - t_t)) > eps: 503 | print("Problem with two layers and TMatrix coeff trans in TE") 504 | print(f"t = {t}, t_TMat={t_t}") 505 | nb_prob += 1 506 | 507 | if nb_prob: 508 | print() 509 | prob = True 510 | nb_prob = 0 511 | 512 | ## Case 4: two layers, TM 513 | # structure = np.random.random(nb_couches*2+1)*w_mean 514 | structure = np.array([1, 1.5]) 515 | 516 | # stack = [0]+[1,2]*nb_couches+[1,0] 517 | stack = [0, 2, 1, 0] 518 | 519 | 520 | epaisseurs = np.concatenate(([0], structure, [0])) 521 | 522 | chose = PM.Structure(materials, stack, epaisseurs, verbose=False) 523 | 524 | r, t, R, T = PM.diff_coefficient(chose, wav, incidence, 1, pas=pas) 525 | 526 | r_ab, t_ab, R_ab, T_ab = PM.diff_coefficient( 527 | chose, wav, incidence, 1, method="A", pas=pas 528 | ) 529 | 530 | 531 | r_t, t_t, R_t, T_t = PM.diff_coefficient(chose, wav, incidence, 1, method="T", pas=pas) 532 | 533 | 534 | if np.sum(abs(r - r_ab)) > eps: 535 | print("Problem with two layers and abeles coeff refl in TM") 536 | print(f"r = {r}, r_abeles={r_ab}") 537 | nb_prob += 1 538 | if np.sum(abs(r - r_t)) > eps: 539 | print("Problem with two layers and TMatrix coeff refl in TM") 540 | print(f"r = {r}, r_TMat={r_t}") 541 | nb_prob += 1 542 | if np.sum(abs(t - t_ab)) > eps: 543 | print("Problem with two layers and abeles coeff trans in TM") 544 | print(f"t = {t}, t_abeles={t_ab}") 545 | nb_prob += 1 546 | if np.sum(abs(t - t_t)) > eps: 547 | print("Problem with two layers and TMatrix coeff trans in TM") 548 | print(f"t = {t}, t_TMat={t_t}") 549 | nb_prob += 1 550 | 551 | if nb_prob: 552 | print() 553 | prob = True 554 | nb_prob = 0 555 | 556 | if not (prob): 557 | print("OK!") 558 | print() 559 | -------------------------------------------------------------------------------- /PyMoosh/modes.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contain all functions linked to mode finding and plotting 3 | """ 4 | 5 | import numpy as np 6 | from PyMoosh.classes import conv_to_nm 7 | from PyMoosh.core import cascade 8 | import copy 9 | import itertools 10 | import matplotlib.pyplot as plt 11 | 12 | 13 | def dispersion(alpha, struct, wavelength, polarization): 14 | """ 15 | It would probably be better to compute the dispersion relation of a 16 | multilayered structure, like the determinant of the inverse of the 17 | scattering matrix. However, strangely enough, for a single interface, it 18 | just does not work. Even though the coefficients of the scattering matrix 19 | diverge the determinant does not, so that it does not work to find the 20 | surface plasmon mode, force instance. 21 | 22 | The present function actually computes the inverse of the modulus of the 23 | reflection coefficient. Since a mode is a pole of the coefficient, here it 24 | should be a zero of the resulting function. The determination of the square 25 | root is modified, so that the modes are not hidden by any cut due to the 26 | square root. 27 | 28 | Args: 29 | alpha (complex) : wavevector 30 | struct (Structure) : the object describing the multilayer 31 | wavelength : the wavelength in vacuum in nanometer 32 | polarization : 0 for TE, 1 for TM. 33 | 34 | Returns: 35 | 1/abs(r) : inverse of the modulus of the reflection coefficient. 36 | 37 | """ 38 | 39 | if struct.unit != "nm": 40 | wavelength = conv_to_nm(wavelength, struct.unit) 41 | Epsilon, Mu = struct.polarizability(wavelength) 42 | thickness = copy.deepcopy(struct.thickness) 43 | # In order to ensure that the phase reference is at the beginning 44 | # of the first layer. Totally necessary when you are looking for 45 | # modes of the structure, this makes the poles of the reflection 46 | # coefficient much more visible. 47 | thickness[0] = 0 48 | Type = struct.layer_type 49 | # The boundary conditions will change when the polarization changes. 50 | if polarization == 0: 51 | f = Mu 52 | else: 53 | f = Epsilon 54 | # Wavevector in vacuum. 55 | k_0 = 2 * np.pi / wavelength 56 | # Number of layers 57 | g = len(struct.layer_type) 58 | # Computation of the vertical wavevectors k_z 59 | gamma = np.sqrt(Epsilon[Type] * Mu[Type] * k_0**2 - np.ones(g) * alpha**2) 60 | 61 | # Changing the determination of the square root to achieve perfect stability 62 | if g > 2: 63 | gamma[1 : g - 2] = gamma[1 : g - 2] * (1 - 2 * (np.imag(gamma[1 : g - 2]) < 0)) 64 | # Changing the determination of the square root in the external medium 65 | # to better see the structure of the complex plane. 66 | gamma[0] = gamma[0] * (1 - 2 * (np.angle(gamma[0]) < -np.pi / 5)) 67 | gamma[g - 1] = gamma[g - 1] * (1 - 2 * (np.angle(gamma[g - 1]) < -np.pi / 5)) 68 | 69 | T = np.zeros(((2 * g, 2, 2)), dtype=complex) 70 | 71 | # first S matrix 72 | T[0] = [[0, 1], [1, 0]] 73 | gf = gamma / f[Type] 74 | for k in range(g - 1): 75 | # Layer scattering matrix 76 | t = np.exp((1j) * gamma[k] * thickness[k]) 77 | T[2 * k + 1] = [[0, t], [t, 0]] 78 | # Interface scattering matrix 79 | b1 = gf[k] 80 | b2 = gf[k + 1] 81 | T[2 * k + 2] = np.array([[b1 - b2, 2 * b2], [2 * b1, b2 - b1]]) / (b1 + b2) 82 | t = np.exp((1j) * gamma[g - 1] * thickness[g - 1]) 83 | T[2 * g - 1] = [[0, t], [t, 0]] 84 | # Once the scattering matrixes have been prepared, now let us combine them 85 | A = np.zeros(((2 * g - 1, 2, 2)), dtype=complex) 86 | A[0] = T[0] 87 | 88 | for j in range(len(T) - 2): 89 | A[j + 1] = cascade(A[j], T[j + 1]) 90 | # reflection coefficient of the whole structure 91 | r = A[len(A) - 1][0, 0] 92 | 93 | return 1 / np.abs(r) 94 | 95 | 96 | # return 1/r 97 | 98 | 99 | def complex_map( 100 | struct, wavelength, polarization, real_bounds, imag_bounds, n_real, n_imag 101 | ): 102 | """ 103 | Maps the function `dispersion` supposed to vanish when the dispersion 104 | relation is satisfied. 105 | 106 | Args: 107 | struct (Structure): object Structure describing the multilayer 108 | wavelength: wavelength in vacuum (in nm) 109 | polarization: 0 for TE, 1 for TM 110 | real_bounds: a list giving the bounds of the effective index 111 | real part [n_min,n_max], defining the zone to 112 | explore. 113 | imag_bounds: a list giving the bounds of the effective index 114 | imaginary part. 115 | n_real: number of points horizontally (real part) 116 | n_imag: number of points vertically (imaginary part) 117 | 118 | Returns: 119 | X (1D numpy array): values of the real part of the effective index 120 | Y (1D numpy array): values of the imaginary part of the effective index 121 | T (2D numpy array): values of the dispersion function 122 | 123 | In order to visualize the map, just use : 124 | import matplotlib.pyplot as plt 125 | plt.contourf(X,Y,np.sqrt(np.real(T))) 126 | plt.show() 127 | """ 128 | 129 | k_0 = 2 * np.pi / wavelength 130 | X = np.linspace(real_bounds[0], real_bounds[1], n_real) 131 | Y = np.linspace(imag_bounds[0], imag_bounds[1], n_imag) 132 | xa, xb = np.meshgrid(X * k_0, Y * k_0) 133 | M = xa + 1j * xb 134 | 135 | T = np.zeros((n_real, n_imag), dtype=complex) 136 | for k in range(n_real): 137 | for l in range(n_imag): 138 | T[k, l] = 1 / dispersion(M[k, l], struct, wavelength, polarization) 139 | 140 | return X, Y, T 141 | 142 | 143 | def guided_modes( 144 | struct, wavelength, polarization, neff_min, neff_max, initial_points=40 145 | ): 146 | """ 147 | This function explores the complex plane, looking for zeros of the 148 | dispersion relation. It does so by launching a steepest descent for a number 149 | `initial_points` of points on the real axis between neff_min and neff_max. 150 | 151 | 152 | Args: 153 | struct (Structure): object describing the multilayer 154 | wavelength (float): wavelength in nm 155 | polarization: 0 for TE, 1 for TM 156 | neff_min: minimum value of the effective index expected 157 | neff_max: maximum value of the effective index expected 158 | 159 | Returns: 160 | modes (list, complex): complex effective index identified as 161 | solutions of the dispersion relation. 162 | 163 | """ 164 | 165 | tolerance = 1e-10 166 | # initial_points = 40 167 | k_0 = 2 * np.pi / wavelength 168 | neff_start = np.linspace(neff_min, neff_max, initial_points, dtype=complex) 169 | modes = [] 170 | for neff in neff_start: 171 | solution = steepest(neff, tolerance, 1000, struct, wavelength, polarization) 172 | 173 | if len(modes) == 0: 174 | modes.append(solution) 175 | elif min(abs(modes - solution)) > 1e-5 * k_0: 176 | modes.append(solution) 177 | 178 | return modes 179 | 180 | 181 | def follow_guided_modes( 182 | struct, 183 | wavelength_list, 184 | polarization, 185 | neff_min, 186 | neff_max, 187 | format="n", 188 | initial_points=40, 189 | plot=True, 190 | ): 191 | """ 192 | This function explores the complex plane, looking for zeros of the 193 | dispersion relation. It does so by launching a steepest descent for a number 194 | `initial_points` of points on the real axis between neff_min and neff_max. 195 | 196 | 197 | Args: 198 | struct (Structure): object describing the multilayer 199 | wavelength_list (float): wavelengths in nm 200 | polarization: 0 for TE, 1 for TM 201 | neff_min: minimum value of the effective index expected 202 | neff_max: maximum value of the effective index expected 203 | format: index output format, n for effective index, wav for wavelength, k for wavevector 204 | 205 | Returns: 206 | modes (list of list, complex): complex effective indices identified as 207 | solutions of the dispersion relation for all wavelengths. 208 | 209 | """ 210 | 211 | if not format in ["n", "k", "wav"]: 212 | print("Unknown index format: accepted values or n, k and wav") 213 | 214 | tolerance = 1e-10 215 | # initial_points = 40 216 | wavelength = wavelength_list[0] 217 | k_0 = 2 * np.pi / wavelength 218 | neff_start = np.linspace(neff_min, neff_max, initial_points, dtype=complex) 219 | 220 | # Finding the first modes, that we will then follow 221 | first_modes = [] 222 | for neff in neff_start: 223 | solution = steepest(neff, tolerance, 1000, struct, wavelength, polarization) 224 | # print(solution) 225 | if len(first_modes) == 0: 226 | first_modes.append(solution) 227 | elif min(abs(first_modes - solution)) > 1e-5: 228 | first_modes.append(solution) 229 | modes = [first_modes] 230 | 231 | # Following these modes for all the wavelength range 232 | for i in range(1, len(wavelength_list)): 233 | wavelength = wavelength_list[i] 234 | k_0 = 2 * np.pi / wavelength 235 | neff_start = np.array(modes[-1]).flatten() 236 | new_modes = [] 237 | if len(neff_start) > 1: 238 | for neff in neff_start: 239 | solution = steepest( 240 | neff, tolerance, 1000, struct, wavelength, polarization 241 | ) 242 | if len(new_modes) == 0: 243 | new_modes.append(solution) 244 | elif min(abs(new_modes - solution)) > 1e-5: 245 | new_modes.append(solution) 246 | else: 247 | neff = neff_start 248 | solution = steepest(neff, tolerance, 1000, struct, wavelength, polarization) 249 | if len(new_modes) == 0: 250 | new_modes.append(solution) 251 | elif min(abs(new_modes - solution)) > 1e-5: 252 | new_modes.append(solution) 253 | 254 | modes.append(np.array(new_modes).flatten()) 255 | 256 | if format == "k": 257 | for i in range(len(modes)): 258 | modes[i] = np.array(modes[i]) * 2 * np.pi / wavelength_list[i] 259 | elif format == "wav": 260 | for i in range(len(modes)): 261 | modes[i] = wavelength_list[i] / np.array(modes[i]) 262 | 263 | mode_filled = np.array(list(itertools.zip_longest(*modes, fillvalue=0))) 264 | mode_filled = mode_filled.T 265 | 266 | # The following bit links modes together, for ease of use 267 | # However, it only follows the modes that exist at the smallest wavelength 268 | follow_modes = [] 269 | nb_mode = np.shape(mode_filled)[1] 270 | for shift in range(nb_mode // 2): 271 | index = ( 272 | nb_mode // 2 + shift 273 | ) # starting with the middle mode because it's the usually the most stable one 274 | mode = [mode_filled[0, index]] 275 | i = 1 276 | while i < len(mode_filled) and index >= 0 and index < nb_mode: 277 | res = np.abs(mode[-1] - mode_filled[i, index - 1 : index + 2]) 278 | if len(res) == 3: 279 | a, b, c = res 280 | if a < b and a < c: 281 | index = index - 1 282 | elif c < a and c < b: 283 | index = index + 1 284 | mode.append(mode_filled[i, index]) 285 | else: 286 | break 287 | i += 1 288 | follow_modes.append(mode) 289 | 290 | if shift > 0: 291 | index = nb_mode // 2 - shift 292 | mode = [mode_filled[0, index]] 293 | i = 1 294 | while i < len(mode_filled) and index >= 0 and index < nb_mode: 295 | res = np.abs(mode[-1] - mode_filled[i, index - 1 : index + 2]) 296 | if len(res) == 3: 297 | a, b, c = res 298 | if a < b and a < c: 299 | index = index - 1 300 | elif c < a and c < b: 301 | index = index + 1 302 | mode.append(mode_filled[i, index]) 303 | else: 304 | break 305 | i += 1 306 | follow_modes.append(mode) 307 | 308 | follow_modes = np.array(list(itertools.zip_longest(*follow_modes, fillvalue=0))) 309 | # Because we are following modes, we add 0 when the modes disappear 310 | 311 | if plot: 312 | modes_no_zero = [] 313 | for i in range(np.shape(follow_modes)[1]): 314 | j = 0 315 | while j < np.shape(follow_modes)[0] and follow_modes[j, i] != 0: 316 | j += 1 317 | modes_no_zero.append(follow_modes[: j - 1, i]) 318 | 319 | for i in range(len(modes_no_zero)): 320 | if format == "n": 321 | plt.plot( 322 | wavelength_list[: len(modes_no_zero[i])], 323 | wavelength_list[: len(modes_no_zero[i])] / modes_no_zero[i], 324 | ) 325 | plt.xlabel("Wavelength in vacuum (nm)") 326 | plt.ylabel("Effective Wavelength (nm)") 327 | elif format == "wav": 328 | plt.plot(wavelength_list[: len(modes_no_zero[i])], modes_no_zero[i]) 329 | plt.xlabel("Wavelength in vacuum (nm)") 330 | plt.ylabel("Effective Wavelength (nm)") 331 | elif format == "k": 332 | plt.plot( 333 | 2 * np.pi / wavelength_list[: len(modes_no_zero[i])], 334 | modes_no_zero[i], 335 | ) 336 | plt.xlabel("Wavevector in vacuum (nm-1)") 337 | plt.ylabel("Effective Wavevector (nm-1)") 338 | plt.show() 339 | 340 | return modes, follow_modes 341 | 342 | 343 | def steepest(start, tol, step_max, struct, wl, pol): 344 | """ 345 | Steepest descent to find a zero of the `dispersion` 346 | function. The advantage of looking for a zero is that you 347 | know when the algorithm can stop (when the value of the function 348 | is smaller than `tol`). 349 | 350 | Args: 351 | start (complex): effective index where the descent starts 352 | tol (real): when dispersion is smaller than tol, the 353 | descent stops. 354 | step_max (integer): maximum number of steps allowed 355 | struct (Structure): the object describing the multilayer 356 | wl (float): wavelength in vacuum 357 | pol: 0 for TE, 1 for TM 358 | 359 | Returns: 360 | 361 | (complex) : last effective index reached at the end of the descent 362 | 363 | """ 364 | 365 | k_0 = 2 * np.pi / wl 366 | z = start * k_0 367 | delta = abs(z) * 0.001 368 | dz = 0.01 * delta 369 | step = 0 370 | current = dispersion(z, struct, wl, pol) 371 | 372 | while (current > tol) and (step < step_max): 373 | 374 | grad = ( 375 | dispersion(z + dz, struct, wl, pol) 376 | - current 377 | # -dispersion(z-dz,struct,wl,pol) 378 | + 1j 379 | * ( 380 | dispersion(z + 1j * dz, struct, wl, pol) 381 | # -dispersion(z-1j*dz,struct,wl,pol)) 382 | - current 383 | ) 384 | ) / (dz) 385 | 386 | if abs(grad) != 0: 387 | z_new = z - delta * grad / abs(grad) 388 | else: 389 | # We have a finishing condition not linked to the gradient 390 | # So if we meet a gradient of 0, we just divide the step by two 391 | delta = delta / 2.0 392 | z_new = z 393 | 394 | value_new = dispersion(z_new, struct, wl, pol) 395 | if value_new > current: 396 | # The path not taken 397 | delta = delta / 2.0 398 | dz = dz / 2.0 399 | else: 400 | current = value_new 401 | z = z_new 402 | # print("Step", step, z,current) 403 | step = step + 1 404 | 405 | # print("End of the loop") 406 | if step == step_max: 407 | print("Warning: maximum number of steps reached. Final n_eff:", z / k_0) 408 | 409 | return z / k_0 410 | 411 | 412 | def profile(struct, n_eff, wavelength, polarization, pixel_size=3): 413 | """ 414 | This function computes the profile of the main field (Ey in TE, Hy in TM) 415 | for a mode defined by (wavelength, n_eff) 416 | 417 | Args: 418 | struct (Structure): object describing the multilayer 419 | n_eff (float): Effective index (k / k_0) of the mode 420 | wavelength (float): Wavelength (nm) in vacuum corresponding to this mode's frequency 421 | polarization (int): 0 for TE, 1 for TM 422 | pixel_size (int, optional): vertical resolution. Defaults to 3 nm. 423 | 424 | Returns: 425 | x (array(float)): positions at which the profile is computed 426 | E (array(complex)): value of the field 427 | """ 428 | if struct.unit != "nm": 429 | wavelength = conv_to_nm(wavelength, struct.unit) 430 | # Wavevector in vacuum. 431 | k_0 = 2 * np.pi / wavelength 432 | # Wavevector of the mode considered here. 433 | alpha = n_eff * k_0 434 | # About the structure: 435 | Epsilon, Mu = struct.polarizability(wavelength) 436 | thickness = copy.deepcopy(struct.thickness) 437 | Type = struct.layer_type 438 | g = len(Type) - 1 439 | # The boundary conditions will change when the polarization changes. 440 | if polarization == 0: 441 | f = Mu 442 | else: 443 | f = Epsilon 444 | # Computation of the vertical wavevectors k_z 445 | gamma = np.sqrt(Epsilon[Type] * Mu[Type] * k_0**2 - np.ones(g + 1) * alpha**2) 446 | # Changing the determination of the square root to achieve perfect stability 447 | if g > 2: 448 | gamma[1 : g - 2] = gamma[1 : g - 2] * (1 - 2 * (np.imag(gamma[1 : g - 2]) < 0)) 449 | # Don't forget the square root has to change 450 | # when the wavevector is complex (same as with 451 | # dispersion and Map) 452 | gamma[0] = gamma[0] * (1 - 2 * (np.angle(gamma[0]) < -np.pi / 5)) 453 | gamma[g] = gamma[g] * (1 - 2 * (np.angle(gamma[g]) < -np.pi / 5)) 454 | # We compute all the scattering matrixes starting with the second layer 455 | T = np.zeros((2 * g, 2, 2), dtype=complex) 456 | T[0] = [[0, 1], [1, 0]] 457 | gf = gamma / f[Type] 458 | for k in range(1, g): 459 | t = np.exp(1j * gamma[k] * thickness[k]) 460 | T[2 * k - 1] = np.array([[0, t], [t, 0]]) 461 | b1 = gf[k] 462 | b2 = gf[k + 1] 463 | T[2 * k] = np.array([[b1 - b2, 2 * b2], [2 * b1, b2 - b1]]) / (b1 + b2) 464 | t = np.exp(1j * gamma[g] * thickness[g]) 465 | T[2 * g - 1] = np.array([[0, t], [t, 0]]) 466 | 467 | H = np.zeros((len(T) - 1, 2, 2), dtype=complex) 468 | A = np.zeros((len(T) - 1, 2, 2), dtype=complex) 469 | H[0] = T[2 * g - 1] 470 | A[0] = T[0] 471 | 472 | for k in range(len(T) - 2): 473 | A[k + 1] = cascade(A[k], T[k + 1]) 474 | H[k + 1] = cascade(T[len(T) - k - 2], H[k]) 475 | 476 | I = np.zeros((len(T), 2, 2), dtype=complex) 477 | for k in range(len(T) - 1): 478 | I[k] = np.array( 479 | [ 480 | [A[k][1, 0], A[k][1, 1] * H[len(T) - k - 2][0, 1]], 481 | [A[k][1, 0] * H[len(T) - k - 2][0, 0], H[len(T) - k - 2][0, 1]], 482 | ] 483 | / (1 - A[k][1, 1] * H[len(T) - k - 2][0, 0]) 484 | ) 485 | 486 | # Coefficients, in each layer 487 | Coeffs = np.zeros((g + 1, 2), dtype=complex) 488 | Coeffs[0] = np.array([0, 1.0]) 489 | # Value of the first down propagating plane wave below 490 | # the first interface, entering the scattering matrix 491 | # for the rest of the structure. The amplitude of the 492 | # incident wave is thus not 1. 493 | b1 = gamma[0] / f[Type[0]] 494 | b2 = gamma[1] / f[Type[1]] 495 | tmp = (b2 - b1) / (2 * b2) 496 | for k in range(g): 497 | Coeffs[k + 1] = tmp * np.array([I[2 * k][0, 0], I[2 * k + 1][1, 0]]) 498 | 499 | n_pixels = np.floor(np.array(thickness) / pixel_size) 500 | n_pixels.astype(int) 501 | n_total = int(np.sum(n_pixels)) 502 | E = np.zeros(n_total, dtype=complex) 503 | h = 0.0 504 | t = 0 505 | 506 | for k in range(g + 1): 507 | for m in range(int(n_pixels[k])): 508 | h = h + pixel_size 509 | E[t] = Coeffs[k, 0] * np.exp(1j * gamma[k] * h) + Coeffs[k, 1] * np.exp( 510 | 1j * gamma[k] * (thickness[k] - h) 511 | ) 512 | t += 1 513 | h = 0 514 | 515 | x = np.linspace(0, sum(thickness), len(E)) 516 | return x, E 517 | -------------------------------------------------------------------------------- /PyMoosh/grads.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains all functions that helps with computing gradients 3 | """ 4 | 5 | # TODO: work on this 6 | 7 | import numpy as np 8 | import copy 9 | from PyMoosh.classes import conv_to_nm, Structure 10 | from PyMoosh.alt_methods import coefficient_A, coefficient_T 11 | from PyMoosh.core import coefficient_S 12 | 13 | 14 | def coefficient_with_grad_A( 15 | struct, 16 | wavelength, 17 | incidence, 18 | polarization, 19 | mode="value", 20 | i_change=-1, 21 | saved_mat=None, 22 | ): 23 | """ 24 | This function computes the reflection and transmission coefficients 25 | of the structure using the (true) Abeles matrix formalism. 26 | 27 | Args: 28 | struct (Structure): belongs to the Structure class 29 | wavelength (float): wavelength of the incidence light (in nm) 30 | incidence (float): incidence angle in radians 31 | polarization (float): 0 for TE, 1 (or anything) for TM 32 | mode (string): "value" to compute r, t, R, T, "grad" to compute them with a small parameter shift 33 | i_change (int): the layer where there is a modification 34 | saved_mat (arrays): the matrices used to compute the coefficients 35 | 36 | 37 | returns: 38 | r (complex): reflection coefficient, phase origin at first interface 39 | t (complex): transmission coefficient 40 | R (float): Reflectance (energy reflection) 41 | T (float): Transmittance (energie transmission) 42 | 43 | 44 | R and T are the energy coefficients (real quantities) 45 | 46 | .. warning: The transmission coefficients have a meaning only if the lower medium 47 | is lossless, or they have no true meaning. 48 | """ 49 | 50 | if mode == "grad" and saved_mat is None: 51 | print( 52 | "Not giving the abeles matrices to compute the gradient leads to regular computation" 53 | ) 54 | return coefficient_A(struct, wavelength, incidence, polarization) 55 | 56 | # In order to get a phase that corresponds to the expected reflected coefficient, 57 | # we make the height of the upper (lossless) medium vanish. It changes only the 58 | # phase of the reflection coefficient. 59 | 60 | # The medium may be dispersive. The permittivity and permability of each 61 | # layer has to be computed each time. 62 | if struct.unit != "nm": 63 | wavelength = conv_to_nm(wavelength, struct.unit) 64 | Epsilon, Mu = struct.polarizability(wavelength) 65 | thickness = copy.deepcopy(struct.thickness) 66 | # In order to ensure that the phase reference is at the beginning 67 | # of the first layer. 68 | thickness[0] = 0 69 | Type = struct.layer_type 70 | # The boundary conditions will change when the polarization changes. 71 | if polarization == 0: 72 | f = Mu 73 | else: 74 | f = Epsilon 75 | # Wavevector in vacuum. 76 | k_0 = 2 * np.pi / wavelength 77 | # Number of layers 78 | g = len(struct.layer_type) 79 | # Wavevector k_x, horizontal 80 | alpha = np.sqrt(Epsilon[Type[0]] * Mu[Type[0]]) * k_0 * np.sin(incidence) 81 | # Computation of the vertical wavevectors k_z 82 | gamma = np.sqrt(Epsilon[Type] * Mu[Type] * k_0**2 - np.ones(g) * alpha**2) 83 | # Be cautious if the upper medium is a negative index one. 84 | if np.real(Epsilon[Type[0]]) < 0 and np.real(Mu[Type[0]]) < 0: 85 | gamma[0] = -gamma[0] 86 | 87 | # Changing the determination of the square root to achieve perfect stability 88 | if g > 2: 89 | gamma[1 : g - 2] = gamma[1 : g - 2] * (1 - 2 * (np.imag(gamma[1 : g - 2]) < 0)) 90 | # Outgoing wave condition for the last medium 91 | if ( 92 | np.real(Epsilon[Type[g - 1]]) < 0 93 | and np.real(Mu[Type[g - 1]]) < 0 94 | and np.real(np.sqrt(Epsilon[Type[g - 1]] * Mu[Type[g - 1]] * k_0**2 - alpha**2)) 95 | != 0 96 | ): 97 | gamma[g - 1] = -np.sqrt( 98 | Epsilon[Type[g - 1]] * Mu[Type[g - 1]] * k_0**2 - alpha**2 99 | ) 100 | else: 101 | gamma[g - 1] = np.sqrt( 102 | Epsilon[Type[g - 1]] * Mu[Type[g - 1]] * k_0**2 - alpha**2 103 | ) 104 | 105 | if mode == "value": 106 | T = np.zeros(((g - 1, 2, 2)), dtype=complex) 107 | for k in range(g - 1): 108 | # Layer scattering matrix 109 | c = np.cos(gamma[k] * thickness[k]) 110 | s = np.sin(gamma[k] * thickness[k]) 111 | 112 | T[k] = [[c, -f[Type[k]] / gamma[k] * s], [gamma[k] / f[Type[k]] * s, c]] 113 | # Once the scattering matrixes have been prepared, now let us combine them 114 | 115 | A = np.empty((len(T) + 1, 2, 2), dtype=complex) 116 | B = np.empty((len(T) + 1, 2, 2), dtype=complex) 117 | A[0] = np.array([[1, 0], [0, 1]]) 118 | B[0] = np.array([[1, 0], [0, 1]]) 119 | for i in range(1, T.shape[0] + 1): 120 | A[i] = T[i - 1] @ A[i - 1] 121 | B[i] = B[i - 1] @ T[-i] 122 | 123 | a = A[-1][0, 0] 124 | b = A[-1][0, 1] 125 | c = A[-1][1, 0] 126 | d = A[-1][1, 1] 127 | 128 | amb = a - 1.0j * gamma[0] / f[Type[0]] * b 129 | apb = a + 1.0j * gamma[0] / f[Type[0]] * b 130 | cmd = c - 1.0j * gamma[0] / f[Type[0]] * d 131 | cpd = c + 1.0j * gamma[0] / f[Type[0]] * d 132 | # reflection coefficient of the whole structure 133 | 134 | r = -(cmd + 1.0j * gamma[-1] / f[Type[-1]] * amb) / ( 135 | cpd + 1.0j * gamma[-1] / f[Type[-1]] * apb 136 | ) 137 | # transmission coefficient of the whole structure 138 | t = a * (r + 1) + 1.0j * gamma[0] / f[Type[0]] * b * (r - 1) 139 | # Energy reflexion coefficient; 140 | R = np.real(abs(r) ** 2) 141 | # Energy transmission coefficient; 142 | T = np.real( 143 | abs(t) ** 2 * gamma[g - 1] * f[Type[0]] / (gamma[0] * f[Type[g - 1]]) 144 | ) 145 | 146 | return r, t, R, T, A, B 147 | 148 | if mode == "grad" and len(saved_mat) == 2: 149 | A, B = saved_mat 150 | T = np.zeros((2, 2), dtype=complex) 151 | # Layer scattering matrix 152 | c = np.cos(gamma[i_change] * thickness[i_change]) 153 | s = np.sin(gamma[i_change] * thickness[i_change]) 154 | 155 | T = [ 156 | [c, -f[Type[i_change]] / gamma[i_change] * s], 157 | [gamma[i_change] / f[Type[i_change]] * s, c], 158 | ] 159 | 160 | res = B[-i_change - 2] @ T @ A[i_change] 161 | 162 | a = res[0, 0] 163 | b = res[0, 1] 164 | c = res[1, 0] 165 | d = res[1, 1] 166 | 167 | amb = a - 1.0j * gamma[0] / f[Type[0]] * b 168 | apb = a + 1.0j * gamma[0] / f[Type[0]] * b 169 | cmd = c - 1.0j * gamma[0] / f[Type[0]] * d 170 | cpd = c + 1.0j * gamma[0] / f[Type[0]] * d 171 | # reflection coefficient of the whole structure 172 | 173 | r = -(cmd + 1.0j * gamma[-1] / f[Type[-1]] * amb) / ( 174 | cpd + 1.0j * gamma[-1] / f[Type[-1]] * apb 175 | ) 176 | # transmission coefficient of the whole structure 177 | t = a * (r + 1) + 1.0j * gamma[0] / f[Type[0]] * b * (r - 1) 178 | # Energy reflexion coefficient; 179 | R = np.real(abs(r) ** 2) 180 | # Energy transmission coefficient; 181 | T = np.real( 182 | abs(t) ** 2 * gamma[g - 1] * f[Type[0]] / (gamma[0] * f[Type[g - 1]]) 183 | ) 184 | 185 | return r, t, R, T 186 | 187 | else: 188 | print("I do not understand what you are trying to do.") 189 | 190 | 191 | def coefficient_with_grad_T( 192 | struct, 193 | wavelength, 194 | incidence, 195 | polarization, 196 | mode="value", 197 | i_change=-1, 198 | saved_mat=None, 199 | ): 200 | """ 201 | This function computes the reflection and transmission coefficients 202 | of the structure using the Transfer matrix formalism. 203 | 204 | Args: 205 | struct (Structure): belongs to the Structure class 206 | wavelength (float): wavelength of the incidence light (in nm) 207 | incidence (float): incidence angle in radians 208 | polarization (float): 0 for TE, 1 (or anything) for TM 209 | mode (string): "value" to compute r, t, R, T, "grad" to compute them with a small parameter shift 210 | i_change (int): the layer where there is a modification 211 | saved_mat (arrays): the matrices used to compute the coefficients 212 | 213 | returns: 214 | r (complex): reflection coefficient, phase origin at first interface 215 | t (complex): transmission coefficient 216 | R (float): Reflectance (energy reflection) 217 | T (float): Transmittance (energie transmission) 218 | 219 | 220 | R and T are the energy coefficients (real quantities) 221 | 222 | .. warning: The transmission coefficients have a meaning only if the lower medium 223 | is lossless, or they have no true meaning. 224 | """ 225 | 226 | if mode == "grad" and saved_mat is None: 227 | print( 228 | "Not giving the T matrices to compute the gradient leads to regular computation" 229 | ) 230 | return coefficient_T(struct, wavelength, incidence, polarization) 231 | 232 | # In order to get a phase that corresponds to the expected reflected coefficient, 233 | # we make the height of the upper (lossless) medium vanish. It changes only the 234 | # phase of the reflection coefficient. 235 | 236 | # The medium may be dispersive. The permittivity and permability of each 237 | # layer has to be computed each time. 238 | if struct.unit != "nm": 239 | wavelength = conv_to_nm(wavelength, struct.unit) 240 | Epsilon, Mu = struct.polarizability(wavelength) 241 | thickness = copy.deepcopy(struct.thickness) 242 | # In order to ensure that the phase reference is at the beginning 243 | # of the first layer. 244 | thickness[0] = 0 245 | Type = struct.layer_type 246 | # The boundary conditions will change when the polarization changes. 247 | if polarization == 0: 248 | f = Mu 249 | else: 250 | f = Epsilon 251 | # Wavevector in vacuum. 252 | k_0 = 2 * np.pi / wavelength 253 | # Number of layers 254 | g = len(struct.layer_type) 255 | # Wavevector k_x, horizontal 256 | alpha = np.sqrt(Epsilon[Type[0]] * Mu[Type[0]]) * k_0 * np.sin(incidence) 257 | # Computation of the vertical wavevectors k_z 258 | gamma = np.sqrt(Epsilon[Type] * Mu[Type] * k_0**2 - np.ones(g) * alpha**2) 259 | # Be cautious if the upper medium is a negative index one. 260 | if np.real(Epsilon[Type[0]]) < 0 and np.real(Mu[Type[0]]) < 0: 261 | gamma[0] = -gamma[0] 262 | 263 | # Changing the determination of the square root to achieve perfect stability 264 | if g > 2: 265 | gamma[1 : g - 2] = gamma[1 : g - 2] * (1 - 2 * (np.imag(gamma[1 : g - 2]) < 0)) 266 | # Outgoing wave condition for the last medium 267 | if ( 268 | np.real(Epsilon[Type[g - 1]]) < 0 269 | and np.real(Mu[Type[g - 1]]) < 0 270 | and np.real(np.sqrt(Epsilon[Type[g - 1]] * Mu[Type[g - 1]] * k_0**2 - alpha**2)) 271 | != 0 272 | ): 273 | gamma[g - 1] = -np.sqrt( 274 | Epsilon[Type[g - 1]] * Mu[Type[g - 1]] * k_0**2 - alpha**2 275 | ) 276 | else: 277 | gamma[g - 1] = np.sqrt( 278 | Epsilon[Type[g - 1]] * Mu[Type[g - 1]] * k_0**2 - alpha**2 279 | ) 280 | 281 | if mode == "value": 282 | T = np.zeros(((2 * g - 2, 2, 2)), dtype=complex) 283 | for k in range(g - 1): 284 | sum = gamma[k] / f[Type[k]] + gamma[k + 1] / f[Type[k + 1]] 285 | dif = gamma[k] / f[Type[k]] - gamma[k + 1] / f[Type[k + 1]] 286 | # print("pop", gamma[k], gamma[k+1]) 287 | # print(sum, dif) 288 | # Layer transfer matrix 289 | T[2 * k] = ( 290 | f[Type[k + 1]] 291 | / (2 * gamma[k + 1]) 292 | * np.array([[sum, -dif], [-dif, sum]]) 293 | ) 294 | 295 | phase = 1.0j * gamma[k + 1] * thickness[k + 1] 296 | # Layer propagation matrix 297 | T[2 * k + 1] = [[np.exp(-phase), 0], [0, np.exp(phase)]] 298 | # Once the scattering matrixes have been prepared, now let us combine them 299 | 300 | A = np.empty((len(T) + 1, 2, 2), dtype=complex) 301 | B = np.empty((len(T) + 1, 2, 2), dtype=complex) 302 | A[0] = np.array([[1, 0], [0, 1]]) 303 | B[0] = np.array([[1, 0], [0, 1]]) 304 | for i in range(1, T.shape[0] + 1): 305 | A[i] = A[i - 1] @ T[i - 1] 306 | B[i] = T[-i] @ B[i - 1] 307 | r = -A[-1][1, 0] / A[-1][0, 0] 308 | # transmission coefficient of the whole structure 309 | t = A[-1][1, 1] - (A[-1][1, 0] * A[-1][0, 1]) / A[-1][0, 0] 310 | # Energy reflexion coefficient; 311 | R = np.real(abs(r) ** 2) 312 | # Energy transmission coefficient; 313 | T = np.real( 314 | abs(t) ** 2 * gamma[g - 1] * f[Type[0]] / (gamma[0] * f[Type[g - 1]]) 315 | ) 316 | 317 | return r, t, R, T, A, B 318 | 319 | if mode == "grad" and len(saved_mat) == 2: 320 | A, B = saved_mat 321 | 322 | if 0 < i_change: 323 | T = np.zeros(((3, 2, 2)), dtype=complex) 324 | 325 | sum = ( 326 | gamma[i_change - 1] / f[Type[i_change - 1]] 327 | + gamma[i_change] / f[Type[i_change]] 328 | ) 329 | dif = ( 330 | gamma[i_change - 1] / f[Type[i_change - 1]] 331 | - gamma[i_change] / f[Type[i_change]] 332 | ) 333 | # Layer transfer matrix 334 | T[0] = ( 335 | f[Type[i_change]] 336 | / (2 * gamma[i_change]) 337 | * np.array([[sum, -dif], [-dif, sum]]) 338 | ) 339 | 340 | phase = 1.0j * gamma[i_change] * thickness[i_change] 341 | # Layer propagation matrix 342 | T[1] = [[np.exp(-phase), 0], [0, np.exp(phase)]] 343 | 344 | sum = ( 345 | gamma[i_change] / f[Type[i_change]] 346 | + gamma[i_change + 1] / f[Type[i_change + 1]] 347 | ) 348 | dif = ( 349 | gamma[i_change] / f[Type[i_change]] 350 | - gamma[i_change + 1] / f[Type[i_change + 1]] 351 | ) 352 | # print("pop", gamma[k], gamma[k+1]) 353 | # print(sum, dif) 354 | # Layer transfer matrix 355 | T[2] = ( 356 | f[Type[i_change + 1]] 357 | / (2 * gamma[i_change + 1]) 358 | * np.array([[sum, -dif], [-dif, sum]]) 359 | ) 360 | # Once the scattering matrixes have been prepared, now let us combine them 361 | 362 | res = A[i_change * 2 - 2] @ T[0] @ T[1] @ T[2] @ B[-(i_change) * 2 - 2] 363 | 364 | else: 365 | T = np.zeros(((2, 2, 2)), dtype=complex) 366 | 367 | phase = 1.0j * gamma[i_change] * thickness[i_change] 368 | # Layer propagation matrix 369 | T[0] = [[np.exp(-phase), 0], [0, np.exp(phase)]] 370 | 371 | sum = ( 372 | gamma[i_change] / f[Type[i_change]] 373 | + gamma[i_change + 1] / f[Type[i_change + 1]] 374 | ) 375 | dif = ( 376 | gamma[i_change] / f[Type[i_change]] 377 | - gamma[i_change + 1] / f[Type[i_change + 1]] 378 | ) 379 | # print("pop", gamma[k], gamma[k+1]) 380 | # print(sum, dif) 381 | # Layer transfer matrix 382 | T[1] = ( 383 | f[Type[i_change + 1]] 384 | / (2 * gamma[i_change + 1]) 385 | * np.array([[sum, -dif], [-dif, sum]]) 386 | ) 387 | # Once the scattering matrixes have been prepared, now let us combine them 388 | res = A[i_change * 2] @ T[0] @ T[1] @ B[-(i_change) * 2 - 2] 389 | 390 | # print(T) 391 | # print(A) 392 | # reflection coefficient of the whole structure 393 | r = -res[1, 0] / res[0, 0] 394 | # transmission coefficient of the whole structure 395 | t = res[1, 1] - (res[1, 0] * res[0, 1]) / res[0, 0] 396 | # Energy reflexion coefficient; 397 | R = np.real(abs(r) ** 2) 398 | # Energy transmission coefficient; 399 | T = np.real( 400 | abs(t) ** 2 * gamma[g - 1] * f[Type[0]] / (gamma[0] * f[Type[g - 1]]) 401 | ) 402 | 403 | return r, t, R, T 404 | 405 | 406 | def diff_coefficient(struct, wavelength, incidence, polarization, method="S", pas=0.01): 407 | """ 408 | This function computes the reflection and transmission coefficients derivative 409 | of the structure using the Transfer matrix formalism. 410 | 411 | Args: 412 | struct (Structure): belongs to the Structure class 413 | wavelength (float): wavelength of the incidence light (in nm) 414 | incidence (float): incidence angle in radians 415 | polarization (float): 0 for TE, 1 (or anything) for TM 416 | method (string): T for T matrix, A for abeles matrix 417 | 418 | returns: 419 | dr (complex): deriv. of reflection coefficient, phase origin at first interface 420 | dt (complex): deriv. of transmission coefficient 421 | dR (float): deriv. of Reflectance (energy reflection) 422 | dT (float): deriv. of Transmittance (energie transmission) 423 | 424 | 425 | R and T are the energy coefficients (real quantities) 426 | 427 | .. warning: The transmission coefficients have a meaning only if the lower medium 428 | is lossless, or they have no true meaning. 429 | """ 430 | 431 | nb_var = len(struct.thickness) - 2 + len(struct.layer_type) - 2 432 | base_thickness = struct.thickness 433 | base_mat = np.array(struct.materials) 434 | base_lay = struct.layer_type 435 | 436 | dr = np.zeros(nb_var, dtype=complex) 437 | dt = np.zeros(nb_var, dtype=complex) 438 | dR = np.zeros(nb_var, dtype=float) 439 | dT = np.zeros(nb_var, dtype=float) 440 | 441 | function = None 442 | 443 | if method == "T": 444 | function = coefficient_with_grad_T 445 | elif method == "A": 446 | function = coefficient_with_grad_A 447 | 448 | if not function is None: 449 | # Using one of the fast differentiation methods 450 | r, t, R, T, A, B = function( 451 | struct, wavelength, incidence, polarization, mode="value" 452 | ) 453 | 454 | for i in range(len(base_thickness) - 2): 455 | thickness = base_thickness.copy() 456 | thickness[i + 1] += pas 457 | struct = Structure(base_mat, base_lay, thickness, verbose=False) 458 | r_pas, t_pas, R_pas, T_pas = function( 459 | struct, 460 | wavelength, 461 | incidence, 462 | polarization, 463 | mode="grad", 464 | i_change=i + 1, 465 | saved_mat=[A, B], 466 | ) 467 | dr[i] = (r_pas - r) / pas 468 | dt[i] = (t_pas - t) / pas 469 | dR[i] = (R_pas - R) / pas 470 | dT[i] = (T_pas - T) / pas 471 | 472 | for i in range(len(base_mat) - 2): 473 | mat = list(base_mat.copy()) 474 | layer_type = base_lay.copy() 475 | 476 | mat.append(mat[base_lay[i + 1]].permittivity + pas) 477 | # Creating a new permittivity and referencing it 478 | layer_type[i + 1] = len(mat) - 1 479 | 480 | struct = Structure(mat, layer_type, base_thickness, verbose=False) 481 | r_pas, t_pas, R_pas, T_pas = function( 482 | struct, 483 | wavelength, 484 | incidence, 485 | polarization, 486 | mode="grad", 487 | i_change=i + 1, 488 | saved_mat=[A, B], 489 | ) 490 | dr[i + len(base_thickness) - 2] = (r_pas - r) / pas 491 | dt[i + len(base_thickness) - 2] = (t_pas - t) / pas 492 | dR[i + len(base_thickness) - 2] = (R_pas - R) / pas 493 | dT[i + len(base_thickness) - 2] = (T_pas - T) / pas 494 | 495 | return dr, dt, dR, dT 496 | 497 | # Not using the fast computation -> using S matrix formalism 498 | function = coefficient_S 499 | ( 500 | r, 501 | t, 502 | R, 503 | T, 504 | ) = function(struct, wavelength, incidence, polarization) 505 | 506 | for i in range(len(base_thickness) - 2): 507 | thickness = base_thickness.copy() 508 | thickness[i + 1] += pas 509 | struct = Structure(base_mat, base_lay, thickness, verbose=False) 510 | r_pas, t_pas, R_pas, T_pas = function( 511 | struct, wavelength, incidence, polarization 512 | ) 513 | dr[i] = (r_pas - r) / pas 514 | dt[i] = (t_pas - t) / pas 515 | dR[i] = (R_pas - R) / pas 516 | dT[i] = (T_pas - T) / pas 517 | 518 | for i in range(len(base_mat) - 2): 519 | mat = list(base_mat.copy()) 520 | layer_type = base_lay.copy() 521 | 522 | mat.append(mat[base_lay[i + 1]].permittivity + pas) 523 | # Creating a new permittivity and referencing it 524 | layer_type[i + 1] = len(mat) - 1 525 | 526 | struct = Structure(mat, layer_type, base_thickness, verbose=False) 527 | r_pas, t_pas, R_pas, T_pas = function( 528 | struct, wavelength, incidence, polarization 529 | ) 530 | dr[i + len(base_thickness) - 2] = (r_pas - r) / pas 531 | dR[i + len(base_thickness) - 2] = (R_pas - R) / pas 532 | dt[i + len(base_thickness) - 2] = (t_pas - t) / pas 533 | dT[i + len(base_thickness) - 2] = (T_pas - T) / pas 534 | 535 | return dr, dt, dR, dT 536 | --------------------------------------------------------------------------------