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/utils.py
ADDED
|
@@ -0,0 +1,1342 @@
|
|
|
1
|
+
# pylint: disable=R0913,R0914,W0201,W0622,C0302,R0902,R0903,W1001,W0612,W0613
|
|
2
|
+
|
|
3
|
+
"""Useful functions and objects used more or less everywhere."""
|
|
4
|
+
|
|
5
|
+
__author__ = "Lorenzo Bolla"
|
|
6
|
+
|
|
7
|
+
import numpy
|
|
8
|
+
import EMpy.constants
|
|
9
|
+
import EMpy.materials
|
|
10
|
+
import scipy.linalg
|
|
11
|
+
import scipy.interpolate
|
|
12
|
+
import scipy.optimize
|
|
13
|
+
import time
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Layer:
|
|
18
|
+
|
|
19
|
+
"""A layer is defined by a material (iso or aniso) and a thickness."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, mat, thickness):
|
|
22
|
+
"""Set the material and the thickness."""
|
|
23
|
+
|
|
24
|
+
self.mat = mat
|
|
25
|
+
self.thickness = thickness
|
|
26
|
+
|
|
27
|
+
def isIsotropic(self):
|
|
28
|
+
"""Return True if the material is isotropic, False if anisotropic."""
|
|
29
|
+
|
|
30
|
+
return self.mat.isIsotropic()
|
|
31
|
+
|
|
32
|
+
def getEPSFourierCoeffs(self, wl, n, anisotropic=True):
|
|
33
|
+
"""Return the Fourier coefficients of eps and eps**-1, orders [-n,n]."""
|
|
34
|
+
|
|
35
|
+
nood = 2 * n + 1
|
|
36
|
+
hmax = nood - 1
|
|
37
|
+
if not anisotropic:
|
|
38
|
+
# isotropic
|
|
39
|
+
EPS = numpy.zeros(2 * hmax + 1, dtype=complex)
|
|
40
|
+
EPS1 = numpy.zeros_like(EPS)
|
|
41
|
+
rix = self.mat.n(wl)
|
|
42
|
+
EPS[hmax] = rix**2
|
|
43
|
+
EPS1[hmax] = rix**-2
|
|
44
|
+
return EPS, EPS1
|
|
45
|
+
else:
|
|
46
|
+
# anisotropic
|
|
47
|
+
EPS = numpy.zeros((3, 3, 2 * hmax + 1), dtype=complex)
|
|
48
|
+
EPS1 = numpy.zeros_like(EPS)
|
|
49
|
+
EPS[:, :, hmax] = (
|
|
50
|
+
numpy.squeeze(self.mat.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
51
|
+
)
|
|
52
|
+
EPS1[:, :, hmax] = scipy.linalg.inv(EPS[:, :, hmax])
|
|
53
|
+
return EPS, EPS1
|
|
54
|
+
|
|
55
|
+
def capacitance(self, area=1.0, wl=0):
|
|
56
|
+
"""Capacitance = eps0 * eps_r * area / thickness."""
|
|
57
|
+
|
|
58
|
+
if self.isIsotropic():
|
|
59
|
+
eps = EMpy.constants.eps0 * numpy.real(self.mat.n(wl).item() ** 2)
|
|
60
|
+
else:
|
|
61
|
+
# suppose to compute the capacitance along the z-axis
|
|
62
|
+
eps = self.mat.epsilonTensor(wl)[2, 2, 0]
|
|
63
|
+
|
|
64
|
+
return eps * area / self.thickness
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
"""Return the description of a layer."""
|
|
68
|
+
|
|
69
|
+
return "%s, thickness: %g" % (self.mat, self.thickness)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BinaryGrating:
|
|
73
|
+
"""A Binary Grating is defined by two materials (iso or aniso), a
|
|
74
|
+
duty cycle, a pitch and a thickness."""
|
|
75
|
+
|
|
76
|
+
def __init__(self, mat1, mat2, dc, pitch, thickness):
|
|
77
|
+
"""Set the materials, the duty cycle and the thickness."""
|
|
78
|
+
self.mat1 = mat1
|
|
79
|
+
self.mat2 = mat2
|
|
80
|
+
self.dc = dc
|
|
81
|
+
self.pitch = pitch
|
|
82
|
+
self.thickness = thickness
|
|
83
|
+
|
|
84
|
+
def isIsotropic(self):
|
|
85
|
+
"""Return True if both the materials are isotropic, False otherwise."""
|
|
86
|
+
return self.mat1.isIsotropic() and self.mat2.isIsotropic()
|
|
87
|
+
|
|
88
|
+
def getEPSFourierCoeffs(self, wl, n, anisotropic=True):
|
|
89
|
+
"""Return the Fourier coefficients of eps and eps**-1, orders [-n,n]."""
|
|
90
|
+
nood = 2 * n + 1
|
|
91
|
+
hmax = nood - 1
|
|
92
|
+
if not anisotropic:
|
|
93
|
+
# isotropic
|
|
94
|
+
rix1 = self.mat1.n(wl)
|
|
95
|
+
rix2 = self.mat2.n(wl)
|
|
96
|
+
f = self.dc
|
|
97
|
+
h = numpy.arange(-hmax, hmax + 1)
|
|
98
|
+
EPS = (rix1**2 - rix2**2) * f * numpy.sinc(h * f) + rix2**2 * (h == 0)
|
|
99
|
+
EPS1 = (rix1**-2 - rix2**-2) * f * numpy.sinc(h * f) + rix2**-2 * (
|
|
100
|
+
h == 0
|
|
101
|
+
)
|
|
102
|
+
return EPS, EPS1
|
|
103
|
+
else:
|
|
104
|
+
# anisotropic
|
|
105
|
+
EPS = numpy.zeros((3, 3, 2 * hmax + 1), dtype=complex)
|
|
106
|
+
EPS1 = numpy.zeros_like(EPS)
|
|
107
|
+
eps1 = numpy.squeeze(self.mat1.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
108
|
+
eps2 = numpy.squeeze(self.mat2.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
109
|
+
f = self.dc
|
|
110
|
+
h = numpy.arange(-hmax, hmax + 1)
|
|
111
|
+
for ih, hh in enumerate(h):
|
|
112
|
+
EPS[:, :, ih] = (eps1 - eps2) * f * numpy.sinc(hh * f) + eps2 * (
|
|
113
|
+
hh == 0
|
|
114
|
+
)
|
|
115
|
+
EPS1[:, :, ih] = (
|
|
116
|
+
scipy.linalg.inv(eps1) - scipy.linalg.inv(eps2)
|
|
117
|
+
) * f * numpy.sinc(hh * f) + scipy.linalg.inv(eps2) * (hh == 0)
|
|
118
|
+
return EPS, EPS1
|
|
119
|
+
|
|
120
|
+
def capacitance(self, area=1.0, wl=0):
|
|
121
|
+
"""Capacitance = eps0 * eps_r * area / thickness."""
|
|
122
|
+
|
|
123
|
+
if self.isIsotropic():
|
|
124
|
+
eps = EMpy.constants.eps0 * numpy.real(
|
|
125
|
+
self.mat1.n(wl) ** 2 * self.dc + self.mat2.n(wl) ** 2 * (1 - self.dc)
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
eps1 = self.mat1.epsilonTensor(wl)[2, 2, 0]
|
|
129
|
+
eps2 = self.mat2.epsilonTensor(wl)[2, 2, 0]
|
|
130
|
+
eps = numpy.real(eps1 * self.dc + eps2 * (1 - self.dc))
|
|
131
|
+
|
|
132
|
+
return eps * area / self.thickness
|
|
133
|
+
|
|
134
|
+
def __str__(self):
|
|
135
|
+
"""Return the description of a binary grating."""
|
|
136
|
+
return "(%s, %s), dc: %g, pitch: %g, thickness: %g" % (
|
|
137
|
+
self.mat1,
|
|
138
|
+
self.mat2,
|
|
139
|
+
self.dc,
|
|
140
|
+
self.pitch,
|
|
141
|
+
self.thickness,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class SymmetricDoubleGrating:
|
|
146
|
+
"""A Symmetric Double Grating is defined by three materials (iso
|
|
147
|
+
or aniso), two duty cycles, a pitch and a thickness.
|
|
148
|
+
|
|
149
|
+
Inside the pitch there are two rect of width dc1*pitch of mat1 and
|
|
150
|
+
dc2*pitch of mat2, with a spacer of fixed width made of mat3 between them.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, mat1, mat2, mat3, dc1, dc2, pitch, thickness):
|
|
154
|
+
"""Set the materials, the duty cycle and the thickness."""
|
|
155
|
+
self.mat1 = mat1
|
|
156
|
+
self.mat2 = mat2
|
|
157
|
+
self.mat3 = mat3
|
|
158
|
+
self.dc1 = dc1
|
|
159
|
+
self.dc2 = dc2
|
|
160
|
+
self.pitch = pitch
|
|
161
|
+
self.thickness = thickness
|
|
162
|
+
|
|
163
|
+
def isIsotropic(self):
|
|
164
|
+
"""Return True if all the materials are isotropic, False otherwise."""
|
|
165
|
+
return (
|
|
166
|
+
self.mat1.isIsotropic()
|
|
167
|
+
and self.mat2.isIsotropic()
|
|
168
|
+
and self.mat3.isIsotropic()
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def getEPSFourierCoeffs(self, wl, n, anisotropic=True):
|
|
172
|
+
"""Return the Fourier coefficients of eps and eps**-1, orders [-n,n]."""
|
|
173
|
+
nood = 2 * n + 1
|
|
174
|
+
hmax = nood - 1
|
|
175
|
+
if not anisotropic:
|
|
176
|
+
# isotropic
|
|
177
|
+
rix1 = self.mat1.n(wl)
|
|
178
|
+
rix2 = self.mat2.n(wl)
|
|
179
|
+
rix3 = self.mat3.n(wl)
|
|
180
|
+
f1 = self.dc1
|
|
181
|
+
f2 = self.dc2
|
|
182
|
+
h = numpy.arange(-hmax, hmax + 1)
|
|
183
|
+
N = len(h)
|
|
184
|
+
A = -N * f1 / 2.0
|
|
185
|
+
B = N * f2 / 2.0
|
|
186
|
+
EPS = (
|
|
187
|
+
rix3**2 * (h == 0)
|
|
188
|
+
+ (rix1**2 - rix3**2)
|
|
189
|
+
* f1
|
|
190
|
+
* numpy.sinc(h * f1)
|
|
191
|
+
* numpy.exp(2j * numpy.pi * h / N * A)
|
|
192
|
+
+ (rix2**2 - rix3**2)
|
|
193
|
+
* f2
|
|
194
|
+
* numpy.sinc(h * f2)
|
|
195
|
+
* numpy.exp(2j * numpy.pi * h / N * B)
|
|
196
|
+
)
|
|
197
|
+
EPS1 = (
|
|
198
|
+
rix3**-2 * (h == 0)
|
|
199
|
+
+ (rix1**-2 - rix3**-2)
|
|
200
|
+
* f1
|
|
201
|
+
* numpy.sinc(h * f1)
|
|
202
|
+
* numpy.exp(2j * numpy.pi * h / N * A)
|
|
203
|
+
+ (rix2**-2 - rix3**-2)
|
|
204
|
+
* f2
|
|
205
|
+
* numpy.sinc(h * f2)
|
|
206
|
+
* numpy.exp(2j * numpy.pi * h / N * B)
|
|
207
|
+
)
|
|
208
|
+
return EPS, EPS1
|
|
209
|
+
else:
|
|
210
|
+
# anisotropic
|
|
211
|
+
EPS = numpy.zeros((3, 3, 2 * hmax + 1), dtype=complex)
|
|
212
|
+
EPS1 = numpy.zeros_like(EPS)
|
|
213
|
+
eps1 = numpy.squeeze(self.mat1.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
214
|
+
eps2 = numpy.squeeze(self.mat2.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
215
|
+
eps3 = numpy.squeeze(self.mat3.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
216
|
+
f1 = self.dc1
|
|
217
|
+
f2 = self.dc2
|
|
218
|
+
h = numpy.arange(-hmax, hmax + 1)
|
|
219
|
+
N = len(h)
|
|
220
|
+
A = -N * f1 / 2.0
|
|
221
|
+
B = N * f2 / 2.0
|
|
222
|
+
for ih, hh in enumerate(h):
|
|
223
|
+
EPS[:, :, ih] = (
|
|
224
|
+
(eps1 - eps3)
|
|
225
|
+
* f1
|
|
226
|
+
* numpy.sinc(hh * f1)
|
|
227
|
+
* numpy.exp(2j * numpy.pi * hh / N * A)
|
|
228
|
+
+ (eps2 - eps3)
|
|
229
|
+
* f2
|
|
230
|
+
* numpy.sinc(hh * f2)
|
|
231
|
+
* numpy.exp(2j * numpy.pi * hh / N * B)
|
|
232
|
+
+ eps3 * (hh == 0)
|
|
233
|
+
)
|
|
234
|
+
EPS1[:, :, ih] = (
|
|
235
|
+
(scipy.linalg.inv(eps1) - scipy.linalg.inv(eps3))
|
|
236
|
+
* f1
|
|
237
|
+
* numpy.sinc(hh * f1)
|
|
238
|
+
* numpy.exp(2j * numpy.pi * hh / N * A)
|
|
239
|
+
+ (scipy.linalg.inv(eps2) - scipy.linalg.inv(eps3))
|
|
240
|
+
* f2
|
|
241
|
+
* numpy.sinc(hh * f2)
|
|
242
|
+
* numpy.exp(2j * numpy.pi * hh / N * B)
|
|
243
|
+
+ scipy.linalg.inv(eps3) * (hh == 0)
|
|
244
|
+
)
|
|
245
|
+
return EPS, EPS1
|
|
246
|
+
|
|
247
|
+
def capacitance(self, area=1.0, wl=0):
|
|
248
|
+
"""Capacitance = eps0 * eps_r * area / thickness."""
|
|
249
|
+
|
|
250
|
+
if self.isIsotropic():
|
|
251
|
+
eps = EMpy.constants.eps0 * numpy.real(
|
|
252
|
+
self.mat1.n(wl) ** 2 * self.dc1
|
|
253
|
+
+ self.mat2.n(wl) ** 2 * self.dc2
|
|
254
|
+
+ self.mat3.n(wl) ** 2 * (1 - self.dc1 - self.dc2)
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
eps1 = self.mat1.epsilonTensor(wl)[2, 2, 0]
|
|
258
|
+
eps2 = self.mat2.epsilonTensor(wl)[2, 2, 0]
|
|
259
|
+
eps3 = self.mat3.epsilonTensor(wl)[2, 2, 0]
|
|
260
|
+
eps = numpy.real(
|
|
261
|
+
eps1 * self.dc1 + eps2 * self.dc2 + eps3 * (1 - self.dc1 - self.dc2)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return eps * area / self.thickness
|
|
265
|
+
|
|
266
|
+
def __str__(self):
|
|
267
|
+
"""Return the description of a binary grating."""
|
|
268
|
+
return "(%s, %s, %s), dc1: %g, dc2: %g, pitch: %g, thickness: %g" % (
|
|
269
|
+
self.mat1,
|
|
270
|
+
self.mat2,
|
|
271
|
+
self.mat3,
|
|
272
|
+
self.dc1,
|
|
273
|
+
self.dc2,
|
|
274
|
+
self.pitch,
|
|
275
|
+
self.thickness,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class AsymmetricDoubleGrating(SymmetricDoubleGrating):
|
|
280
|
+
"""An Asymmetric Double Grating is defined by three materials (iso
|
|
281
|
+
or aniso), three duty cycles, a pitch and a thickness.
|
|
282
|
+
|
|
283
|
+
Inside the pitch there are two rect of width dc1*pitch of mat1 and
|
|
284
|
+
dc2*pitch of mat2, separated by dcM*pitch mat3 (between mat1 e
|
|
285
|
+
mat2, not between mat2 and mat1!).
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def __init__(self, mat1, mat2, mat3, dc1, dc2, dcM, pitch, thickness):
|
|
289
|
+
SymmetricDoubleGrating.__init__(
|
|
290
|
+
self, mat1, mat2, mat3, dc1, dc2, pitch, thickness
|
|
291
|
+
)
|
|
292
|
+
self.dcM = dcM
|
|
293
|
+
|
|
294
|
+
def getEPSFourierCoeffs(self, wl, n, anisotropic=True):
|
|
295
|
+
"""Return the Fourier coefficients of eps and eps**-1, orders [-n,n]."""
|
|
296
|
+
nood = 2 * n + 1
|
|
297
|
+
hmax = nood - 1
|
|
298
|
+
if not anisotropic:
|
|
299
|
+
# isotropic
|
|
300
|
+
rix1 = self.mat1.n(wl)
|
|
301
|
+
rix2 = self.mat2.n(wl)
|
|
302
|
+
rix3 = self.mat3.n(wl)
|
|
303
|
+
f1 = self.dc1
|
|
304
|
+
f2 = self.dc2
|
|
305
|
+
fM = self.dcM
|
|
306
|
+
h = numpy.arange(-hmax, hmax + 1)
|
|
307
|
+
N = len(h)
|
|
308
|
+
A = -N * (f1 + fM) / 2.0
|
|
309
|
+
B = N * (f2 + fM) / 2.0
|
|
310
|
+
EPS = (
|
|
311
|
+
rix3**2 * (h == 0)
|
|
312
|
+
+ (rix1**2 - rix3**2)
|
|
313
|
+
* f1
|
|
314
|
+
* numpy.sinc(h * f1)
|
|
315
|
+
* numpy.exp(2j * numpy.pi * h / N * A)
|
|
316
|
+
+ (rix2**2 - rix3**2)
|
|
317
|
+
* f2
|
|
318
|
+
* numpy.sinc(h * f2)
|
|
319
|
+
* numpy.exp(2j * numpy.pi * h / N * B)
|
|
320
|
+
)
|
|
321
|
+
EPS1 = (
|
|
322
|
+
rix3**-2 * (h == 0)
|
|
323
|
+
+ (rix1**-2 - rix3**-2)
|
|
324
|
+
* f1
|
|
325
|
+
* numpy.sinc(h * f1)
|
|
326
|
+
* numpy.exp(2j * numpy.pi * h / N * A)
|
|
327
|
+
+ (rix2**-2 - rix3**-2)
|
|
328
|
+
* f2
|
|
329
|
+
* numpy.sinc(h * f2)
|
|
330
|
+
* numpy.exp(2j * numpy.pi * h / N * B)
|
|
331
|
+
)
|
|
332
|
+
return EPS, EPS1
|
|
333
|
+
else:
|
|
334
|
+
# anisotropic
|
|
335
|
+
EPS = numpy.zeros((3, 3, 2 * hmax + 1), dtype=complex)
|
|
336
|
+
EPS1 = numpy.zeros_like(EPS)
|
|
337
|
+
eps1 = numpy.squeeze(self.mat1.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
338
|
+
eps2 = numpy.squeeze(self.mat2.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
339
|
+
eps3 = numpy.squeeze(self.mat3.epsilonTensor(wl)) / EMpy.constants.eps0
|
|
340
|
+
f1 = self.dc1
|
|
341
|
+
f2 = self.dc2
|
|
342
|
+
fM = self.dcM
|
|
343
|
+
h = numpy.arange(-hmax, hmax + 1)
|
|
344
|
+
N = len(h)
|
|
345
|
+
A = -N * (f1 + fM) / 2.0
|
|
346
|
+
B = N * (f2 + fM) / 2.0
|
|
347
|
+
for ih, hh in enumerate(h):
|
|
348
|
+
EPS[:, :, ih] = (
|
|
349
|
+
(eps1 - eps3)
|
|
350
|
+
* f1
|
|
351
|
+
* numpy.sinc(hh * f1)
|
|
352
|
+
* numpy.exp(2j * numpy.pi * hh / N * A)
|
|
353
|
+
+ (eps2 - eps3)
|
|
354
|
+
* f2
|
|
355
|
+
* numpy.sinc(hh * f2)
|
|
356
|
+
* numpy.exp(2j * numpy.pi * hh / N * B)
|
|
357
|
+
+ eps3 * (hh == 0)
|
|
358
|
+
)
|
|
359
|
+
EPS1[:, :, ih] = (
|
|
360
|
+
(scipy.linalg.inv(eps1) - scipy.linalg.inv(eps3))
|
|
361
|
+
* f1
|
|
362
|
+
* numpy.sinc(hh * f1)
|
|
363
|
+
* numpy.exp(2j * numpy.pi * hh / N * A)
|
|
364
|
+
+ (scipy.linalg.inv(eps2) - scipy.linalg.inv(eps3))
|
|
365
|
+
* f2
|
|
366
|
+
* numpy.sinc(hh * f2)
|
|
367
|
+
* numpy.exp(2j * numpy.pi * hh / N * B)
|
|
368
|
+
+ scipy.linalg.inv(eps3) * (hh == 0)
|
|
369
|
+
)
|
|
370
|
+
return EPS, EPS1
|
|
371
|
+
|
|
372
|
+
def capacitance(self, area=1.0, wl=0):
|
|
373
|
+
"""Capacitance = eps0 * eps_r * area / thickness."""
|
|
374
|
+
|
|
375
|
+
if self.isIsotropic():
|
|
376
|
+
eps = EMpy.constants.eps0 * numpy.real(
|
|
377
|
+
self.mat1.n(wl) ** 2 * self.dc1
|
|
378
|
+
+ self.mat2.n(wl) ** 2 * self.dc2
|
|
379
|
+
+ self.mat3.n(wl) ** 2 * (1 - self.dc1 - self.dc2)
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
eps1 = self.mat1.epsilonTensor(wl)[2, 2, 0]
|
|
383
|
+
eps2 = self.mat2.epsilonTensor(wl)[2, 2, 0]
|
|
384
|
+
eps3 = self.mat3.epsilonTensor(wl)[2, 2, 0]
|
|
385
|
+
eps = numpy.real(
|
|
386
|
+
eps1 * self.dc1 + eps2 * self.dc2 + eps3 * (1 - self.dc1 - self.dc2)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return eps * area / self.thickness
|
|
390
|
+
|
|
391
|
+
def __str__(self):
|
|
392
|
+
"""Return the description of a binary grating."""
|
|
393
|
+
return (
|
|
394
|
+
"(%s, %s, %s), dc1: %g, dc2: %g, dcM: %g, " "pitch: %g, thickness: %g"
|
|
395
|
+
) % (
|
|
396
|
+
self.mat1,
|
|
397
|
+
self.mat2,
|
|
398
|
+
self.mat3,
|
|
399
|
+
self.dc1,
|
|
400
|
+
self.dc2,
|
|
401
|
+
self.dcM,
|
|
402
|
+
self.pitch,
|
|
403
|
+
self.thickness,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class LiquidCrystalCell:
|
|
408
|
+
"""Liquid Crystal Cell.
|
|
409
|
+
|
|
410
|
+
A liquid crystal cell is determined by a liquid crystal, a voltage
|
|
411
|
+
applied to it, a total thickness, an anchoring thickness. The
|
|
412
|
+
liquid crystal molecules are anchored to the cell with a given
|
|
413
|
+
pretilt angle (that, at zero volts, is constant throughout all the LC cell).
|
|
414
|
+
The cell is decomposed in nlayers homogeneous layers. The LC
|
|
415
|
+
characteristics in each layer is either read from file or deduced
|
|
416
|
+
by the LC physical parameters solving a boundary value problem
|
|
417
|
+
(bvp).
|
|
418
|
+
|
|
419
|
+
Inspiration from:
|
|
420
|
+
U{http://www.ee.ucl.ac.uk/~rjames/modelling/constant-order/oned/}.
|
|
421
|
+
|
|
422
|
+
@ivar lc: Liquid Crystal.
|
|
423
|
+
@ivar voltage: voltage applied.
|
|
424
|
+
@ivar t_tot: total thickness.
|
|
425
|
+
@ivar t_anchoring: anchoring thickness.
|
|
426
|
+
@ivar pretilt: LC angle pretilt.
|
|
427
|
+
@ivar totaltwist: LC angle total twist between the anchoring layers.
|
|
428
|
+
@ivar nlayers: number of layers to subdived the cell.
|
|
429
|
+
@ivar data_file: file with the angles for voltages applid to the cell.
|
|
430
|
+
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
def __init__(
|
|
434
|
+
self,
|
|
435
|
+
lc,
|
|
436
|
+
voltage,
|
|
437
|
+
t_tot,
|
|
438
|
+
t_anchoring,
|
|
439
|
+
pretilt=0,
|
|
440
|
+
totaltwist=0,
|
|
441
|
+
nlayers=100,
|
|
442
|
+
data_file=None,
|
|
443
|
+
):
|
|
444
|
+
|
|
445
|
+
self.lc = lc
|
|
446
|
+
self.t_tot = t_tot
|
|
447
|
+
self.t_anchoring = t_anchoring
|
|
448
|
+
self.pretilt = pretilt
|
|
449
|
+
self.totaltwist = totaltwist
|
|
450
|
+
self.nlayers = nlayers
|
|
451
|
+
self.data_file = data_file
|
|
452
|
+
# thicknesses of internal layers
|
|
453
|
+
tlc_internal = (
|
|
454
|
+
(self.t_tot - 2.0 * self.t_anchoring)
|
|
455
|
+
/ (self.nlayers - 2.0)
|
|
456
|
+
* numpy.ones(self.nlayers - 2)
|
|
457
|
+
)
|
|
458
|
+
# thicknesses of layers
|
|
459
|
+
self.tlc = numpy.r_[self.t_anchoring, tlc_internal, self.t_anchoring]
|
|
460
|
+
# internal sample points
|
|
461
|
+
lhs = numpy.r_[0, numpy.cumsum(tlc_internal)]
|
|
462
|
+
# normalized sample points: at the center of internal layers, plus the
|
|
463
|
+
# boundaries (i.e. the anchoring layers)
|
|
464
|
+
self.normalized_sample_points = numpy.r_[
|
|
465
|
+
0, (lhs[1:] + lhs[:-1]) / 2.0 / (self.t_tot - 2 * self.t_anchoring), 1
|
|
466
|
+
]
|
|
467
|
+
tmp = numpy.r_[0, numpy.cumsum(self.tlc)]
|
|
468
|
+
self.sample_points = 0.5 * (tmp[1:] + tmp[:-1])
|
|
469
|
+
# finally, apply voltage
|
|
470
|
+
self.voltage = voltage
|
|
471
|
+
|
|
472
|
+
def getvoltage(self):
|
|
473
|
+
return self.__voltage
|
|
474
|
+
|
|
475
|
+
def setvoltage(self, v):
|
|
476
|
+
self.__voltage = v
|
|
477
|
+
if self.data_file is not None:
|
|
478
|
+
self.__angles = self._get_angles_from_file()
|
|
479
|
+
else:
|
|
480
|
+
self.__angles = self._get_angles_from_bvp()
|
|
481
|
+
|
|
482
|
+
voltage = property(fget=getvoltage, fset=setvoltage)
|
|
483
|
+
|
|
484
|
+
def getangles(self):
|
|
485
|
+
return self.__angles
|
|
486
|
+
|
|
487
|
+
angles = property(fget=getangles)
|
|
488
|
+
|
|
489
|
+
def __ode_3k(self, z, f):
|
|
490
|
+
"""Inspiration from:
|
|
491
|
+
U{http://www.ee.ucl.ac.uk/~rjames/modelling/constant-order/oned/}."""
|
|
492
|
+
|
|
493
|
+
# ------------------------------------------------------------
|
|
494
|
+
# minimise Oseen Frank free energy and solve Laplace equation
|
|
495
|
+
# ------------------------------------------------------------
|
|
496
|
+
# [f(1..6)] = [theta theta' phi phi' u u']
|
|
497
|
+
|
|
498
|
+
theta2, dtheta2dz, phi2, dphi2dz, u2, du2dz = f
|
|
499
|
+
K11 = self.lc.K11
|
|
500
|
+
K22 = self.lc.K22
|
|
501
|
+
K33 = self.lc.K33
|
|
502
|
+
q0 = self.lc.q0
|
|
503
|
+
epslow = self.lc.epslow
|
|
504
|
+
deleps = self.lc.deleps
|
|
505
|
+
|
|
506
|
+
e0 = EMpy.constants.eps0
|
|
507
|
+
K1122 = K11 - K22
|
|
508
|
+
K3322 = K33 - K22
|
|
509
|
+
costheta1 = numpy.cos(theta2)
|
|
510
|
+
sintheta1 = numpy.sin(theta2)
|
|
511
|
+
ezz = e0 * (epslow + deleps * sintheta1**2)
|
|
512
|
+
|
|
513
|
+
# maple generated (see lc3k.mws)
|
|
514
|
+
ddtheta2dz = (
|
|
515
|
+
costheta1
|
|
516
|
+
* sintheta1
|
|
517
|
+
* (
|
|
518
|
+
K1122 * dtheta2dz**2
|
|
519
|
+
+ 2 * K3322 * costheta1**2 * dphi2dz**2
|
|
520
|
+
- K3322 * dtheta2dz**2
|
|
521
|
+
- K22 * dphi2dz**2
|
|
522
|
+
- e0 * deleps * du2dz**2
|
|
523
|
+
+ 2 * q0 * K22 * dphi2dz
|
|
524
|
+
- K3322 * dphi2dz**2
|
|
525
|
+
)
|
|
526
|
+
/ (K1122 * costheta1**2 - K3322 * costheta1**2 + K22 + K3322)
|
|
527
|
+
)
|
|
528
|
+
ddphi2dz = (
|
|
529
|
+
2
|
|
530
|
+
* sintheta1
|
|
531
|
+
* dtheta2dz
|
|
532
|
+
* (
|
|
533
|
+
2 * K3322 * costheta1**2 * dphi2dz
|
|
534
|
+
- K22 * dphi2dz
|
|
535
|
+
+ q0 * K22
|
|
536
|
+
- K3322 * dphi2dz
|
|
537
|
+
)
|
|
538
|
+
/ costheta1
|
|
539
|
+
/ (K3322 * costheta1**2 - K22 - K3322)
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
ddu2dz = -2 * e0 * deleps * sintheta1 * costheta1 * dtheta2dz * du2dz / ezz
|
|
543
|
+
|
|
544
|
+
return numpy.array([ddtheta2dz, ddphi2dz, ddu2dz])
|
|
545
|
+
|
|
546
|
+
def __bc_nosplay(self, f):
|
|
547
|
+
"""Inspiration from:
|
|
548
|
+
U{http://www.ee.ucl.ac.uk/~rjames/modelling/constant-order/oned/}."""
|
|
549
|
+
|
|
550
|
+
theta2, dtheta2dz, phi2, dphi2dz, u2, du2dz = f
|
|
551
|
+
return numpy.array(
|
|
552
|
+
[
|
|
553
|
+
theta2[0] - self.pretilt,
|
|
554
|
+
phi2[1] - 0,
|
|
555
|
+
u2[2] - 0,
|
|
556
|
+
theta2[3] - self.pretilt,
|
|
557
|
+
phi2[4] - self.totaltwist,
|
|
558
|
+
u2[5] - self.voltage,
|
|
559
|
+
]
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
def __ic_nosplay(self, z):
|
|
563
|
+
"""Inspiration from:
|
|
564
|
+
U{http://www.ee.ucl.ac.uk/~rjames/modelling/constant-order/oned/}."""
|
|
565
|
+
|
|
566
|
+
self.maxtilt = 90 * numpy.pi / 180 - self.pretilt
|
|
567
|
+
init = numpy.array(
|
|
568
|
+
[
|
|
569
|
+
self.pretilt + self.maxtilt * 4 * z * (1 - z),
|
|
570
|
+
self.maxtilt * 4 * (1 - 2 * z),
|
|
571
|
+
self.totaltwist * z,
|
|
572
|
+
self.totaltwist * numpy.ones_like(z),
|
|
573
|
+
self.voltage * z,
|
|
574
|
+
self.voltage * numpy.ones_like(z),
|
|
575
|
+
]
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
return init, self.__ode_3k(z, init)
|
|
579
|
+
|
|
580
|
+
def __apply_tension(self):
|
|
581
|
+
"""Inspiration from:
|
|
582
|
+
U{http://www.ee.ucl.ac.uk/~rjames/modelling/constant-order/oned/}."""
|
|
583
|
+
|
|
584
|
+
from scikits.bvp1lg import colnew # type: ignore
|
|
585
|
+
|
|
586
|
+
boundary_points = numpy.array([0, 0, 0, 1, 1, 1])
|
|
587
|
+
tol = 1e-6 * numpy.ones_like(boundary_points)
|
|
588
|
+
degrees = numpy.array([2, 2, 2])
|
|
589
|
+
|
|
590
|
+
solution = colnew.solve(
|
|
591
|
+
boundary_points,
|
|
592
|
+
degrees,
|
|
593
|
+
self.__ode_3k,
|
|
594
|
+
self.__bc_nosplay,
|
|
595
|
+
is_linear=False,
|
|
596
|
+
initial_guess=self.__ic_nosplay,
|
|
597
|
+
tolerances=tol,
|
|
598
|
+
vectorized=True,
|
|
599
|
+
maximum_mesh_size=1000,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
self.bvp_solution = solution
|
|
603
|
+
|
|
604
|
+
def get_parameters(self, z=None):
|
|
605
|
+
"""Inspiration from:
|
|
606
|
+
U{http://www.ee.ucl.ac.uk/~rjames/modelling/constant-order/oned/}."""
|
|
607
|
+
|
|
608
|
+
if z is None:
|
|
609
|
+
z = self.bvp_solution.mesh
|
|
610
|
+
|
|
611
|
+
data = self.bvp_solution(z)
|
|
612
|
+
theta = EMpy.utils.rad2deg(numpy.pi / 2.0 - data[:, 0])
|
|
613
|
+
phi = EMpy.utils.rad2deg(data[:, 2])
|
|
614
|
+
u = data[:, 4]
|
|
615
|
+
|
|
616
|
+
return z, theta, phi, u
|
|
617
|
+
|
|
618
|
+
def _get_angles_from_file(self):
|
|
619
|
+
|
|
620
|
+
# interpolate data file
|
|
621
|
+
data = numpy.loadtxt(self.data_file)
|
|
622
|
+
data_x = numpy.linspace(0, 1, data.shape[0] - 1)
|
|
623
|
+
data_y = data[0, :]
|
|
624
|
+
x = self.normalized_sample_points
|
|
625
|
+
y = [self.voltage]
|
|
626
|
+
angles = interp2(x, y, data_x, data_y, data[1:, :])
|
|
627
|
+
|
|
628
|
+
return angles.squeeze()
|
|
629
|
+
|
|
630
|
+
def _get_angles_from_bvp(self):
|
|
631
|
+
|
|
632
|
+
# solve bvp
|
|
633
|
+
self.__apply_tension()
|
|
634
|
+
z_ = self.normalized_sample_points
|
|
635
|
+
z, theta, phi, u = self.get_parameters(z_)
|
|
636
|
+
|
|
637
|
+
return theta
|
|
638
|
+
|
|
639
|
+
def createMultilayer(self):
|
|
640
|
+
"""Split the cell in nlayers homogeneous layers."""
|
|
641
|
+
|
|
642
|
+
m = []
|
|
643
|
+
for a, t in zip(EMpy.utils.deg2rad(self.angles), self.tlc):
|
|
644
|
+
epsT = EMpy.materials.EpsilonTensor(
|
|
645
|
+
epsilon_tensor_const=EMpy.utils.euler_rotate(
|
|
646
|
+
numpy.diag([self.lc.nE, self.lc.nO, self.lc.nO]) ** 2,
|
|
647
|
+
0.0,
|
|
648
|
+
numpy.pi / 2.0,
|
|
649
|
+
numpy.pi / 2.0 - a,
|
|
650
|
+
)
|
|
651
|
+
* EMpy.constants.eps0,
|
|
652
|
+
epsilon_tensor_known={
|
|
653
|
+
0: EMpy.utils.euler_rotate(
|
|
654
|
+
numpy.diag(
|
|
655
|
+
[
|
|
656
|
+
self.lc.nE_electrical,
|
|
657
|
+
self.lc.nO_electrical,
|
|
658
|
+
self.lc.nO_electrical,
|
|
659
|
+
]
|
|
660
|
+
)
|
|
661
|
+
** 2,
|
|
662
|
+
0.0,
|
|
663
|
+
numpy.pi / 2.0,
|
|
664
|
+
numpy.pi / 2.0 - a,
|
|
665
|
+
)
|
|
666
|
+
* EMpy.constants.eps0,
|
|
667
|
+
},
|
|
668
|
+
)
|
|
669
|
+
m.append(
|
|
670
|
+
Layer(EMpy.materials.AnisotropicMaterial("LC", epsilon_tensor=epsT), t)
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
return Multilayer(m)
|
|
674
|
+
|
|
675
|
+
def capacitance(self, area=1.0, wl=0):
|
|
676
|
+
"""Capacitance = eps0 * eps_r * area / thickness."""
|
|
677
|
+
return self.createMultilayer().capacitance(area, wl)
|
|
678
|
+
|
|
679
|
+
@staticmethod
|
|
680
|
+
def isIsotropic():
|
|
681
|
+
"""Return False."""
|
|
682
|
+
return False
|
|
683
|
+
|
|
684
|
+
def __str__(self):
|
|
685
|
+
"""Return the description of a LiquidCrystal."""
|
|
686
|
+
return (
|
|
687
|
+
"datafile: %s, voltage: %g, t_tot: %g, "
|
|
688
|
+
"t_anchoring: %g, (nO, nE) = (%g, %g)"
|
|
689
|
+
) % (
|
|
690
|
+
self.data_file,
|
|
691
|
+
self.voltage,
|
|
692
|
+
self.t_tot,
|
|
693
|
+
self.t_anchoring,
|
|
694
|
+
self.lc.nO,
|
|
695
|
+
self.lc.nE,
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
class Multilayer:
|
|
700
|
+
|
|
701
|
+
"""A Multilayer is a list of layers with some more methods."""
|
|
702
|
+
|
|
703
|
+
def __init__(self, data=None):
|
|
704
|
+
"""Initialize the data list."""
|
|
705
|
+
if data is None:
|
|
706
|
+
data = []
|
|
707
|
+
self.data = data[:]
|
|
708
|
+
|
|
709
|
+
def __delitem__(self, i):
|
|
710
|
+
"""Delete an item from list."""
|
|
711
|
+
del self.data[i]
|
|
712
|
+
|
|
713
|
+
def __getitem__(self, i):
|
|
714
|
+
"""Get an item of the list of layers."""
|
|
715
|
+
return self.data[i]
|
|
716
|
+
|
|
717
|
+
def __getslice__(self, i, j):
|
|
718
|
+
"""Get a Multilayer from a slice of layers."""
|
|
719
|
+
return Multilayer(self.data[i:j])
|
|
720
|
+
|
|
721
|
+
def __len__(self):
|
|
722
|
+
"""Return the number of layers."""
|
|
723
|
+
return len(self.data)
|
|
724
|
+
|
|
725
|
+
def __setitem__(self, i, item):
|
|
726
|
+
"""Set an item of the list of layers."""
|
|
727
|
+
self.data[i] = item
|
|
728
|
+
|
|
729
|
+
def __setslice__(self, i, j, other):
|
|
730
|
+
"""Set a slice of layers."""
|
|
731
|
+
self.data[i:j] = other
|
|
732
|
+
|
|
733
|
+
def append(self, item):
|
|
734
|
+
"""Append a layer to the layers list."""
|
|
735
|
+
self.data.append(item)
|
|
736
|
+
|
|
737
|
+
def extend(self, other):
|
|
738
|
+
"""Extend the layers list with other layers."""
|
|
739
|
+
self.data.extend(other)
|
|
740
|
+
|
|
741
|
+
def insert(self, i, item):
|
|
742
|
+
"""Insert a new layer in the layers list at the position i."""
|
|
743
|
+
self.data.insert(i, item)
|
|
744
|
+
|
|
745
|
+
def remove(self, item):
|
|
746
|
+
"""Remove item from layers list."""
|
|
747
|
+
self.data.remove(item)
|
|
748
|
+
|
|
749
|
+
def pop(self, i=-1):
|
|
750
|
+
return self.data.pop(i)
|
|
751
|
+
|
|
752
|
+
def isIsotropic(self):
|
|
753
|
+
"""Return True if all the layers of the multilayers are
|
|
754
|
+
isotropic, False otherwise."""
|
|
755
|
+
return numpy.asarray([m.isIsotropic() for m in self.data]).all()
|
|
756
|
+
|
|
757
|
+
def simplify(self):
|
|
758
|
+
"""Return a new flatten Multilayer, with expanded LiquidCrystalCells."""
|
|
759
|
+
# make a tmp list, copy of self, to work with
|
|
760
|
+
tmp = self.data[:]
|
|
761
|
+
# expand the liquid crystals
|
|
762
|
+
for il, l in enumerate(tmp):
|
|
763
|
+
if isinstance(l, LiquidCrystalCell):
|
|
764
|
+
tmp[il] = l.createMultilayer()
|
|
765
|
+
|
|
766
|
+
# flatten the tmp list
|
|
767
|
+
def helper(multilayer):
|
|
768
|
+
"""Recurse to flatten all the nested Multilayers."""
|
|
769
|
+
ret = []
|
|
770
|
+
for layer in multilayer:
|
|
771
|
+
if not isinstance(layer, Multilayer):
|
|
772
|
+
ret.append(layer)
|
|
773
|
+
else:
|
|
774
|
+
ret.extend(helper(layer[:]))
|
|
775
|
+
return ret
|
|
776
|
+
|
|
777
|
+
return Multilayer(helper(tmp))
|
|
778
|
+
|
|
779
|
+
def capacitance(self, area=1.0, wl=0):
|
|
780
|
+
"""Capacitance = eps0 * eps_r * area / thickness."""
|
|
781
|
+
|
|
782
|
+
m = self.simplify()
|
|
783
|
+
ctot_1 = 0.0
|
|
784
|
+
for l in m:
|
|
785
|
+
if numpy.isfinite(l.thickness):
|
|
786
|
+
ctot_1 += 1.0 / l.capacitance(area, wl)
|
|
787
|
+
return 1.0 / ctot_1
|
|
788
|
+
|
|
789
|
+
def __str__(self):
|
|
790
|
+
"""Return a description of the Multilayer."""
|
|
791
|
+
if self.__len__() == 0:
|
|
792
|
+
list_str = "<emtpy>"
|
|
793
|
+
else:
|
|
794
|
+
list_str = "\n".join(
|
|
795
|
+
["%d: %s" % (il, l.__str__()) for il, l in enumerate(self.data)]
|
|
796
|
+
)
|
|
797
|
+
return "Multilayer\n----------\n" + list_str
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
class Slice(Multilayer):
|
|
801
|
+
def __init__(self, width, *argv):
|
|
802
|
+
Multilayer.__init__(self, *argv)
|
|
803
|
+
self.width = width
|
|
804
|
+
|
|
805
|
+
def heights(self):
|
|
806
|
+
return numpy.array([l.thickness for l in self])
|
|
807
|
+
|
|
808
|
+
def ys(self):
|
|
809
|
+
return numpy.r_[0.0, self.heights().cumsum()]
|
|
810
|
+
|
|
811
|
+
def height(self):
|
|
812
|
+
return self.heights().sum()
|
|
813
|
+
|
|
814
|
+
def find_layer(self, y):
|
|
815
|
+
l = numpy.where(self.ys() <= y)[0]
|
|
816
|
+
if len(l) > 0:
|
|
817
|
+
return self[min(l[-1], len(self) - 1)]
|
|
818
|
+
else:
|
|
819
|
+
return self[0]
|
|
820
|
+
|
|
821
|
+
def plot(self, x0, x1, nmin, nmax, wl=1.55e-6):
|
|
822
|
+
try:
|
|
823
|
+
import pylab
|
|
824
|
+
except ImportError:
|
|
825
|
+
warning("no pylab installed")
|
|
826
|
+
return
|
|
827
|
+
y0 = 0
|
|
828
|
+
# ytot = sum([l.thickness for l in self])
|
|
829
|
+
for l in self:
|
|
830
|
+
y1 = y0 + l.thickness
|
|
831
|
+
n = l.mat.n(wl)
|
|
832
|
+
r = 1.0 - (1.0 * (n - nmin) / (nmax - nmin))
|
|
833
|
+
pylab.fill(
|
|
834
|
+
[x0, x1, x1, x0], [y0, y0, y1, y1], ec="yellow", fc=(r, r, r), alpha=0.5
|
|
835
|
+
)
|
|
836
|
+
y0 = y1
|
|
837
|
+
pylab.axis("image")
|
|
838
|
+
|
|
839
|
+
def __str__(self):
|
|
840
|
+
return "width = %e\n%s" % (self.width, Multilayer.__str__(self))
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
class CrossSection(list):
|
|
844
|
+
def __str__(self):
|
|
845
|
+
return "\n".join("%s" % s for s in self)
|
|
846
|
+
|
|
847
|
+
def widths(self):
|
|
848
|
+
return numpy.array([s.width for s in self])
|
|
849
|
+
|
|
850
|
+
def xs(self):
|
|
851
|
+
return numpy.r_[0.0, self.widths().cumsum()]
|
|
852
|
+
|
|
853
|
+
def ys(self):
|
|
854
|
+
tmp = numpy.concatenate([s.ys() for s in self])
|
|
855
|
+
# get rid of numerical errors
|
|
856
|
+
tmp = numpy.round(tmp * 1e10) * 1e-10
|
|
857
|
+
return numpy.unique(tmp)
|
|
858
|
+
|
|
859
|
+
def width(self):
|
|
860
|
+
return self.widths().sum()
|
|
861
|
+
|
|
862
|
+
def grid(self, nx_per_region, ny_per_region):
|
|
863
|
+
|
|
864
|
+
xs = self.xs()
|
|
865
|
+
ys = self.ys()
|
|
866
|
+
|
|
867
|
+
nxregions = len(xs) - 1
|
|
868
|
+
nyregions = len(ys) - 1
|
|
869
|
+
|
|
870
|
+
if numpy.isscalar(nx_per_region):
|
|
871
|
+
nx = (nx_per_region,) * nxregions
|
|
872
|
+
elif len(nx_per_region) != nxregions:
|
|
873
|
+
raise ValueError("wrong nx_per_region dim")
|
|
874
|
+
else:
|
|
875
|
+
nx = nx_per_region
|
|
876
|
+
|
|
877
|
+
if numpy.isscalar(ny_per_region):
|
|
878
|
+
ny = (ny_per_region,) * nyregions
|
|
879
|
+
elif len(ny_per_region) != nyregions:
|
|
880
|
+
raise ValueError("wrong ny_per_region dim")
|
|
881
|
+
else:
|
|
882
|
+
ny = ny_per_region
|
|
883
|
+
|
|
884
|
+
X = []
|
|
885
|
+
x0 = xs[0]
|
|
886
|
+
for x, n in zip(xs[1:], nx):
|
|
887
|
+
X.append(numpy.linspace(x0, x, n + 1)[:-1])
|
|
888
|
+
x0 = x
|
|
889
|
+
X = numpy.concatenate(X)
|
|
890
|
+
X = numpy.r_[X, x0]
|
|
891
|
+
|
|
892
|
+
Y = []
|
|
893
|
+
y0 = ys[0]
|
|
894
|
+
for y, n in zip(ys[1:], ny):
|
|
895
|
+
Y.append(numpy.linspace(y0, y, n + 1)[:-1])
|
|
896
|
+
y0 = y
|
|
897
|
+
Y = numpy.concatenate(Y)
|
|
898
|
+
Y = numpy.r_[Y, y0]
|
|
899
|
+
|
|
900
|
+
return X, Y
|
|
901
|
+
|
|
902
|
+
def find_slice(self, x):
|
|
903
|
+
s = numpy.where(self.xs() <= x)[0]
|
|
904
|
+
if len(s) > 0:
|
|
905
|
+
return self[min(s[-1], len(self) - 1)]
|
|
906
|
+
else:
|
|
907
|
+
return self[0]
|
|
908
|
+
|
|
909
|
+
def _epsfunc(self, x, y, wl):
|
|
910
|
+
if numpy.isscalar(x) and numpy.isscalar(y):
|
|
911
|
+
return self.find_slice(x).find_layer(y).mat.n(wl) ** 2
|
|
912
|
+
else:
|
|
913
|
+
raise ValueError("only scalars, please!")
|
|
914
|
+
|
|
915
|
+
def epsfunc(self, x, y, wl):
|
|
916
|
+
eps = numpy.ones((len(x), len(y)), dtype=complex)
|
|
917
|
+
for ix, xx in enumerate(x):
|
|
918
|
+
for iy, yy in enumerate(y):
|
|
919
|
+
eps[ix, iy] = self._epsfunc(xx, yy, wl)
|
|
920
|
+
return eps
|
|
921
|
+
|
|
922
|
+
def plot(self, wl=1.55e-6):
|
|
923
|
+
try:
|
|
924
|
+
import pylab
|
|
925
|
+
except ImportError:
|
|
926
|
+
warning("no pylab installed")
|
|
927
|
+
return
|
|
928
|
+
x0 = 0
|
|
929
|
+
ns = [[l.mat.n(wl) for l in s] for s in self]
|
|
930
|
+
nmax = max(max(ns))
|
|
931
|
+
nmin = min(min(ns))
|
|
932
|
+
for s in self:
|
|
933
|
+
x1 = x0 + s.width
|
|
934
|
+
s.plot(x0, x1, nmin, nmax, wl=wl)
|
|
935
|
+
x0 = x1
|
|
936
|
+
pylab.axis("image")
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
class Peak:
|
|
940
|
+
def __init__(self, x, y, idx, x0, y0, xFWHM_1, xFWHM_2):
|
|
941
|
+
self.x = x
|
|
942
|
+
self.y = y
|
|
943
|
+
self.idx = idx
|
|
944
|
+
self.x0 = x0
|
|
945
|
+
self.y0 = y0
|
|
946
|
+
self.xFWHM_1 = xFWHM_1
|
|
947
|
+
self.xFWHM_2 = xFWHM_2
|
|
948
|
+
self.FWHM = numpy.abs(xFWHM_2 - xFWHM_1)
|
|
949
|
+
|
|
950
|
+
def __str__(self):
|
|
951
|
+
return "(%g, %g) [%d, (%g, %g)] FWHM = %s" % (
|
|
952
|
+
self.x,
|
|
953
|
+
self.y,
|
|
954
|
+
self.idx,
|
|
955
|
+
self.x0,
|
|
956
|
+
self.y0,
|
|
957
|
+
self.FWHM,
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
def deg2rad(x):
|
|
962
|
+
"""Convert from deg to rad."""
|
|
963
|
+
return x / 180.0 * numpy.pi
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
def rad2deg(x):
|
|
967
|
+
"""Convert from rad to deg."""
|
|
968
|
+
return x / numpy.pi * 180.0
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
def norm(x):
|
|
972
|
+
"""Return the norm of a 1D vector."""
|
|
973
|
+
return numpy.sqrt(numpy.vdot(x, x))
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def normalize(x):
|
|
977
|
+
"""Return a normalized 1D vector."""
|
|
978
|
+
return x / norm(x)
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def euler_rotate(X, phi, theta, psi):
|
|
982
|
+
"""Euler rotate.
|
|
983
|
+
|
|
984
|
+
Rotate the matrix X by the angles phi, theta, psi.
|
|
985
|
+
|
|
986
|
+
INPUT
|
|
987
|
+
X = 2d numpy.array.
|
|
988
|
+
phi, theta, psi = rotation angles.
|
|
989
|
+
|
|
990
|
+
OUTPUT
|
|
991
|
+
Rotated matrix = 2d numpy.array.
|
|
992
|
+
|
|
993
|
+
NOTE
|
|
994
|
+
see http://mathworld.wolfram.com/EulerAngles.html
|
|
995
|
+
"""
|
|
996
|
+
|
|
997
|
+
A = numpy.array(
|
|
998
|
+
[
|
|
999
|
+
[
|
|
1000
|
+
numpy.cos(psi) * numpy.cos(phi)
|
|
1001
|
+
- numpy.cos(theta) * numpy.sin(phi) * numpy.sin(psi),
|
|
1002
|
+
-numpy.sin(psi) * numpy.cos(phi)
|
|
1003
|
+
- numpy.cos(theta) * numpy.sin(phi) * numpy.cos(psi),
|
|
1004
|
+
numpy.sin(theta) * numpy.sin(phi),
|
|
1005
|
+
],
|
|
1006
|
+
[
|
|
1007
|
+
numpy.cos(psi) * numpy.sin(phi)
|
|
1008
|
+
+ numpy.cos(theta) * numpy.cos(phi) * numpy.sin(psi),
|
|
1009
|
+
-numpy.sin(psi) * numpy.sin(phi)
|
|
1010
|
+
+ numpy.cos(theta) * numpy.cos(phi) * numpy.cos(psi),
|
|
1011
|
+
-numpy.sin(theta) * numpy.cos(phi),
|
|
1012
|
+
],
|
|
1013
|
+
[
|
|
1014
|
+
numpy.sin(theta) * numpy.sin(psi),
|
|
1015
|
+
numpy.sin(theta) * numpy.cos(psi),
|
|
1016
|
+
numpy.cos(theta),
|
|
1017
|
+
],
|
|
1018
|
+
]
|
|
1019
|
+
)
|
|
1020
|
+
return numpy.dot(A, numpy.dot(X, scipy.linalg.inv(A)))
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def snell(theta_inc, n):
|
|
1024
|
+
"""Snell law.
|
|
1025
|
+
|
|
1026
|
+
INPUT
|
|
1027
|
+
theta_inc = angle of incidence.
|
|
1028
|
+
n = 1D numpy.array of refractive indices.
|
|
1029
|
+
|
|
1030
|
+
OUTPUT
|
|
1031
|
+
theta = 1D numpy.array.
|
|
1032
|
+
"""
|
|
1033
|
+
|
|
1034
|
+
theta = numpy.zeros_like(n)
|
|
1035
|
+
theta[0] = theta_inc
|
|
1036
|
+
for i in range(1, n.size):
|
|
1037
|
+
theta[i] = numpy.arcsin(n[i - 1] / n[i] * numpy.sin(theta[i - 1]))
|
|
1038
|
+
return theta
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def group_delay_and_dispersion(wls, y):
|
|
1042
|
+
"""Compute group delay and dispersion.
|
|
1043
|
+
|
|
1044
|
+
INPUT
|
|
1045
|
+
wls = wavelengths (ndarray).
|
|
1046
|
+
y = function (ndarray).
|
|
1047
|
+
|
|
1048
|
+
OUTPUT
|
|
1049
|
+
phi = phase of function in rad.
|
|
1050
|
+
tau = group delay in ps.
|
|
1051
|
+
Dpsnm = dispersion in ps/nm.
|
|
1052
|
+
|
|
1053
|
+
NOTE
|
|
1054
|
+
wls and y must have the same shape.
|
|
1055
|
+
phi has the same shape as wls.
|
|
1056
|
+
tau has wls.shape - (..., 1)
|
|
1057
|
+
Dpsnm has wls.shape - (..., 2)
|
|
1058
|
+
"""
|
|
1059
|
+
|
|
1060
|
+
# transform the input in ndarrays
|
|
1061
|
+
wls = numpy.asarray(wls)
|
|
1062
|
+
y = numpy.asarray(y)
|
|
1063
|
+
|
|
1064
|
+
# check for good input
|
|
1065
|
+
if wls.shape != y.shape:
|
|
1066
|
+
raise ValueError("wls and y must have the same shape.")
|
|
1067
|
+
|
|
1068
|
+
f = EMpy.constants.c / wls
|
|
1069
|
+
|
|
1070
|
+
df = numpy.diff(f)
|
|
1071
|
+
toPSNM = 1e12 / 1e9
|
|
1072
|
+
cnmps = EMpy.constants.c / toPSNM
|
|
1073
|
+
|
|
1074
|
+
# phase
|
|
1075
|
+
phi = numpy.unwrap(4.0 * numpy.angle(y)) / 4.0
|
|
1076
|
+
|
|
1077
|
+
# group delay
|
|
1078
|
+
tau = -0.5 / numpy.pi * numpy.diff(phi) / df * 1e12
|
|
1079
|
+
|
|
1080
|
+
# dispersion in ps/nm
|
|
1081
|
+
Dpsnm = -0.5 / numpy.pi / cnmps * f[1:-1] ** 2 * numpy.diff(phi, 2) / df[0:-1] ** 2
|
|
1082
|
+
|
|
1083
|
+
return phi, tau, Dpsnm
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
def rix2losses(n, wl):
|
|
1087
|
+
"""Return real(n), imag(n), alpha, alpha_cm1, alpha_dBcm1, given a
|
|
1088
|
+
complex refractive index. Power goes as: P = P0 exp(-alpha*z)."""
|
|
1089
|
+
nr = numpy.real(n)
|
|
1090
|
+
ni = numpy.imag(n)
|
|
1091
|
+
alpha = 4 * numpy.pi * ni / wl
|
|
1092
|
+
alpha_cm1 = alpha / 100.0
|
|
1093
|
+
alpha_dBcm1 = 10 * numpy.log10(numpy.exp(1)) * alpha_cm1
|
|
1094
|
+
return nr, ni, alpha, alpha_cm1, alpha_dBcm1
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
def loss_cm2rix(n_real, alpha_cm1, wl):
|
|
1098
|
+
"""Return complex refractive index, given real index (n_real), absorption
|
|
1099
|
+
coefficient (alpha_cm1) in cm^-1, and wavelength (wl) in meters.
|
|
1100
|
+
Do not pass more than one argument as array, will return erroneous result."""
|
|
1101
|
+
ni = 100 * alpha_cm1 * wl / (numpy.pi * 4)
|
|
1102
|
+
return n_real - 1j * ni
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def loss_m2rix(n_real, alpha_m1, wl):
|
|
1106
|
+
"""Return complex refractive index, given real index (n_real), absorption
|
|
1107
|
+
coefficient (alpha_m1) in m^-1, and wavelength (wl) in meters.
|
|
1108
|
+
Do not pass more than one argument as array, will return erroneous result."""
|
|
1109
|
+
ni = alpha_m1 * wl / (numpy.pi * 4)
|
|
1110
|
+
return n_real - 1j * ni
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
def loss_dBcm2rix(n_real, alpha_dBcm1, wl):
|
|
1114
|
+
"""Return complex refractive index, given real index (n_real), absorption
|
|
1115
|
+
coefficient (alpha_dBcm1) in dB/cm, and wavelength (wl) in meters.
|
|
1116
|
+
Do not pass more than one argument as array, will return erroneous result."""
|
|
1117
|
+
ni = 10 * alpha_dBcm1 * wl / (numpy.log10(numpy.exp(1)) * 4 * numpy.pi)
|
|
1118
|
+
return n_real - 1j * ni
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def wl2f(wl0, dwl):
|
|
1122
|
+
"""Convert a central wavelength and an interval to frequency."""
|
|
1123
|
+
wl1 = wl0 - dwl / 2.0
|
|
1124
|
+
wl2 = wl0 + dwl / 2.0
|
|
1125
|
+
f1 = EMpy.constants.c / wl2
|
|
1126
|
+
f2 = EMpy.constants.c / wl1
|
|
1127
|
+
f0 = (f1 + f2) / 2.0
|
|
1128
|
+
df = f2 - f1
|
|
1129
|
+
return f0, df
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
def f2wl(f0, df):
|
|
1133
|
+
"""Convert a central frequency and an interval to wavelength."""
|
|
1134
|
+
return wl2f(f0, df)
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def find_peaks(x, y, threshold=1e-6):
|
|
1138
|
+
# find peaks' candidates
|
|
1139
|
+
dy = numpy.diff(y)
|
|
1140
|
+
ddy = numpy.diff(numpy.sign(dy))
|
|
1141
|
+
# idxs = numpy.where(ddy < 0)[0] + 1
|
|
1142
|
+
idxs = numpy.where(ddy < 0)
|
|
1143
|
+
|
|
1144
|
+
if len(idxs) == 0:
|
|
1145
|
+
# there is only 1 min in f, so the max is on either boundary
|
|
1146
|
+
# get the max and set FWHM = 0
|
|
1147
|
+
idx = numpy.argmax(y)
|
|
1148
|
+
p = Peak(x[idx], y[idx], idx, x[idx], y[idx], x[idx], x[idx])
|
|
1149
|
+
# return a list of one element
|
|
1150
|
+
return [p]
|
|
1151
|
+
|
|
1152
|
+
# refine search with splines
|
|
1153
|
+
tck = scipy.interpolate.splrep(x, y)
|
|
1154
|
+
|
|
1155
|
+
# look for zero derivative
|
|
1156
|
+
def absdy(x_):
|
|
1157
|
+
return numpy.abs(scipy.interpolate.splev(x_, tck, der=1))
|
|
1158
|
+
|
|
1159
|
+
peaks = []
|
|
1160
|
+
for idx in idxs:
|
|
1161
|
+
|
|
1162
|
+
# look around the candidate
|
|
1163
|
+
xtol = (x.max() - x.min()) * 1e-6
|
|
1164
|
+
xopt = scipy.optimize.fminbound(
|
|
1165
|
+
absdy, x[idx - 1], x[idx + 1], xtol=xtol, disp=False
|
|
1166
|
+
)
|
|
1167
|
+
yopt = scipy.interpolate.splev(xopt, tck)
|
|
1168
|
+
|
|
1169
|
+
if yopt > threshold * y.max():
|
|
1170
|
+
|
|
1171
|
+
# FWHM
|
|
1172
|
+
tckFWHM = scipy.interpolate.splrep(x, y - 0.5 * yopt)
|
|
1173
|
+
roots = scipy.interpolate.sproot(tckFWHM)
|
|
1174
|
+
|
|
1175
|
+
idxFWHM = numpy.searchsorted(roots, xopt)
|
|
1176
|
+
if idxFWHM <= 0:
|
|
1177
|
+
xFWHM_1 = x[0]
|
|
1178
|
+
else:
|
|
1179
|
+
xFWHM_1 = roots[idxFWHM - 1]
|
|
1180
|
+
if idxFWHM >= len(roots):
|
|
1181
|
+
xFWHM_2 = x[-1]
|
|
1182
|
+
else:
|
|
1183
|
+
xFWHM_2 = roots[idxFWHM]
|
|
1184
|
+
|
|
1185
|
+
p = Peak(xopt, yopt, idx, x[idx], y[idx], xFWHM_1, xFWHM_2)
|
|
1186
|
+
peaks.append(p)
|
|
1187
|
+
|
|
1188
|
+
def cmp_y(x_, y_):
|
|
1189
|
+
# to sort in descending order
|
|
1190
|
+
if x_.y == y_.y:
|
|
1191
|
+
return 0
|
|
1192
|
+
if x_.y > y_.y:
|
|
1193
|
+
return -1
|
|
1194
|
+
return 1
|
|
1195
|
+
|
|
1196
|
+
peaks.sort(cmp=cmp_y)
|
|
1197
|
+
|
|
1198
|
+
return peaks
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
def cond(M):
|
|
1202
|
+
"""Return the condition number of the 2D array M."""
|
|
1203
|
+
svdv = scipy.linalg.svdvals(M)
|
|
1204
|
+
return svdv.max() / svdv.min()
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
def interp2(x, y, xp, yp, fp):
|
|
1208
|
+
"""Interpolate a 2D complex array.
|
|
1209
|
+
|
|
1210
|
+
:rtype : numpy.array
|
|
1211
|
+
"""
|
|
1212
|
+
f1r = numpy.zeros((len(xp), len(y)))
|
|
1213
|
+
f1i = numpy.zeros((len(xp), len(y)))
|
|
1214
|
+
for ixp in range(len(xp)):
|
|
1215
|
+
f1r[ixp, :] = numpy.interp(y, yp, numpy.real(fp[ixp, :]))
|
|
1216
|
+
f1i[ixp, :] = numpy.interp(y, yp, numpy.imag(fp[ixp, :]))
|
|
1217
|
+
fr = numpy.zeros((len(x), len(y)))
|
|
1218
|
+
fi = numpy.zeros((len(x), len(y)))
|
|
1219
|
+
for iy in range(len(y)):
|
|
1220
|
+
fr[:, iy] = numpy.interp(x, xp, f1r[:, iy])
|
|
1221
|
+
fi[:, iy] = numpy.interp(x, xp, f1i[:, iy])
|
|
1222
|
+
return fr + 1j * fi
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
def trapz2(f, x=None, y=None, dx=1.0, dy=1.0):
|
|
1226
|
+
"""Double integrate."""
|
|
1227
|
+
return numpy.trapz(numpy.trapz(f, x=y, dx=dy), x=x, dx=dx)
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
def centered1d(x):
|
|
1231
|
+
return (x[1:] + x[:-1]) / 2.0
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
def centered2d(x):
|
|
1235
|
+
return (x[1:, 1:] + x[1:, :-1] + x[:-1, 1:] + x[:-1, :-1]) / 4.0
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
def blackbody(f, T):
|
|
1239
|
+
return (
|
|
1240
|
+
2
|
|
1241
|
+
* EMpy.constants.h
|
|
1242
|
+
* f**3
|
|
1243
|
+
/ (EMpy.constants.c**2)
|
|
1244
|
+
* 1.0
|
|
1245
|
+
/ (numpy.exp(EMpy.constants.h * f / (EMpy.constants.k * T)) - 1)
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
def warning(s):
|
|
1250
|
+
"""Print a warning on the stdout.
|
|
1251
|
+
|
|
1252
|
+
:param s: warning message
|
|
1253
|
+
:type s: str
|
|
1254
|
+
:rtype : str
|
|
1255
|
+
"""
|
|
1256
|
+
print("WARNING --- {}".format(s))
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
class ProgressBar:
|
|
1260
|
+
|
|
1261
|
+
"""Creates a text-based progress bar. Call the object with the `print'
|
|
1262
|
+
command to see the progress bar, which looks something like this:
|
|
1263
|
+
|
|
1264
|
+
[=======> 22% ]
|
|
1265
|
+
|
|
1266
|
+
You may specify the progress bar's width, min and max values on init.
|
|
1267
|
+
"""
|
|
1268
|
+
|
|
1269
|
+
def __init__(self, minValue=0, maxValue=100, totalWidth=80):
|
|
1270
|
+
self.progBar = "[]" # This holds the progress bar string
|
|
1271
|
+
self.min = minValue
|
|
1272
|
+
self.max = maxValue
|
|
1273
|
+
self.span = maxValue - minValue
|
|
1274
|
+
self.width = totalWidth
|
|
1275
|
+
self.reset()
|
|
1276
|
+
|
|
1277
|
+
def reset(self):
|
|
1278
|
+
self.start_time = time.time()
|
|
1279
|
+
self.amount = 0 # When amount == max, we are 100% done
|
|
1280
|
+
self.updateAmount(0) # Build progress bar string
|
|
1281
|
+
|
|
1282
|
+
def updateAmount(self, newAmount=0):
|
|
1283
|
+
"""Update the progress bar with the new amount (with min and max
|
|
1284
|
+
values set at initialization; if it is over or under, it takes the
|
|
1285
|
+
min or max value as a default."""
|
|
1286
|
+
if newAmount < self.min:
|
|
1287
|
+
newAmount = self.min
|
|
1288
|
+
if newAmount > self.max:
|
|
1289
|
+
newAmount = self.max
|
|
1290
|
+
self.amount = newAmount
|
|
1291
|
+
|
|
1292
|
+
# Figure out the new percent done, round to an integer
|
|
1293
|
+
diffFromMin = float(self.amount - self.min)
|
|
1294
|
+
percentDone = (diffFromMin / float(self.span)) * 100.0
|
|
1295
|
+
percentDone = int(round(percentDone))
|
|
1296
|
+
|
|
1297
|
+
# Figure out how many hash bars the percentage should be
|
|
1298
|
+
allFull = self.width - 2 - 18
|
|
1299
|
+
numHashes = (percentDone / 100.0) * allFull
|
|
1300
|
+
numHashes = int(round(numHashes))
|
|
1301
|
+
|
|
1302
|
+
# Build a progress bar with an arrow of equal signs; special cases for
|
|
1303
|
+
# empty and full
|
|
1304
|
+
if numHashes == 0:
|
|
1305
|
+
self.progBar = "[>%s]" % (" " * (allFull - 1))
|
|
1306
|
+
elif numHashes == allFull:
|
|
1307
|
+
self.progBar = "[%s]" % ("=" * allFull)
|
|
1308
|
+
else:
|
|
1309
|
+
self.progBar = "[%s>%s]" % (
|
|
1310
|
+
"=" * (numHashes - 1),
|
|
1311
|
+
" " * (allFull - numHashes),
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
# figure out where to put the percentage, roughly centered
|
|
1315
|
+
percentPlace = (len(self.progBar) / 2) - len(str(percentDone))
|
|
1316
|
+
percentString = " " + str(percentDone) + "% "
|
|
1317
|
+
|
|
1318
|
+
elapsed_time = time.time() - self.start_time
|
|
1319
|
+
|
|
1320
|
+
# slice the percentage into the bar
|
|
1321
|
+
self.progBar = "".join(
|
|
1322
|
+
[
|
|
1323
|
+
self.progBar[0:percentPlace],
|
|
1324
|
+
percentString,
|
|
1325
|
+
self.progBar[percentPlace + len(percentString) :],
|
|
1326
|
+
]
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
if percentDone > 0:
|
|
1330
|
+
self.progBar += " %6ds / %6ds" % (
|
|
1331
|
+
int(elapsed_time),
|
|
1332
|
+
int(elapsed_time * (100.0 / percentDone - 1)),
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
def update(self, value, every=1):
|
|
1336
|
+
"""Updates the amount, and writes to stdout. Prints a carriage return
|
|
1337
|
+
first, so it will overwrite the current line in stdout."""
|
|
1338
|
+
if value % every == 0 or value >= self.max:
|
|
1339
|
+
print("\r", end=" ")
|
|
1340
|
+
self.updateAmount(value)
|
|
1341
|
+
sys.stdout.write(self.progBar)
|
|
1342
|
+
sys.stdout.flush()
|