ElectroMagneticPython 2.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- EMpy/RCWA.py +885 -0
- EMpy/__init__.py +39 -0
- EMpy/constants.py +25 -0
- EMpy/devices.py +1414 -0
- EMpy/materials.py +371 -0
- EMpy/modesolvers/FD.py +2536 -0
- EMpy/modesolvers/FMM.py +2422 -0
- EMpy/modesolvers/__init__.py +13 -0
- EMpy/modesolvers/geometries.py +231 -0
- EMpy/modesolvers/interface.py +71 -0
- EMpy/scattering.py +236 -0
- EMpy/transfer_matrix.py +423 -0
- EMpy/utils.py +1342 -0
- EMpy/version.py +1 -0
- ElectroMagneticPython-2.1.4.dist-info/LICENSE +21 -0
- ElectroMagneticPython-2.1.4.dist-info/METADATA +95 -0
- ElectroMagneticPython-2.1.4.dist-info/RECORD +22 -0
- ElectroMagneticPython-2.1.4.dist-info/WHEEL +5 -0
- ElectroMagneticPython-2.1.4.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/materials_test.py +60 -0
- tests/utils_test.py +23 -0
EMpy/materials.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# pylint: disable=W0622,R0903,R0902,R0913,W0633
|
|
2
|
+
|
|
3
|
+
"""Functions and objects to manipulate materials.
|
|
4
|
+
|
|
5
|
+
A material is an object with a refractive index function.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from functools import partial
|
|
9
|
+
|
|
10
|
+
import numpy
|
|
11
|
+
from scipy.integrate import quad
|
|
12
|
+
from EMpy.constants import eps0
|
|
13
|
+
|
|
14
|
+
__author__ = "Lorenzo Bolla"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Material:
|
|
18
|
+
|
|
19
|
+
"""Generic class to handle materials.
|
|
20
|
+
|
|
21
|
+
This class is intended to be subclassed to obtain isotropic and
|
|
22
|
+
anisotropic materials.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, name=""):
|
|
26
|
+
"""Set material name."""
|
|
27
|
+
self.name = name
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RefractiveIndex:
|
|
31
|
+
|
|
32
|
+
"""Refractive Index.
|
|
33
|
+
|
|
34
|
+
Unaware of temperature.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
Provide ONE of the following named-arguments:
|
|
39
|
+
|
|
40
|
+
n0_const : float
|
|
41
|
+
A single-value of refractive index, to be used regardless of
|
|
42
|
+
the wavelength requested.
|
|
43
|
+
For example:
|
|
44
|
+
>>> n0_const = 1.448 for SiO2
|
|
45
|
+
|
|
46
|
+
n0_poly : list/tuple
|
|
47
|
+
Use a polynomial rix dispersion function: provide the
|
|
48
|
+
polynomial coefficients to be evaluated by numpy.polyval.
|
|
49
|
+
# sets the refractive index function as n = 9(wl**3) +
|
|
50
|
+
# 5(wl**2) + 3(wl) + 1
|
|
51
|
+
For example:
|
|
52
|
+
>>> n0_poly = (9,5,3,1)
|
|
53
|
+
|
|
54
|
+
n0_smcoeffs (Sellmeier coefficients): 6-element list/tuple
|
|
55
|
+
Set the rix dispersion function to the 6-parameter Sellmeier
|
|
56
|
+
function as so:
|
|
57
|
+
n(wls) = sqrt(1. +
|
|
58
|
+
B1 * wls ** 2 / (wls ** 2 - C1) +
|
|
59
|
+
B2 * wls ** 2 / (wls ** 2 - C2) +
|
|
60
|
+
B3 * wls ** 2 / (wls ** 2 - C3))
|
|
61
|
+
For example:
|
|
62
|
+
>>> n0_smcoeffs = [B1, B2, B3, C1, C2, C3] # six values total
|
|
63
|
+
|
|
64
|
+
n0_func : function
|
|
65
|
+
Provide an arbitrary function to return the refractive index
|
|
66
|
+
versus wavelength.
|
|
67
|
+
For example:
|
|
68
|
+
>>> def SiN_func(wl):
|
|
69
|
+
>>> x = wl * 1e6 # convert to microns
|
|
70
|
+
>>> return 1.887 + 0.01929/x**2 + 1.6662e-4/x**4 # Cauchy func
|
|
71
|
+
>>> SiN_rix = RefractiveIndex(n0_func=SiN_func)
|
|
72
|
+
or
|
|
73
|
+
>>> SiN_rix = RefractiveIndex(
|
|
74
|
+
n0_func=lambda wl: 1.887 + 0.01929/(wl*1e6)**2 +
|
|
75
|
+
1.6662e-4/(wl*1e6)**4)
|
|
76
|
+
|
|
77
|
+
Your function should return a `numpy.array`, since it will be
|
|
78
|
+
passed a `numpy.array` of the wavelengths requested. This
|
|
79
|
+
conversion to `array` happens automatically if your function
|
|
80
|
+
does math on the wavelength.
|
|
81
|
+
|
|
82
|
+
n0_known : dictionary
|
|
83
|
+
Use if RefractiveIndex will only be evaluated at a specific set of `wls`.
|
|
84
|
+
n0_known should be a dictionary of `key:value` pairs
|
|
85
|
+
corresponding to `wavelength:rix`, for example:
|
|
86
|
+
>>> n0_known = { 1500e-9:1.445, 1550e-9:1.446, 1600e-9:1.447 }
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self, n0_const=None, n0_poly=None, n0_smcoeffs=None, n0_known=None, n0_func=None
|
|
92
|
+
):
|
|
93
|
+
|
|
94
|
+
if n0_const is not None:
|
|
95
|
+
self.get_rix = partial(self.__from_const, n0_const)
|
|
96
|
+
|
|
97
|
+
elif n0_poly is not None:
|
|
98
|
+
self.get_rix = partial(self.__from_poly, n0_poly)
|
|
99
|
+
|
|
100
|
+
elif n0_smcoeffs is not None:
|
|
101
|
+
self.get_rix = partial(self.__from_sellmeier, n0_smcoeffs)
|
|
102
|
+
|
|
103
|
+
elif n0_func is not None:
|
|
104
|
+
self.get_rix = partial(self.__from_function, n0_func)
|
|
105
|
+
|
|
106
|
+
elif n0_known is not None:
|
|
107
|
+
self.get_rix = partial(self.__from_known, n0_known)
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
raise ValueError("Please provide at least one parameter.")
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def __from_const(n0, wls):
|
|
114
|
+
wls = numpy.atleast_1d(wls)
|
|
115
|
+
return n0 * numpy.ones_like(wls)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def __from_poly(n0_poly, wls):
|
|
119
|
+
wls = numpy.atleast_1d(wls)
|
|
120
|
+
return numpy.polyval(n0_poly, wls) * numpy.ones_like(wls)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def __from_sellmeier(n0_smcoeffs, wls):
|
|
124
|
+
wls = numpy.atleast_1d(wls)
|
|
125
|
+
B1, B2, B3, C1, C2, C3 = n0_smcoeffs
|
|
126
|
+
return numpy.sqrt(
|
|
127
|
+
1.0
|
|
128
|
+
+ B1 * wls**2 / (wls**2 - C1)
|
|
129
|
+
+ B2 * wls**2 / (wls**2 - C2)
|
|
130
|
+
+ B3 * wls**2 / (wls**2 - C3)
|
|
131
|
+
) * numpy.ones_like(wls)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def __from_function(n0_func, wls):
|
|
135
|
+
# ensure wls is array
|
|
136
|
+
wls = numpy.atleast_1d(wls)
|
|
137
|
+
return n0_func(wls) * numpy.ones_like(wls)
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def __from_known(n0_known, wls):
|
|
141
|
+
wls = numpy.atleast_1d(wls)
|
|
142
|
+
# Note: we should interpolate...
|
|
143
|
+
return numpy.array([n0_known.get(wl, 0) for wl in wls])
|
|
144
|
+
|
|
145
|
+
def __call__(self, wls):
|
|
146
|
+
return self.get_rix(wls)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ThermalOpticCoefficient:
|
|
150
|
+
|
|
151
|
+
"""Thermal Optic Coefficient."""
|
|
152
|
+
|
|
153
|
+
def __init__(self, data=None, T0=300):
|
|
154
|
+
self.__data = data
|
|
155
|
+
self.T0 = T0
|
|
156
|
+
|
|
157
|
+
def TOC(self, T):
|
|
158
|
+
if self.__data is not None:
|
|
159
|
+
return numpy.polyval(self.__data, T)
|
|
160
|
+
else:
|
|
161
|
+
return 0.0
|
|
162
|
+
|
|
163
|
+
def __call__(self, T):
|
|
164
|
+
return self.TOC(T)
|
|
165
|
+
|
|
166
|
+
def dnT(self, T):
|
|
167
|
+
"""Integrate the TOC to get the rix variation."""
|
|
168
|
+
return quad(self.TOC, self.T0, T)[0]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class IsotropicMaterial(Material):
|
|
172
|
+
|
|
173
|
+
"""Subclasses Material to describe isotropic materials.
|
|
174
|
+
|
|
175
|
+
Frequency dispersion and thermic aware.
|
|
176
|
+
In all the member functions, wls must be an ndarray.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self, name="", n0=RefractiveIndex(n0_const=1.0), toc=ThermalOpticCoefficient()
|
|
181
|
+
):
|
|
182
|
+
"""Set name, default temperature, refractive index and TOC
|
|
183
|
+
(thermal optic coefficient)."""
|
|
184
|
+
Material.__init__(self, name)
|
|
185
|
+
self.n0 = n0
|
|
186
|
+
self.toc = toc
|
|
187
|
+
|
|
188
|
+
def n(self, wls, T=None):
|
|
189
|
+
"""Return the refractive index at T as a [1 x wls] array."""
|
|
190
|
+
if T is None:
|
|
191
|
+
T = self.toc.T0
|
|
192
|
+
return self.n0(wls) + self.toc.dnT(T)
|
|
193
|
+
|
|
194
|
+
def epsilon(self, wls, T=None):
|
|
195
|
+
"""Return the epsilon at T as a [1 x wls] array."""
|
|
196
|
+
if T is None:
|
|
197
|
+
T = self.toc.T0
|
|
198
|
+
return self.n(wls, T) ** 2 * eps0
|
|
199
|
+
|
|
200
|
+
def epsilonTensor(self, wls, T=None):
|
|
201
|
+
"""Return the epsilon at T as a [3 x 3 x wls] array."""
|
|
202
|
+
if T is None:
|
|
203
|
+
T = self.toc.T0
|
|
204
|
+
tmp = numpy.eye(3)
|
|
205
|
+
return tmp[:, :, numpy.newaxis] * self.epsilon(wls, T)
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def isIsotropic():
|
|
209
|
+
"""Return True, because the material is isotropic."""
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
def __str__(self):
|
|
213
|
+
"""Return material name."""
|
|
214
|
+
return self.name + ", isotropic"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class EpsilonTensor:
|
|
218
|
+
def __init__(
|
|
219
|
+
self, epsilon_tensor_const=eps0 * numpy.eye(3), epsilon_tensor_known=None
|
|
220
|
+
):
|
|
221
|
+
if epsilon_tensor_known is None:
|
|
222
|
+
epsilon_tensor_known = {}
|
|
223
|
+
self.epsilon_tensor_const = epsilon_tensor_const
|
|
224
|
+
self.epsilon_tensor_known = epsilon_tensor_known
|
|
225
|
+
|
|
226
|
+
def __call__(self, wls):
|
|
227
|
+
"""Return the epsilon tensor as a [3 x 3 x wls.size] matrix."""
|
|
228
|
+
wls = numpy.atleast_1d(wls)
|
|
229
|
+
if wls.size == 1:
|
|
230
|
+
if wls.item() in self.epsilon_tensor_known:
|
|
231
|
+
return self.epsilon_tensor_known[wls.item()][:, :, numpy.newaxis]
|
|
232
|
+
return self.epsilon_tensor_const[:, :, numpy.newaxis] * numpy.ones_like(wls)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class AnisotropicMaterial(Material):
|
|
236
|
+
|
|
237
|
+
"""Subclass Material to describe anisotropic materials.
|
|
238
|
+
|
|
239
|
+
No frequency dispersion nor thermic aware.
|
|
240
|
+
In all the member functions, wls must be an ndarray.
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def __init__(self, name="", epsilon_tensor=EpsilonTensor()):
|
|
245
|
+
"""Set name and default epsilon tensor."""
|
|
246
|
+
Material.__init__(self, name)
|
|
247
|
+
self.epsilonTensor = epsilon_tensor
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def isIsotropic():
|
|
251
|
+
"""Return False, because the material is anisotropic."""
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
def __str__(self):
|
|
255
|
+
"""Return material name."""
|
|
256
|
+
return self.name + ", anisotropic"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# Vacuum
|
|
260
|
+
Vacuum = IsotropicMaterial(name="Vacuum")
|
|
261
|
+
|
|
262
|
+
# Air
|
|
263
|
+
Air = IsotropicMaterial(name="Air")
|
|
264
|
+
|
|
265
|
+
# Silicon
|
|
266
|
+
Si = IsotropicMaterial(
|
|
267
|
+
name="Silicon",
|
|
268
|
+
n0=RefractiveIndex(n0_poly=(0.076006e12, -0.31547e6, 3.783)),
|
|
269
|
+
toc=ThermalOpticCoefficient((-1.49e-10, 3.47e-7, 9.48e-5)),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# SiO2
|
|
273
|
+
SiO2 = IsotropicMaterial(
|
|
274
|
+
name="Silica",
|
|
275
|
+
n0=RefractiveIndex(n0_const=1.446),
|
|
276
|
+
toc=ThermalOpticCoefficient((1.1e-4,)),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# BK7 glass (see http://en.wikipedia.org/wiki/Sellmeier_equation)
|
|
280
|
+
BK7 = IsotropicMaterial(
|
|
281
|
+
name="Borosilicate crown glass",
|
|
282
|
+
n0=RefractiveIndex(
|
|
283
|
+
n0_smcoeffs=(
|
|
284
|
+
1.03961212,
|
|
285
|
+
2.31792344e-1,
|
|
286
|
+
1.01046945,
|
|
287
|
+
6.00069867e-15,
|
|
288
|
+
2.00179144e-14,
|
|
289
|
+
1.03560653e-10,
|
|
290
|
+
)
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class LiquidCrystal(Material):
|
|
296
|
+
|
|
297
|
+
"""Liquid Crystal.
|
|
298
|
+
|
|
299
|
+
A liquid crystal is determined by it ordinary and extraordinary
|
|
300
|
+
refractive indices, its elastic tensor and its chirality.
|
|
301
|
+
Inspiration here:
|
|
302
|
+
U{http://www.ee.ucl.ac.uk/~rjames/modelling/constant-order/oned/}.
|
|
303
|
+
|
|
304
|
+
@ivar name: Liquid Crystal name.
|
|
305
|
+
@ivar nO: Ordinary refractive index.
|
|
306
|
+
@ivar nE: Extraordinary refractive index.
|
|
307
|
+
@ivar K11: Elastic tensor, first component.
|
|
308
|
+
@ivar K22: Elastic tensor, second component.
|
|
309
|
+
@ivar K33: Elastic tensor, third component.
|
|
310
|
+
@ivar q0: Chirality.
|
|
311
|
+
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
def __init__(
|
|
315
|
+
self,
|
|
316
|
+
name="",
|
|
317
|
+
nO=1.0,
|
|
318
|
+
nE=1.0,
|
|
319
|
+
nO_electrical=1.0,
|
|
320
|
+
nE_electrical=1.0,
|
|
321
|
+
K11=0.0,
|
|
322
|
+
K22=0.0,
|
|
323
|
+
K33=0.0,
|
|
324
|
+
q0=0.0,
|
|
325
|
+
):
|
|
326
|
+
"""Set name, the refractive indices, the elastic constants and
|
|
327
|
+
the chirality."""
|
|
328
|
+
|
|
329
|
+
Material.__init__(self, name)
|
|
330
|
+
self.nO = nO
|
|
331
|
+
self.nE = nE
|
|
332
|
+
self.nO_electrical = nO_electrical
|
|
333
|
+
self.nE_electrical = nE_electrical
|
|
334
|
+
self.K11 = K11
|
|
335
|
+
self.K22 = K22
|
|
336
|
+
self.K33 = K33
|
|
337
|
+
self.q0 = q0
|
|
338
|
+
self.epslow = self.nO_electrical**2
|
|
339
|
+
self.deleps = self.nE_electrical**2 - self.epslow
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def get_10400_000_100(conc000):
|
|
343
|
+
"""Return a LiquidCrystal made of conc% 000 and (100-conc)% 100."""
|
|
344
|
+
|
|
345
|
+
conc = [0, 100]
|
|
346
|
+
epsO_electrical = [3.38, 3.28]
|
|
347
|
+
epsE_electrical = [5.567, 5.867]
|
|
348
|
+
epsO = [1.47551**2, 1.46922**2]
|
|
349
|
+
epsE = [1.61300**2, 1.57016**2]
|
|
350
|
+
|
|
351
|
+
K11 = 13.5e-12 # elastic constant [N] (splay)
|
|
352
|
+
K22 = 6.5e-12 # elastic constant [N] (twist)
|
|
353
|
+
K33 = 20e-12 # elastic constant [N] (bend)
|
|
354
|
+
q0 = 0 # chirality 2*pi/pitch
|
|
355
|
+
|
|
356
|
+
nO_electrical_ = numpy.interp(conc000, conc, epsO_electrical) ** 0.5
|
|
357
|
+
nE_electrical_ = numpy.interp(conc000, conc, epsE_electrical) ** 0.5
|
|
358
|
+
nO_ = numpy.interp(conc000, conc, epsO) ** 0.5
|
|
359
|
+
nE_ = numpy.interp(conc000, conc, epsE) ** 0.5
|
|
360
|
+
|
|
361
|
+
return LiquidCrystal(
|
|
362
|
+
"10400_000_100_" + str(conc000) + "_" + str(100 - conc000),
|
|
363
|
+
nO_,
|
|
364
|
+
nE_,
|
|
365
|
+
nO_electrical_,
|
|
366
|
+
nE_electrical_,
|
|
367
|
+
K11,
|
|
368
|
+
K22,
|
|
369
|
+
K33,
|
|
370
|
+
q0,
|
|
371
|
+
)
|