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