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/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
+ )