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/devices.py
ADDED
|
@@ -0,0 +1,1414 @@
|
|
|
1
|
+
# pylint: disable=C0302,W0622,W1001,R0902,R0903,W0201,R0913,R0914,C0325,W0221
|
|
2
|
+
|
|
3
|
+
"""Set of objects and functions to describe the behaviour of some
|
|
4
|
+
widely known devices and build new ones.
|
|
5
|
+
|
|
6
|
+
A set of widely known devices is described, by its transfer function,
|
|
7
|
+
transfer matrix and chain matrix:
|
|
8
|
+
|
|
9
|
+
- Coupler.
|
|
10
|
+
- Line.
|
|
11
|
+
- Mach-Zehnder Interferometer.
|
|
12
|
+
- All-Pass Ring Resonator.
|
|
13
|
+
- Single Ring Resonator.
|
|
14
|
+
- N-Rings Resonator.
|
|
15
|
+
- Tunable Triple-Coupled Ring Resonator.
|
|
16
|
+
- Tunable Coupled Ring Triple-Coupled Ring Resonator.
|
|
17
|
+
- Tunable Coupled Triple-Coupled Ring Resonator.
|
|
18
|
+
- Straight Waveguide.
|
|
19
|
+
- Etalon.
|
|
20
|
+
|
|
21
|
+
By combining the transfer matrices and chain matrices of known
|
|
22
|
+
devices, new ones can be studied.
|
|
23
|
+
|
|
24
|
+
@see: U{Schwelb, "Transmission, Group Delay, and Dispersion in
|
|
25
|
+
Single-Ring Optical Resonators and Add/Drop Filters - A Tutorial
|
|
26
|
+
Overview", JLT 22(5), p. 1380, 2004
|
|
27
|
+
<http://jlt.osa.org/abstract.cfm?id=80103>}
|
|
28
|
+
@see: U{Barbarossa, "Theoretical Analysis of Triple-Coupler Ring-Based
|
|
29
|
+
Optical Guided-Wave Resonator", JLT 13(2), p. 148, 1995
|
|
30
|
+
<http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?tp=&arnumber=365200&
|
|
31
|
+
isnumber=8367>}
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
__author__ = "Lorenzo Bolla"
|
|
35
|
+
|
|
36
|
+
import numpy
|
|
37
|
+
import EMpy.utils
|
|
38
|
+
from functools import reduce
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DeviceMatrix:
|
|
42
|
+
|
|
43
|
+
"""Device Matrix.
|
|
44
|
+
|
|
45
|
+
Base class for TransferMatrix and ChainMatrix.
|
|
46
|
+
|
|
47
|
+
Notes
|
|
48
|
+
=====
|
|
49
|
+
|
|
50
|
+
- Data is stored as numpy.matrix.
|
|
51
|
+
- It is a virtual class.
|
|
52
|
+
|
|
53
|
+
@ivar data: Device matrix data, automatically converted into a
|
|
54
|
+
numpy.matrix on assignment.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, data):
|
|
59
|
+
self.data = data
|
|
60
|
+
|
|
61
|
+
def getdata(self):
|
|
62
|
+
return self.__data
|
|
63
|
+
|
|
64
|
+
def setdata(self, value):
|
|
65
|
+
self.__data = numpy.asmatrix(value)
|
|
66
|
+
|
|
67
|
+
__data = None
|
|
68
|
+
data = property(fget=getdata, fset=setdata, doc="data")
|
|
69
|
+
|
|
70
|
+
def compose(self, A):
|
|
71
|
+
"""Compose two device matrices.
|
|
72
|
+
|
|
73
|
+
@raise NotImplementedError: always, because DeviceMatrix is a
|
|
74
|
+
virtual class.
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
raise NotImplementedError("DeviceMatrix is virtual class.")
|
|
78
|
+
|
|
79
|
+
def __str__(self):
|
|
80
|
+
return self.data.__str__()
|
|
81
|
+
|
|
82
|
+
__repr__ = __str__
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TransferMatrix(DeviceMatrix):
|
|
86
|
+
|
|
87
|
+
"""Transfer Matrix.
|
|
88
|
+
|
|
89
|
+
Notation::
|
|
90
|
+
|
|
91
|
+
b1 = h11 h12 * a1 --> b12 = H a12
|
|
92
|
+
b2 h21 h22 a2
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, data):
|
|
97
|
+
DeviceMatrix.__init__(self, data)
|
|
98
|
+
|
|
99
|
+
def to_transfer(self):
|
|
100
|
+
"""Convert to transfer matrix (nothing to do).
|
|
101
|
+
|
|
102
|
+
@rtype: L{TransferMatrix}
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def to_chain(self):
|
|
109
|
+
"""Convert to chain matrix.
|
|
110
|
+
|
|
111
|
+
Notation::
|
|
112
|
+
|
|
113
|
+
b1 = h11 a1 + h12 a2
|
|
114
|
+
--> a1 = (b2 - h22 a2) / h21 = 1 / h21 b2 - h22 / h21 a2
|
|
115
|
+
b2 = h21 a1 + h22 a2
|
|
116
|
+
--> b1 = h11 / h21 * (b2 - h22 a2) + h12 a2
|
|
117
|
+
= h11 / h21 b2 + (h12 * h21 - h11 * h22) / h12 a2
|
|
118
|
+
|
|
119
|
+
@rtype: L{ChainMatrix}
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
return ChainMatrix(
|
|
123
|
+
[
|
|
124
|
+
[1.0 / self.data[1, 0], -self.data[1, 1] / self.data[1, 0]],
|
|
125
|
+
[
|
|
126
|
+
self.data[0, 0] / self.data[1, 0],
|
|
127
|
+
(
|
|
128
|
+
self.data[0, 1] * self.data[1, 0]
|
|
129
|
+
- self.data[0, 0] * self.data[1, 1]
|
|
130
|
+
)
|
|
131
|
+
/ self.data[1, 0],
|
|
132
|
+
],
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def compose(self, M):
|
|
137
|
+
"""Compose the transfer matrix with another device matrix.
|
|
138
|
+
|
|
139
|
+
Notation::
|
|
140
|
+
|
|
141
|
+
b12 = H1 a12 and d12 = H2 c12, but b12 == c12,
|
|
142
|
+
|
|
143
|
+
therefore::
|
|
144
|
+
|
|
145
|
+
d12 = H2*H1 a12.
|
|
146
|
+
|
|
147
|
+
Notes
|
|
148
|
+
=====
|
|
149
|
+
|
|
150
|
+
First, convert the second matrix to transfer.
|
|
151
|
+
|
|
152
|
+
@param M: DeviceMatrix (will be converted to TransferMatrix).
|
|
153
|
+
@rtype: L{TransferMatrix}
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
return TransferMatrix(M.to_transfer().data * self.data)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class ChainMatrix(DeviceMatrix):
|
|
161
|
+
|
|
162
|
+
"""Chain Matrix.
|
|
163
|
+
|
|
164
|
+
Notation::
|
|
165
|
+
|
|
166
|
+
a1 = g11 g12 * b2 --> ab1 = G ba2
|
|
167
|
+
b1 g21 g22 a2
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(self, *args, **kwargs):
|
|
172
|
+
DeviceMatrix.__init__(self, *args, **kwargs)
|
|
173
|
+
|
|
174
|
+
def to_transfer(self):
|
|
175
|
+
"""Convert to transfer matrix.
|
|
176
|
+
|
|
177
|
+
Notation::
|
|
178
|
+
|
|
179
|
+
a1 = g11 b2 + g12 a2
|
|
180
|
+
b2 = 1 / g11 a1 - g12 / g11 a2 = 1 / g11 a1 - g12 / g11 a2
|
|
181
|
+
b1 = g21 b2 + g22 a2
|
|
182
|
+
b1 = g21 / g11 a1 + (g22 - g12 * g21 / g11) a2
|
|
183
|
+
= g21 / g11 a1 + (g11 g22 - g12 g21) / g11 a2
|
|
184
|
+
|
|
185
|
+
@rtype: L{TransferMatrix}
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
return TransferMatrix(
|
|
189
|
+
[
|
|
190
|
+
[
|
|
191
|
+
self.data[1, 0] / self.data[0, 0],
|
|
192
|
+
(
|
|
193
|
+
self.data[0, 0] * self.data[1, 1]
|
|
194
|
+
- self.data[0, 1] * self.data[1, 0]
|
|
195
|
+
)
|
|
196
|
+
/ self.data[0, 0],
|
|
197
|
+
],
|
|
198
|
+
[1.0 / self.data[0, 0], -self.data[0, 1] / self.data[0, 0]],
|
|
199
|
+
]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def to_chain(self):
|
|
203
|
+
"""Convert to chain matrix (nothing to do).
|
|
204
|
+
|
|
205
|
+
@rtype: L{ChainMatrix}
|
|
206
|
+
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
return self
|
|
210
|
+
|
|
211
|
+
def compose(self, M):
|
|
212
|
+
"""Compose the chain matrix with another device matrix.
|
|
213
|
+
|
|
214
|
+
Notation::
|
|
215
|
+
|
|
216
|
+
ab1 = G1 ba2 and ab3 = G2 ba4, but ba2 == ab3,
|
|
217
|
+
|
|
218
|
+
therefore::
|
|
219
|
+
|
|
220
|
+
ab1 = G1*G2 ba4.
|
|
221
|
+
|
|
222
|
+
Notes
|
|
223
|
+
=====
|
|
224
|
+
|
|
225
|
+
First, convert the second matrix to chain.
|
|
226
|
+
|
|
227
|
+
@param M: L{DeviceMatrix} (will be converted to L{ChainMatrix}).
|
|
228
|
+
@rtype: L{ChainMatrix}
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
return ChainMatrix(self.data * M.to_chain().data)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def composeTM(A, B):
|
|
236
|
+
"""Compose two transfer matrices.
|
|
237
|
+
|
|
238
|
+
Notes
|
|
239
|
+
=====
|
|
240
|
+
|
|
241
|
+
First convert them to TM, to be sure.
|
|
242
|
+
|
|
243
|
+
@param A: First L{TransferMatrix}.
|
|
244
|
+
@param B: Second L{TransferMatrix}.
|
|
245
|
+
@rtype: L{TransferMatrix}
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
return A.to_transfer().compose(B)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def composeCM(A, B):
|
|
253
|
+
"""Compose two chain matrices.
|
|
254
|
+
|
|
255
|
+
Notes
|
|
256
|
+
=====
|
|
257
|
+
|
|
258
|
+
First convert them to CM, to be sure.
|
|
259
|
+
|
|
260
|
+
@param A: First L{ChainMatrix}.
|
|
261
|
+
@param B: Second L{ChainMatrix}.
|
|
262
|
+
@rtype: L{ChainMatrix}
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
return A.to_chain().compose(B)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def composeTMlist(TMlist):
|
|
270
|
+
"""Compose n transfer matrices in a list.
|
|
271
|
+
|
|
272
|
+
@param TMlist: List of L{TransferMatrix}.
|
|
273
|
+
@rtype: L{TransferMatrix}
|
|
274
|
+
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
return reduce(composeTM, TMlist)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def composeCMlist(CMlist):
|
|
281
|
+
"""Compose n chain matrices in a list.
|
|
282
|
+
|
|
283
|
+
@param CMlist: List of L{ChainMatrix}.
|
|
284
|
+
@rtype: L{ChainMatrix}
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
return reduce(composeCM, CMlist)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class Device:
|
|
292
|
+
|
|
293
|
+
"""Generic device.
|
|
294
|
+
|
|
295
|
+
Notes
|
|
296
|
+
=====
|
|
297
|
+
|
|
298
|
+
Parent virtual class for all the other devices.
|
|
299
|
+
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
def sanity_check(self):
|
|
303
|
+
raise NotImplementedError()
|
|
304
|
+
|
|
305
|
+
def solve(self, *args, **kwargs):
|
|
306
|
+
raise NotImplementedError()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class Coupler(Device):
|
|
310
|
+
|
|
311
|
+
"""Coupler.
|
|
312
|
+
|
|
313
|
+
Examples
|
|
314
|
+
========
|
|
315
|
+
|
|
316
|
+
Here is how to compute the THRU and DROP of a 3dB coupler, with
|
|
317
|
+
wavelength-dependent losses:
|
|
318
|
+
|
|
319
|
+
>>> wls = numpy.linspace(1.52e-6, 1.57e-6, 1000)
|
|
320
|
+
>>> k = .5 ** .5
|
|
321
|
+
>>> q = numpy.linspace(.9, .95, 1000)
|
|
322
|
+
>>> coupler = EMpy.devices.Coupler(wls, k, q).solve()
|
|
323
|
+
|
|
324
|
+
@ivar wl: Wavelength.
|
|
325
|
+
@type wl: numpy.ndarray
|
|
326
|
+
@ivar K: Field coupling coefficient.
|
|
327
|
+
@type K: numpy.ndarray
|
|
328
|
+
@ivar q: Field coupling loss.
|
|
329
|
+
@type q: numpy.ndarray
|
|
330
|
+
@ivar THRU: also called "bar".
|
|
331
|
+
@type THRU: numpy.ndarray
|
|
332
|
+
@ivar DROP: also called "cross".
|
|
333
|
+
@type DROP: numpy.ndarray
|
|
334
|
+
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
def __init__(self, wl, K, q=1.0):
|
|
338
|
+
"""Set the parameters of the coupler and check them."""
|
|
339
|
+
|
|
340
|
+
self.wl = wl
|
|
341
|
+
one = numpy.ones_like(wl)
|
|
342
|
+
# make K,q of the same shape as wl (scalar or ndarray)
|
|
343
|
+
self.K = K * one
|
|
344
|
+
self.q = q * one
|
|
345
|
+
self.sanity_check()
|
|
346
|
+
|
|
347
|
+
def sanity_check(self):
|
|
348
|
+
"""Check for good input.
|
|
349
|
+
|
|
350
|
+
@raise ValueError: if input numpy.ndarrays have wrong shape.
|
|
351
|
+
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
if (numpy.isscalar(self.q) or numpy.isscalar(self.K)) and not numpy.isscalar(
|
|
355
|
+
self.wl
|
|
356
|
+
):
|
|
357
|
+
raise ValueError("wl is not a scalar but K and q are.")
|
|
358
|
+
if (
|
|
359
|
+
not numpy.isscalar(self.q) or not numpy.isscalar(self.K)
|
|
360
|
+
) and numpy.isscalar(self.wl):
|
|
361
|
+
raise ValueError("wl is a scalar but K and q are not.")
|
|
362
|
+
if not (
|
|
363
|
+
numpy.asarray(self.wl).shape
|
|
364
|
+
== numpy.asarray(self.K).shape
|
|
365
|
+
== numpy.asarray(self.q).shape
|
|
366
|
+
):
|
|
367
|
+
raise ValueError("wl, K and q must have the same shape")
|
|
368
|
+
|
|
369
|
+
def solve(self):
|
|
370
|
+
"""Compute the THRU and DROP."""
|
|
371
|
+
|
|
372
|
+
Kbar = numpy.sqrt(1.0 - self.K**2)
|
|
373
|
+
self.THRU = self.q * Kbar
|
|
374
|
+
self.DROP = self.q * 1j * self.K
|
|
375
|
+
|
|
376
|
+
return self
|
|
377
|
+
|
|
378
|
+
def TM(self, wl=None):
|
|
379
|
+
"""Return the L{TransferMatrix} of the L{Coupler}, for a given wl.
|
|
380
|
+
|
|
381
|
+
Notation::
|
|
382
|
+
|
|
383
|
+
TM = q * [ t jk] , with t = (1 - k**2)**.5
|
|
384
|
+
[jk t]
|
|
385
|
+
|
|
386
|
+
@param wl: Wavelength at which computing the TM. If None, use
|
|
387
|
+
the only wl provided at construction time.
|
|
388
|
+
@type wl: scalar
|
|
389
|
+
|
|
390
|
+
@return: L{TransferMatrix}.
|
|
391
|
+
@rtype: L{TransferMatrix}
|
|
392
|
+
|
|
393
|
+
@raise ValueError: (if wl=None and self.wl is not scalar) or
|
|
394
|
+
wl is not scalar.
|
|
395
|
+
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
if wl is None:
|
|
399
|
+
if not numpy.isscalar(self.wl):
|
|
400
|
+
raise ValueError("which wl?")
|
|
401
|
+
K = self.K
|
|
402
|
+
q = self.q
|
|
403
|
+
elif numpy.isscalar(wl):
|
|
404
|
+
K = numpy.interp(
|
|
405
|
+
numpy.atleast_1d(wl),
|
|
406
|
+
numpy.atleast_1d(self.wl),
|
|
407
|
+
numpy.atleast_1d(self.K),
|
|
408
|
+
).item()
|
|
409
|
+
q = numpy.interp(
|
|
410
|
+
numpy.atleast_1d(wl),
|
|
411
|
+
numpy.atleast_1d(self.wl),
|
|
412
|
+
numpy.atleast_1d(self.q),
|
|
413
|
+
).item()
|
|
414
|
+
else:
|
|
415
|
+
raise ValueError("wl must be scalar")
|
|
416
|
+
|
|
417
|
+
Kbar = numpy.sqrt(1 - K**2)
|
|
418
|
+
return TransferMatrix([[q * Kbar, q * 1j * K], [q * 1j * K, q * Kbar]])
|
|
419
|
+
|
|
420
|
+
def CM(self, wl=None):
|
|
421
|
+
"""Return the L{ChainMatrix} of the coupler.
|
|
422
|
+
|
|
423
|
+
Notes
|
|
424
|
+
=====
|
|
425
|
+
|
|
426
|
+
First build its L{TransferMatrix}, then convert it to L{ChainMatrix}.
|
|
427
|
+
|
|
428
|
+
@param wl: Wavelength at which computing the L{ChainMatrix}.
|
|
429
|
+
|
|
430
|
+
@return: L{ChainMatrix}.
|
|
431
|
+
@rtype: L{ChainMatrix}
|
|
432
|
+
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
return self.TM(wl).to_chain()
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class Line(Device):
|
|
439
|
+
|
|
440
|
+
"""Line.
|
|
441
|
+
|
|
442
|
+
Notes
|
|
443
|
+
=====
|
|
444
|
+
|
|
445
|
+
The Line device is a four ports device, hence is made of two
|
|
446
|
+
two-ports lines.
|
|
447
|
+
|
|
448
|
+
Examples
|
|
449
|
+
========
|
|
450
|
+
|
|
451
|
+
Here is how to create a L{Line}, where the first line is a 2.pi.R1
|
|
452
|
+
long L{SWG} and the second has zero length.
|
|
453
|
+
|
|
454
|
+
>>> wls = numpy.linspace(1.52e-6, 1.57e-6, 1000)
|
|
455
|
+
>>> SWG = EMpy.devices.SWG(400, 220, 125).solve(wls)
|
|
456
|
+
>>> R1 = 5.664e-6
|
|
457
|
+
>>> line = EMpy.devices.Line(wls, SWG.neff, 2 * numpy.pi * R1, SWG.neff, 0)
|
|
458
|
+
|
|
459
|
+
@ivar wl: Wavelength.
|
|
460
|
+
@type wl: numpy.ndarray
|
|
461
|
+
@ivar neff1: Effective index of the first line.
|
|
462
|
+
@type neff1: numpy.ndarray
|
|
463
|
+
@ivar l1: Length of the first line.
|
|
464
|
+
@type l1: scalar
|
|
465
|
+
@ivar neff2: Effective index of the second line.
|
|
466
|
+
@type neff2: numpy.ndarray
|
|
467
|
+
@ivar l2: Length of the second line.
|
|
468
|
+
@type l2: scalar
|
|
469
|
+
@ivar THRU: output of the first line.
|
|
470
|
+
@type THRU: numpy.ndarray
|
|
471
|
+
@ivar DROP: output of the second line.
|
|
472
|
+
@type DROP: numpy.ndarray
|
|
473
|
+
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
def __init__(self, wl, neff1, l1, neff2, l2):
|
|
477
|
+
"""Set the parameters of the line and check them."""
|
|
478
|
+
|
|
479
|
+
self.wl = wl
|
|
480
|
+
one = numpy.ones_like(wl)
|
|
481
|
+
self.neff1 = neff1 * one
|
|
482
|
+
self.l1 = l1
|
|
483
|
+
self.neff2 = neff2 * one
|
|
484
|
+
self.l2 = l2
|
|
485
|
+
self.sanity_check()
|
|
486
|
+
|
|
487
|
+
def sanity_check(self):
|
|
488
|
+
"""Check for good input.
|
|
489
|
+
|
|
490
|
+
@raise ValueError: if l1 and l2 are not scalar.
|
|
491
|
+
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
if not (numpy.isscalar(self.l1) and numpy.isscalar(self.l2)):
|
|
495
|
+
raise ValueError("lengths must be scalars")
|
|
496
|
+
|
|
497
|
+
def solve(self):
|
|
498
|
+
"""Compute the THRU and DROP."""
|
|
499
|
+
|
|
500
|
+
beta1 = 2 * numpy.pi * self.neff1 / self.wl
|
|
501
|
+
beta2 = 2 * numpy.pi * self.neff2 / self.wl
|
|
502
|
+
self.THRU = numpy.exp(-1j * beta1 * self.l1)
|
|
503
|
+
self.DROP = numpy.exp(-1j * beta2 * self.l2)
|
|
504
|
+
|
|
505
|
+
return self
|
|
506
|
+
|
|
507
|
+
def TM(self, wl=None):
|
|
508
|
+
"""Return the L{TransferMatrix} of the L{Line}, for a given wl.
|
|
509
|
+
|
|
510
|
+
Notation::
|
|
511
|
+
|
|
512
|
+
TM = [exp(-1j * beta1 * l1) 0 ]
|
|
513
|
+
[0 exp(-1j * beta2 * l2)]
|
|
514
|
+
|
|
515
|
+
@param wl: Wavelength at which computing the TM. If None, use
|
|
516
|
+
the only wl provided at construction time.
|
|
517
|
+
@type wl: scalar
|
|
518
|
+
|
|
519
|
+
@return: L{TransferMatrix}.
|
|
520
|
+
@rtype: L{TransferMatrix}
|
|
521
|
+
|
|
522
|
+
@raise ValueError: (if wl=None and self.wl is not scalar) or
|
|
523
|
+
wl is not scalar.
|
|
524
|
+
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
if wl is None:
|
|
528
|
+
if not numpy.isscalar(self.wl):
|
|
529
|
+
raise ValueError("which wl?")
|
|
530
|
+
neff1 = self.neff1
|
|
531
|
+
neff2 = self.neff2
|
|
532
|
+
wl = self.wl
|
|
533
|
+
elif numpy.isscalar(wl):
|
|
534
|
+
neff1 = numpy.interp(
|
|
535
|
+
numpy.atleast_1d(wl),
|
|
536
|
+
numpy.atleast_1d(self.wl),
|
|
537
|
+
numpy.atleast_1d(self.neff1),
|
|
538
|
+
).item()
|
|
539
|
+
neff2 = numpy.interp(
|
|
540
|
+
numpy.atleast_1d(wl),
|
|
541
|
+
numpy.atleast_1d(self.wl),
|
|
542
|
+
numpy.atleast_1d(self.neff2),
|
|
543
|
+
).item()
|
|
544
|
+
else:
|
|
545
|
+
raise ValueError("wl must be scalar")
|
|
546
|
+
|
|
547
|
+
beta1 = 2 * numpy.pi * neff1 / wl
|
|
548
|
+
beta2 = 2 * numpy.pi * neff2 / wl
|
|
549
|
+
return TransferMatrix(
|
|
550
|
+
[
|
|
551
|
+
[numpy.exp(-1j * beta1 * self.l1), 0],
|
|
552
|
+
[0, numpy.exp(-1j * beta2 * self.l2)],
|
|
553
|
+
]
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
def CM(self, wl=None):
|
|
557
|
+
"""Return the L{ChainMatrix} of the line.
|
|
558
|
+
|
|
559
|
+
Notes
|
|
560
|
+
=====
|
|
561
|
+
|
|
562
|
+
First build its L{TransferMatrix}, then convert it to L{ChainMatrix}.
|
|
563
|
+
|
|
564
|
+
@return: L{ChainMatrix}.
|
|
565
|
+
@rtype: L{ChainMatrix}
|
|
566
|
+
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
return self.TM(wl).to_chain()
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class MZ(Device):
|
|
573
|
+
|
|
574
|
+
"""Mach-Zehnder Interferometer (MZI).
|
|
575
|
+
|
|
576
|
+
A MZI is made of two L{Coupler}s connected by a L{Line}.
|
|
577
|
+
A different phase shift on either arms of the L{Line} determines
|
|
578
|
+
the amount of power in bar (THRU) or cross (DROP).
|
|
579
|
+
|
|
580
|
+
Examples
|
|
581
|
+
========
|
|
582
|
+
|
|
583
|
+
Here is how to build a MZI:
|
|
584
|
+
|
|
585
|
+
>>> wls = numpy.linspace(1.52e-6, 1.57e-6, 1000)
|
|
586
|
+
>>> SWG = EMpy.devices.SWG(400, 220, 125).solve(wls)
|
|
587
|
+
>>> R1 = 5.664e-6
|
|
588
|
+
>>> Kbus2ringeq = .5
|
|
589
|
+
>>> coupler = EMpy.devices.Coupler(wls, Kbus2ringeq**.5)
|
|
590
|
+
>>> line = EMpy.devices.Line(wls, SWG.neff, 2 * numpy.pi * R1, SWG.neff, 0)
|
|
591
|
+
>>> mz = EMpy.devices.MZ(coupler, line, coupler).solve()
|
|
592
|
+
|
|
593
|
+
@ivar coupler1: First L{Coupler}
|
|
594
|
+
@type coupler1: L{Coupler}
|
|
595
|
+
@ivar line: L{Line} connecting the two L{Coupler}s.
|
|
596
|
+
@type line: L{Line}
|
|
597
|
+
@ivar coupler2: Second L{Coupler}
|
|
598
|
+
@type coupler2: L{Coupler}
|
|
599
|
+
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
def __init__(self, coupler1, line, coupler2):
|
|
603
|
+
"""Set the parameters of the MZ and check them."""
|
|
604
|
+
|
|
605
|
+
self.wl = coupler1.wl
|
|
606
|
+
self.coupler1 = coupler1
|
|
607
|
+
self.coupler2 = coupler2
|
|
608
|
+
self.line = line
|
|
609
|
+
self.sanity_check()
|
|
610
|
+
|
|
611
|
+
def sanity_check(self):
|
|
612
|
+
"""Check for good input.
|
|
613
|
+
|
|
614
|
+
@raise ValueError: if coupler1 or coupler2 or line have incompatible wl.
|
|
615
|
+
|
|
616
|
+
"""
|
|
617
|
+
|
|
618
|
+
self.coupler1.sanity_check()
|
|
619
|
+
self.coupler2.sanity_check()
|
|
620
|
+
self.line.sanity_check()
|
|
621
|
+
if not (
|
|
622
|
+
numpy.alltrue(self.coupler1.wl == self.line.wl)
|
|
623
|
+
and numpy.alltrue(self.coupler1.wl == self.coupler2.wl)
|
|
624
|
+
):
|
|
625
|
+
raise ValueError("incompatible wl")
|
|
626
|
+
|
|
627
|
+
def solve(self):
|
|
628
|
+
"""Compute the THRU and DROP."""
|
|
629
|
+
|
|
630
|
+
K1 = self.coupler1.K
|
|
631
|
+
K2 = self.coupler2.K
|
|
632
|
+
K1bar = numpy.sqrt(1.0 - K1**2)
|
|
633
|
+
K2bar = numpy.sqrt(1.0 - K2**2)
|
|
634
|
+
q1 = self.coupler1.q
|
|
635
|
+
q2 = self.coupler2.q
|
|
636
|
+
|
|
637
|
+
beta1 = 2.0 * numpy.pi * self.line.neff1 / self.wl
|
|
638
|
+
beta2 = 2.0 * numpy.pi * self.line.neff2 / self.wl
|
|
639
|
+
|
|
640
|
+
dephasing1 = numpy.exp(-1j * beta1 * self.line.l1)
|
|
641
|
+
dephasing2 = numpy.exp(-1j * beta2 * self.line.l2)
|
|
642
|
+
|
|
643
|
+
self.THRU = q1 * q2 * (K1bar * K2bar * dephasing1 - K1 * K2 * dephasing2)
|
|
644
|
+
self.DROP = q1 * q2 * 1j * (K1 * K2bar * dephasing2 + K2 * K1bar * dephasing1)
|
|
645
|
+
|
|
646
|
+
return self
|
|
647
|
+
|
|
648
|
+
def TM(self, wl=None):
|
|
649
|
+
"""Return the transfer matrix of the MZ.
|
|
650
|
+
|
|
651
|
+
Notes
|
|
652
|
+
=====
|
|
653
|
+
|
|
654
|
+
Compose the TM of the single devices which compose the MZ.
|
|
655
|
+
|
|
656
|
+
@return: L{TransferMatrix}.
|
|
657
|
+
@rtype: L{TransferMatrix}
|
|
658
|
+
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
return composeTMlist(
|
|
662
|
+
[self.coupler1.TM(wl), self.line.TM(wl), self.coupler2.TM(wl)]
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
def CM(self, wl=None):
|
|
666
|
+
"""Return the chain matrix of the MZ.
|
|
667
|
+
|
|
668
|
+
Notes
|
|
669
|
+
=====
|
|
670
|
+
|
|
671
|
+
First build its L{TransferMatrix}, then convert it to L{ChainMatrix}.
|
|
672
|
+
|
|
673
|
+
@return: L{ChainMatrix}.
|
|
674
|
+
@rtype: L{ChainMatrix}
|
|
675
|
+
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
return self.TM(wl).to_chain()
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
class APRR(Device):
|
|
682
|
+
|
|
683
|
+
"""All-Pass Ring Resonator.
|
|
684
|
+
|
|
685
|
+
An APRR is made of a L{Coupler} with a feedback line (the ring).
|
|
686
|
+
|
|
687
|
+
Examples
|
|
688
|
+
========
|
|
689
|
+
|
|
690
|
+
>>> wls = numpy.linspace(1.5e-6, 1.6e-6, 1000)
|
|
691
|
+
>>> K = numpy.sqrt(0.08)
|
|
692
|
+
>>> q = 1.
|
|
693
|
+
>>> l = 2*numpy.pi*5e-6
|
|
694
|
+
>>> SWG = EMpy.devices.SWG(488, 220, 25).solve(wls)
|
|
695
|
+
>>> coupler = EMpy.devices.Coupler(wls, K, q)
|
|
696
|
+
>>> APRR = EMpy.devices.APRR(coupler, SWG.neff, l).solve()
|
|
697
|
+
|
|
698
|
+
@ivar coupler: L{Coupler}.
|
|
699
|
+
@type coupler: L{Coupler}
|
|
700
|
+
@ivar neff: Effective index of the feedback line.
|
|
701
|
+
@type neff: numpy.ndarray
|
|
702
|
+
@ivar l: Length of the feedback line (perimeter of the ring).
|
|
703
|
+
@type l: scalar
|
|
704
|
+
@ivar THRU: THRU of the all-pass.
|
|
705
|
+
@type THRU: numpy.ndarray
|
|
706
|
+
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
def __init__(self, coupler, neff, l):
|
|
710
|
+
"""Set the parameters of the all-pass and check them."""
|
|
711
|
+
|
|
712
|
+
self.wl = coupler.wl
|
|
713
|
+
self.coupler = coupler
|
|
714
|
+
self.neff = neff
|
|
715
|
+
self.l = l
|
|
716
|
+
self.sanity_check()
|
|
717
|
+
|
|
718
|
+
def sanity_check(self):
|
|
719
|
+
"""Check for good input.
|
|
720
|
+
|
|
721
|
+
@raise ValueError: if l is not a scalar or wl and neff have
|
|
722
|
+
different shapes.
|
|
723
|
+
|
|
724
|
+
"""
|
|
725
|
+
|
|
726
|
+
self.coupler.sanity_check()
|
|
727
|
+
if not numpy.isscalar(self.wl):
|
|
728
|
+
if not numpy.isscalar(self.neff):
|
|
729
|
+
if self.wl.shape != self.neff.shape:
|
|
730
|
+
raise ValueError("wl and neff must have the same shape.")
|
|
731
|
+
if not numpy.isscalar(self.l):
|
|
732
|
+
raise ValueError("l must be scalar.")
|
|
733
|
+
|
|
734
|
+
def solve(self):
|
|
735
|
+
"""Compute the THRU."""
|
|
736
|
+
|
|
737
|
+
K = self.coupler.K
|
|
738
|
+
q = self.coupler.q
|
|
739
|
+
|
|
740
|
+
Kbar = numpy.sqrt(1.0 - K**2)
|
|
741
|
+
beta = 2.0 * numpy.pi * self.neff / self.wl
|
|
742
|
+
t = numpy.exp(-1j * beta * self.l)
|
|
743
|
+
self.THRU = q * (Kbar - q * t) / (1.0 - q * Kbar * t)
|
|
744
|
+
|
|
745
|
+
return self
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
class SRR(Device):
|
|
749
|
+
|
|
750
|
+
"""Single Ring Resonator.
|
|
751
|
+
|
|
752
|
+
A SRR is made of the parallel of a L{Coupler} a feedback line (the
|
|
753
|
+
ring) and another L{Coupler}.
|
|
754
|
+
|
|
755
|
+
Examples
|
|
756
|
+
========
|
|
757
|
+
|
|
758
|
+
>>> wls = numpy.linspace(1.5e-6, 1.6e-6, 1000)
|
|
759
|
+
>>> coupler1 = EMpy.devices.Coupler(wls, 0.08**.5)
|
|
760
|
+
>>> coupler2 = EMpy.devices.Coupler(wls, 0.08**.5)
|
|
761
|
+
>>> R = 5e-6
|
|
762
|
+
>>> l1 = numpy.pi * R
|
|
763
|
+
>>> l2 = numpy.pi * R
|
|
764
|
+
>>> SWG = EMpy.devices.SWG(488, 220, 25).solve(wls)
|
|
765
|
+
>>> SRR = EMpy.devices.SRR(coupler1, coupler2, SWG.neff, l1, l2).solve()
|
|
766
|
+
|
|
767
|
+
@ivar coupler1: Bus-to-Ring L{Coupler}.
|
|
768
|
+
@type coupler1: L{Coupler}
|
|
769
|
+
@ivar coupler2: Ring-to-Bus L{Coupler}.
|
|
770
|
+
@type coupler2: L{Coupler}
|
|
771
|
+
@ivar neff: Effective index of the feedback line.
|
|
772
|
+
@type neff: numpy.ndarray
|
|
773
|
+
@ivar l1: Length of the feedback line (perimeter of the ring) from
|
|
774
|
+
coupler1 to coupler2.
|
|
775
|
+
@type l1: scalar
|
|
776
|
+
@ivar l2: Length of the feedback line (perimeter of the ring) from
|
|
777
|
+
coupler2 to coupler1.
|
|
778
|
+
@type l2: scalar
|
|
779
|
+
@ivar THRU: THRU of the filter.
|
|
780
|
+
@type THRU: numpy.ndarray
|
|
781
|
+
@ivar DROP: DROP of the filter.
|
|
782
|
+
@type DROP: numpy.ndarray
|
|
783
|
+
|
|
784
|
+
"""
|
|
785
|
+
|
|
786
|
+
def __init__(self, coupler1, coupler2, neff, l1, l2):
|
|
787
|
+
"""Set the parameters of the device and check them."""
|
|
788
|
+
|
|
789
|
+
self.wl = coupler1.wl
|
|
790
|
+
self.coupler1 = coupler1
|
|
791
|
+
self.coupler2 = coupler2
|
|
792
|
+
self.neff = neff
|
|
793
|
+
self.l1 = l1
|
|
794
|
+
self.l2 = l2
|
|
795
|
+
self.line1 = Line(self.wl, neff, 0.0, neff, l1)
|
|
796
|
+
self.line2 = Line(self.wl, neff, 0.0, neff, l2)
|
|
797
|
+
self.sanity_check()
|
|
798
|
+
|
|
799
|
+
def sanity_check(self):
|
|
800
|
+
"""Check for good input.
|
|
801
|
+
|
|
802
|
+
@raise ValueError: if wl are incompatible.
|
|
803
|
+
|
|
804
|
+
"""
|
|
805
|
+
|
|
806
|
+
self.coupler1.sanity_check()
|
|
807
|
+
self.coupler2.sanity_check()
|
|
808
|
+
self.line1.sanity_check()
|
|
809
|
+
self.line2.sanity_check()
|
|
810
|
+
if not (
|
|
811
|
+
numpy.alltrue(self.coupler1.wl == self.coupler2.wl)
|
|
812
|
+
and numpy.alltrue(self.coupler1.wl == self.line1.wl)
|
|
813
|
+
and numpy.alltrue(self.coupler1.wl == self.line2.wl)
|
|
814
|
+
):
|
|
815
|
+
raise ValueError("incompatible wl")
|
|
816
|
+
|
|
817
|
+
def solve(self):
|
|
818
|
+
"""Compute the THRU and the DROP."""
|
|
819
|
+
|
|
820
|
+
K1 = self.coupler1.K
|
|
821
|
+
K2 = self.coupler2.K
|
|
822
|
+
K1bar = numpy.sqrt(1.0 - K1**2)
|
|
823
|
+
K2bar = numpy.sqrt(1.0 - K2**2)
|
|
824
|
+
q1 = self.coupler1.q
|
|
825
|
+
q2 = self.coupler2.q
|
|
826
|
+
l1 = self.line1.l2
|
|
827
|
+
l2 = self.line2.l2
|
|
828
|
+
|
|
829
|
+
beta = 2.0 * numpy.pi * self.neff / self.wl
|
|
830
|
+
|
|
831
|
+
t1 = numpy.exp(-1j * beta * l1)
|
|
832
|
+
t2 = numpy.exp(-1j * beta * l2)
|
|
833
|
+
|
|
834
|
+
denom = 1.0 - q1 * q2 * K1bar * K2bar * t1 * t2
|
|
835
|
+
self.DROP = -q1 * q2 * K1 * K2 * t2 / denom
|
|
836
|
+
self.THRU = q1 * (K1bar - q1 * q2 * K2bar * t1 * t2) / denom
|
|
837
|
+
|
|
838
|
+
return self
|
|
839
|
+
|
|
840
|
+
def TM(self, wl=None):
|
|
841
|
+
"""Return the L{TransferMatrix} of the SRR.
|
|
842
|
+
|
|
843
|
+
Notes
|
|
844
|
+
=====
|
|
845
|
+
|
|
846
|
+
First build its L{ChainMatrix}, then convert it to L{TransferMatrix}.
|
|
847
|
+
|
|
848
|
+
@return: L{TransferMatrix}.
|
|
849
|
+
@rtype: L{TransferMatrix}
|
|
850
|
+
|
|
851
|
+
"""
|
|
852
|
+
|
|
853
|
+
return self.CM(wl).to_transfer()
|
|
854
|
+
|
|
855
|
+
def CM(self, wl=None):
|
|
856
|
+
"""Return the L{ChainMatrix} of the SRR.
|
|
857
|
+
|
|
858
|
+
Notes
|
|
859
|
+
=====
|
|
860
|
+
|
|
861
|
+
Compose the CM of the single devices which compose the SRR.
|
|
862
|
+
|
|
863
|
+
@return: L{ChainMatrix}.
|
|
864
|
+
@rtype: L{ChainMatrix}
|
|
865
|
+
|
|
866
|
+
"""
|
|
867
|
+
|
|
868
|
+
return composeCM(
|
|
869
|
+
composeTMlist([self.line1.TM(wl), self.coupler1.TM(wl), self.line2.TM(wl)]),
|
|
870
|
+
self.coupler2.CM(wl),
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
class NRR(Device):
|
|
875
|
+
|
|
876
|
+
"""N-Rings Resonator.
|
|
877
|
+
|
|
878
|
+
A NRR is made of the parallel of two lines connected by N ring resonators.
|
|
879
|
+
|
|
880
|
+
Examples
|
|
881
|
+
========
|
|
882
|
+
|
|
883
|
+
>>> wls = numpy.linspace(1.5e-6, 1.6e-6, 1000)
|
|
884
|
+
>>> from EMpy.devices import Coupler
|
|
885
|
+
>>> couplers = [Coupler(wls, 0.08**.5), Coupler(wls, 0.003**.5),
|
|
886
|
+
Coupler(wls, 0.08**.5)]
|
|
887
|
+
>>> q = [1., 1., 1.]
|
|
888
|
+
>>> R = 5e-6
|
|
889
|
+
>>> l1s = [numpy.pi * R, numpy.pi * R]
|
|
890
|
+
>>> l2s = [numpy.pi * R, numpy.pi * R]
|
|
891
|
+
>>> SWG = EMpy.devices.SWG(488, 220, 25).solve(wls)
|
|
892
|
+
>>> neffs = [SWG.neff, SWG.neff]
|
|
893
|
+
>>> NRR = EMpy.devices.NRR(couplers, neffs, l1s, l2s).solve()
|
|
894
|
+
|
|
895
|
+
@ivar Ks: List of L{Coupler}s.
|
|
896
|
+
@type Ks: list
|
|
897
|
+
@ivar neffs: List of rings' effective indexes.
|
|
898
|
+
@type neffs: numpy.ndarray
|
|
899
|
+
@ivar l1s: List of rings' arcs connecting one coupler to the next.
|
|
900
|
+
@type l1s: list
|
|
901
|
+
@ivar l2s: List of rings' arcs connecting one coupler to the previous.
|
|
902
|
+
@type l2s: list
|
|
903
|
+
@ivar THRU: THRU of the filter.
|
|
904
|
+
@type THRU: numpy.ndarray
|
|
905
|
+
@ivar DROP: DROP of the filter.
|
|
906
|
+
@type DROP: numpy.ndarray
|
|
907
|
+
|
|
908
|
+
"""
|
|
909
|
+
|
|
910
|
+
def __init__(self, Ks, neffs, l1s, l2s):
|
|
911
|
+
"""Set the parameters of the device and check them."""
|
|
912
|
+
|
|
913
|
+
self.wl = Ks[0].wl
|
|
914
|
+
self.Ks = Ks
|
|
915
|
+
self.neffs = neffs
|
|
916
|
+
self.l1s = l1s
|
|
917
|
+
self.l2s = l2s
|
|
918
|
+
self.sanity_check()
|
|
919
|
+
|
|
920
|
+
def sanity_check(self):
|
|
921
|
+
"""Check for good input.
|
|
922
|
+
|
|
923
|
+
@raise ValueError: if wl or the number of couplers are incompatible.
|
|
924
|
+
|
|
925
|
+
"""
|
|
926
|
+
|
|
927
|
+
for K in self.Ks:
|
|
928
|
+
K.sanity_check()
|
|
929
|
+
if not (numpy.alltrue(self.wl == K.wl)):
|
|
930
|
+
raise ValueError("incompatible wl")
|
|
931
|
+
if not (len(self.Ks) - 1 == len(self.neffs) == len(self.l1s) == len(self.l2s)):
|
|
932
|
+
raise ValueError("number of couplers and number of rings do not match.")
|
|
933
|
+
|
|
934
|
+
def solve(self):
|
|
935
|
+
"""Compute the THRU and the DROP.
|
|
936
|
+
|
|
937
|
+
Notes
|
|
938
|
+
=====
|
|
939
|
+
|
|
940
|
+
No analytic expression is available, therefore build the TM of
|
|
941
|
+
the device for each wl.
|
|
942
|
+
|
|
943
|
+
"""
|
|
944
|
+
|
|
945
|
+
self.THRU = numpy.zeros_like(self.wl).astype("complex")
|
|
946
|
+
self.DROP = numpy.zeros_like(self.wl).astype("complex")
|
|
947
|
+
for iwl, wl in enumerate(self.wl):
|
|
948
|
+
H = self.TM(wl)
|
|
949
|
+
self.THRU[iwl] = H.data[0, 0]
|
|
950
|
+
self.DROP[iwl] = H.data[0, 1]
|
|
951
|
+
|
|
952
|
+
return self
|
|
953
|
+
|
|
954
|
+
def TM(self, wl=None):
|
|
955
|
+
"""Return the L{TransferMatrix} of the NRR.
|
|
956
|
+
|
|
957
|
+
Notes
|
|
958
|
+
=====
|
|
959
|
+
|
|
960
|
+
Compose the TM of the single devices which compose the NRR.
|
|
961
|
+
|
|
962
|
+
@return: L{TransferMatrix}.
|
|
963
|
+
@rtype: L{TransferMatrix}
|
|
964
|
+
|
|
965
|
+
"""
|
|
966
|
+
|
|
967
|
+
return self.CM(wl).to_transfer()
|
|
968
|
+
|
|
969
|
+
def CM(self, wl=None):
|
|
970
|
+
"""Return the L{TransferMatrix} of the NRR.
|
|
971
|
+
|
|
972
|
+
Notes
|
|
973
|
+
=====
|
|
974
|
+
|
|
975
|
+
First build its L{TransferMatrix}, then convert it to L{ChainMatrix}.
|
|
976
|
+
|
|
977
|
+
@return: L{ChainMatrix}.
|
|
978
|
+
@rtype: L{ChainMatrix}
|
|
979
|
+
|
|
980
|
+
"""
|
|
981
|
+
|
|
982
|
+
if wl is None:
|
|
983
|
+
wl = self.wl
|
|
984
|
+
Hs = []
|
|
985
|
+
for K, neff, l1, l2 in zip(self.Ks, self.neffs, self.l1s, self.l2s):
|
|
986
|
+
Hs.append(
|
|
987
|
+
composeTMlist(
|
|
988
|
+
[
|
|
989
|
+
Line(self.wl, neff, 0.0, neff, l1).TM(wl),
|
|
990
|
+
K.TM(wl),
|
|
991
|
+
Line(self.wl, neff, 0.0, neff, l2).TM(wl),
|
|
992
|
+
]
|
|
993
|
+
)
|
|
994
|
+
)
|
|
995
|
+
return composeCM(composeTMlist(Hs), self.Ks[-1].CM(wl))
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
class T_TCRR(Device):
|
|
999
|
+
|
|
1000
|
+
"""Tunable Triple-Coupled Ring Resonator.
|
|
1001
|
+
|
|
1002
|
+
A T_TCRR is made of a L{MZ}, with a feedback line.
|
|
1003
|
+
|
|
1004
|
+
"""
|
|
1005
|
+
|
|
1006
|
+
def __init__(self, neff, K, q, l, coupling=None):
|
|
1007
|
+
"""Set the parameters of the device.
|
|
1008
|
+
|
|
1009
|
+
INPUT
|
|
1010
|
+
neff = effective index (can be complex).
|
|
1011
|
+
K = field coupling coeffs.
|
|
1012
|
+
q = coupler losses.
|
|
1013
|
+
l = ring arcs.
|
|
1014
|
+
coupling = 'optimum' to change K2, K3 and l.
|
|
1015
|
+
"""
|
|
1016
|
+
|
|
1017
|
+
self.neff = numpy.asarray(neff)
|
|
1018
|
+
self.K = numpy.asarray(K)
|
|
1019
|
+
self.q = q
|
|
1020
|
+
self.l = numpy.asarray(l)
|
|
1021
|
+
self.coupling = coupling
|
|
1022
|
+
self.sanity_check()
|
|
1023
|
+
|
|
1024
|
+
def sanity_check(self):
|
|
1025
|
+
"""Check for good input."""
|
|
1026
|
+
if self.K.shape != (3,):
|
|
1027
|
+
raise ValueError("K must be a 1D-array with 3 elements.")
|
|
1028
|
+
if not numpy.isscalar(self.q):
|
|
1029
|
+
raise ValueError("q must be a scalar.")
|
|
1030
|
+
if self.l.shape != (4,):
|
|
1031
|
+
raise ValueError("l must be a 1D-array with 4 elements.")
|
|
1032
|
+
|
|
1033
|
+
def solve(self, wls):
|
|
1034
|
+
"""Compute the THRU and the DROP."""
|
|
1035
|
+
neff, K, q, l, coupling = (self.neff, self.K, self.q, self.l, self.coupling)
|
|
1036
|
+
|
|
1037
|
+
# transform the input in ndarrays
|
|
1038
|
+
wls = numpy.asarray(wls)
|
|
1039
|
+
if wls.shape != neff.shape:
|
|
1040
|
+
raise ValueError("wrong wls and neff shape.")
|
|
1041
|
+
|
|
1042
|
+
if coupling == "optimum":
|
|
1043
|
+
# optimum: l2 = 2l1, l3 = l4
|
|
1044
|
+
circ = l[0] + l[2] + l[3]
|
|
1045
|
+
l = circ * numpy.array([0.5, 1.0, 0.25, 0.25])
|
|
1046
|
+
# optimum: K1==K2, K3=2*K1*K1bar
|
|
1047
|
+
K[1] = K[0]
|
|
1048
|
+
K[2] = 2.0 * K[0] * numpy.sqrt(1 - K[0] ** 2)
|
|
1049
|
+
|
|
1050
|
+
Kbar = numpy.sqrt(1.0 - K**2)
|
|
1051
|
+
beta = 2.0 * numpy.pi * neff / wls
|
|
1052
|
+
|
|
1053
|
+
t1 = numpy.exp(-1j * beta * l[0])
|
|
1054
|
+
t2 = numpy.exp(-1j * beta * l[1])
|
|
1055
|
+
t3 = numpy.exp(-1j * beta * l[2])
|
|
1056
|
+
t4 = numpy.exp(-1j * beta * l[3])
|
|
1057
|
+
|
|
1058
|
+
denom = (
|
|
1059
|
+
q**3 * Kbar[2] * t3 * t4 * (Kbar[0] * Kbar[1] * t1 - K[0] * K[1] * t2)
|
|
1060
|
+
- 1.0
|
|
1061
|
+
)
|
|
1062
|
+
self.DROP = (
|
|
1063
|
+
q**3 * K[2] * t3 * (K[0] * Kbar[1] * t1 + Kbar[0] * K[1] * t2) / denom
|
|
1064
|
+
)
|
|
1065
|
+
self.THRU = (
|
|
1066
|
+
q**2 * (K[0] * K[1] * t1 - Kbar[0] * Kbar[1] * t2)
|
|
1067
|
+
+ q**5 * Kbar[2] * t1 * t2 * t3 * t4
|
|
1068
|
+
) / denom
|
|
1069
|
+
|
|
1070
|
+
return self
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
class T_CRTCRR(Device):
|
|
1074
|
+
|
|
1075
|
+
"""Tunable Coupled Ring Triple-Coupled Ring Resonator.
|
|
1076
|
+
|
|
1077
|
+
NOTE
|
|
1078
|
+
see 1997_IEEE Proc El_Barbarossa_Novel
|
|
1079
|
+
"""
|
|
1080
|
+
|
|
1081
|
+
def __init__(self, neff, K, q, l, coupling=None):
|
|
1082
|
+
"""Set the parameters of the device.
|
|
1083
|
+
|
|
1084
|
+
INPUT
|
|
1085
|
+
neff = effective index (can be complex).
|
|
1086
|
+
K = field coupling coeffs.
|
|
1087
|
+
q = coupler losses.
|
|
1088
|
+
l = ring arcs.
|
|
1089
|
+
coupling = 'optimum' <not implemented yet>.
|
|
1090
|
+
"""
|
|
1091
|
+
|
|
1092
|
+
self.neff = numpy.asarray(neff)
|
|
1093
|
+
self.K = numpy.asarray(K)
|
|
1094
|
+
self.q = q
|
|
1095
|
+
self.l = numpy.asarray(l)
|
|
1096
|
+
self.coupling = coupling
|
|
1097
|
+
self.sanity_check()
|
|
1098
|
+
|
|
1099
|
+
def sanity_check(self):
|
|
1100
|
+
"""Check for good input."""
|
|
1101
|
+
if not (self.K.shape == self.l.shape == (3,)):
|
|
1102
|
+
raise ValueError("K and l must be 1D-arrays with 3 elements.")
|
|
1103
|
+
if not numpy.isscalar(self.q):
|
|
1104
|
+
raise ValueError("q must be a scalar.")
|
|
1105
|
+
|
|
1106
|
+
def solve(self, wls):
|
|
1107
|
+
"""Compute the THRU and the DROP."""
|
|
1108
|
+
neff, K, q, l, _ = (self.neff, self.K, self.q, self.l, self.coupling)
|
|
1109
|
+
|
|
1110
|
+
# transform the input in ndarrays
|
|
1111
|
+
wls = numpy.asarray(wls)
|
|
1112
|
+
if wls.shape != neff.shape:
|
|
1113
|
+
raise ValueError("wrong wls and neff shape.")
|
|
1114
|
+
|
|
1115
|
+
Kbar = numpy.sqrt(1.0 - K**2)
|
|
1116
|
+
beta = 2.0 * numpy.pi * neff / wls
|
|
1117
|
+
|
|
1118
|
+
t1 = numpy.exp(-1j * beta * l[0])
|
|
1119
|
+
t2 = numpy.exp(-1j * beta * l[1])
|
|
1120
|
+
t3 = numpy.exp(-1j * beta * l[2])
|
|
1121
|
+
t31 = t3 / t1
|
|
1122
|
+
|
|
1123
|
+
N1 = Kbar[0] ** 2 - K[0] ** 2 * t31
|
|
1124
|
+
N1bar = K[0] ** 2 - Kbar[0] ** 2 * t31
|
|
1125
|
+
|
|
1126
|
+
denom = (
|
|
1127
|
+
1
|
|
1128
|
+
- q**3 * t1**2 * N1 * Kbar[2]
|
|
1129
|
+
- q**2 * Kbar[1] * t2**2 * (Kbar[2] - q**3 * t1**2 * N1)
|
|
1130
|
+
)
|
|
1131
|
+
self.DROP = (
|
|
1132
|
+
1j
|
|
1133
|
+
* q**4
|
|
1134
|
+
* K[0]
|
|
1135
|
+
* K[1]
|
|
1136
|
+
* K[2]
|
|
1137
|
+
* Kbar[0]
|
|
1138
|
+
* t1**1.5
|
|
1139
|
+
* t2
|
|
1140
|
+
* (1 + t31)
|
|
1141
|
+
/ denom
|
|
1142
|
+
)
|
|
1143
|
+
self.THRU = (
|
|
1144
|
+
q**2
|
|
1145
|
+
* t1
|
|
1146
|
+
* (
|
|
1147
|
+
N1bar
|
|
1148
|
+
- q**2 * Kbar[1] * Kbar[2] * t2**2 * N1bar
|
|
1149
|
+
+ q**3 * Kbar[2] * t1 * t3
|
|
1150
|
+
- q**5 * Kbar[1] * t1 * t2**2 * t3
|
|
1151
|
+
)
|
|
1152
|
+
/ denom
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
return self
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
class T_CTCRR(Device):
|
|
1159
|
+
|
|
1160
|
+
"""Tunable Coupled Triple Coupled Ring Resonator.
|
|
1161
|
+
|
|
1162
|
+
NOTE
|
|
1163
|
+
see 1997_IEEE Proc El_Barbarossa_Novel
|
|
1164
|
+
"""
|
|
1165
|
+
|
|
1166
|
+
def __init__(self, neff, K, q, l, coupling=None):
|
|
1167
|
+
"""Set the parameters of the device.
|
|
1168
|
+
|
|
1169
|
+
INPUT
|
|
1170
|
+
neff = effective index (can be complex).
|
|
1171
|
+
K = field coupling coeffs.
|
|
1172
|
+
q = coupler losses.
|
|
1173
|
+
l = ring arcs.
|
|
1174
|
+
coupling = 'optimum'.
|
|
1175
|
+
"""
|
|
1176
|
+
|
|
1177
|
+
self.neff = numpy.asarray(neff)
|
|
1178
|
+
self.K = numpy.asarray(K)
|
|
1179
|
+
self.q = q
|
|
1180
|
+
self.l = numpy.asarray(l)
|
|
1181
|
+
self.coupling = coupling
|
|
1182
|
+
self.sanity_check()
|
|
1183
|
+
|
|
1184
|
+
def sanity_check(self):
|
|
1185
|
+
"""Check for good input."""
|
|
1186
|
+
if self.K.shape != (3,):
|
|
1187
|
+
raise ValueError("K must be a 1D-array with 3 elements.")
|
|
1188
|
+
if self.l.shape != (4,):
|
|
1189
|
+
raise ValueError("l must be a 1D-array with 4 elements.")
|
|
1190
|
+
if not numpy.isscalar(self.q):
|
|
1191
|
+
raise ValueError("q must be a scalar.")
|
|
1192
|
+
|
|
1193
|
+
def solve(self, wls):
|
|
1194
|
+
"""Compute the THRU and the DROP."""
|
|
1195
|
+
neff, K, q, l, coupling = (self.neff, self.K, self.q, self.l, self.coupling)
|
|
1196
|
+
|
|
1197
|
+
# transform the input in ndarrays
|
|
1198
|
+
wls = numpy.asarray(wls)
|
|
1199
|
+
if wls.shape != neff.shape:
|
|
1200
|
+
raise ValueError("wrong wls and neff shape.")
|
|
1201
|
+
|
|
1202
|
+
if coupling is not None:
|
|
1203
|
+
# optimum: l2 = 2l1, l3 = l4
|
|
1204
|
+
l[1] = l[0] # OKKIO: check me!
|
|
1205
|
+
l[2] = 2 * l[0]
|
|
1206
|
+
l[3] = 2 * l[1]
|
|
1207
|
+
# optimum: see article
|
|
1208
|
+
K[2] = numpy.absolute(
|
|
1209
|
+
2
|
|
1210
|
+
* K[0]
|
|
1211
|
+
* K[1]
|
|
1212
|
+
* numpy.sqrt(1 - K[0] ** 2)
|
|
1213
|
+
* numpy.sqrt(1 - K[1] ** 2)
|
|
1214
|
+
/ (K[0] ** 2 + K[1] ** 2 - 2 * K[0] ** 2 * K[1] ** 2 - 1)
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
Kbar = numpy.sqrt(1.0 - K**2)
|
|
1218
|
+
beta = 2.0 * numpy.pi * neff / wls
|
|
1219
|
+
|
|
1220
|
+
t1 = numpy.exp(-1j * beta * l[0])
|
|
1221
|
+
t2 = numpy.exp(-1j * beta * l[1])
|
|
1222
|
+
t3 = numpy.exp(-1j * beta * l[2])
|
|
1223
|
+
t4 = numpy.exp(-1j * beta * l[3])
|
|
1224
|
+
t31 = t3 / t1
|
|
1225
|
+
t42 = t4 / t2
|
|
1226
|
+
|
|
1227
|
+
N1 = Kbar[0] ** 2 - K[0] ** 2 * t31
|
|
1228
|
+
N1bar = K[0] ** 2 - Kbar[0] ** 2 * t31
|
|
1229
|
+
N2 = Kbar[1] ** 2 - K[1] ** 2 * t42
|
|
1230
|
+
|
|
1231
|
+
denom = (
|
|
1232
|
+
1.0
|
|
1233
|
+
- q**3 * t1**2 * N1 * Kbar[2]
|
|
1234
|
+
- q**3 * t2**2 * N2 * (Kbar[2] - q**3 * t1**2 * N1)
|
|
1235
|
+
)
|
|
1236
|
+
self.DROP = (
|
|
1237
|
+
1j
|
|
1238
|
+
* q**5
|
|
1239
|
+
* K[0]
|
|
1240
|
+
* K[1]
|
|
1241
|
+
* K[2]
|
|
1242
|
+
* Kbar[0]
|
|
1243
|
+
* Kbar[1]
|
|
1244
|
+
* t1**1.5
|
|
1245
|
+
* t2**1.5
|
|
1246
|
+
* (1 + t31)
|
|
1247
|
+
* (1 + t42)
|
|
1248
|
+
/ denom
|
|
1249
|
+
)
|
|
1250
|
+
self.THRU = (
|
|
1251
|
+
q**5 * t1**2 * t3 * (q**3 * t2**2 * N2 - Kbar[2])
|
|
1252
|
+
+ q**2 * t1 * N1bar * (q**3 * t2**2 * N2 * Kbar[2] - 1.0)
|
|
1253
|
+
) / denom
|
|
1254
|
+
|
|
1255
|
+
return self
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
class SWG(Device):
|
|
1259
|
+
"""SOI rib straight waveguides.
|
|
1260
|
+
|
|
1261
|
+
Compute the effective index, dispersion and group index of a SOI
|
|
1262
|
+
rib straight waveguides.
|
|
1263
|
+
|
|
1264
|
+
Available dimensions:
|
|
1265
|
+
|
|
1266
|
+
- width: 400nm..488nm.
|
|
1267
|
+
- height: 220nm.
|
|
1268
|
+
- temperature: 25..225 centrigrades.
|
|
1269
|
+
|
|
1270
|
+
Notes
|
|
1271
|
+
=====
|
|
1272
|
+
|
|
1273
|
+
The effective index is interpolated from values obtained by FimmWAVE.
|
|
1274
|
+
Interpolation is done by a polynomial of degree 2.
|
|
1275
|
+
|
|
1276
|
+
@ivar w: Width of the waveguide.
|
|
1277
|
+
@type w: scalar
|
|
1278
|
+
@ivar h: Height of the waveguide.
|
|
1279
|
+
@type h: scalar
|
|
1280
|
+
@ivar T: Working temperature.
|
|
1281
|
+
@type T: scalar
|
|
1282
|
+
@ivar neff: Effective index.
|
|
1283
|
+
@type neff: numpy.ndarray
|
|
1284
|
+
@ivar disp: Dispersion.
|
|
1285
|
+
@type disp: numpy.ndarray
|
|
1286
|
+
@ivar ng: Group index.
|
|
1287
|
+
@type ng: numpy.ndarray
|
|
1288
|
+
|
|
1289
|
+
"""
|
|
1290
|
+
|
|
1291
|
+
pf488_25 = [-0.00000006023385, -0.00096294997155, 4.06422992413444]
|
|
1292
|
+
pf488_125 = [-0.00000006391000, -0.00095671206284, 4.08461627147979]
|
|
1293
|
+
pf488_225 = [-0.00000006806052, -0.00094901669805, 4.10613946676196]
|
|
1294
|
+
|
|
1295
|
+
pf400_25 = [1.733732171601458e-007, -1.911057235401467e-003, 4.774734532616097e000]
|
|
1296
|
+
pf400_125 = [1.477287990868007e-007, -1.842388456386622e-003, 4.750925877259235e000]
|
|
1297
|
+
pf400_225 = [1.255208385485759e-007, -1.784557912270417e-003, 4.737972109281652e000]
|
|
1298
|
+
|
|
1299
|
+
def __init__(self, w, h, T):
|
|
1300
|
+
|
|
1301
|
+
self.w = w
|
|
1302
|
+
self.h = h # not used!
|
|
1303
|
+
self.T = T
|
|
1304
|
+
|
|
1305
|
+
def sanity_check(self):
|
|
1306
|
+
pass
|
|
1307
|
+
|
|
1308
|
+
def solve(self, wls):
|
|
1309
|
+
"""Compute neff, disp and ng for a given wls.
|
|
1310
|
+
|
|
1311
|
+
@param wls: Wavelength.
|
|
1312
|
+
@type wls: numpy.ndarray
|
|
1313
|
+
|
|
1314
|
+
@raise ValueError: if w or T are out of bounds (and
|
|
1315
|
+
interpolation is not possible).
|
|
1316
|
+
|
|
1317
|
+
"""
|
|
1318
|
+
|
|
1319
|
+
if (self.w, self.T) == (488, 25):
|
|
1320
|
+
pf = SWG.pf488_25
|
|
1321
|
+
elif (self.w, self.T) == (488, 125):
|
|
1322
|
+
pf = SWG.pf488_125
|
|
1323
|
+
elif (self.w, self.T) == (488, 225):
|
|
1324
|
+
pf = SWG.pf488_225
|
|
1325
|
+
elif (self.w, self.T) == (400, 225):
|
|
1326
|
+
pf = SWG.pf400_25
|
|
1327
|
+
elif (self.w, self.T) == (400, 125):
|
|
1328
|
+
pf = SWG.pf400_125
|
|
1329
|
+
elif (self.w, self.T) == (400, 225):
|
|
1330
|
+
pf = SWG.pf400_225
|
|
1331
|
+
elif self.w < 400 or self.w > 488 or self.T < 25 or self.T > 225:
|
|
1332
|
+
raise ValueError("input out of bounds")
|
|
1333
|
+
else:
|
|
1334
|
+
import scipy.interpolate
|
|
1335
|
+
|
|
1336
|
+
pf_ = numpy.zeros((2, 3, 3))
|
|
1337
|
+
[w_, T_] = numpy.meshgrid([400, 488], [25, 125, 225])
|
|
1338
|
+
pf_[0, 0, :] = SWG.pf400_25
|
|
1339
|
+
pf_[0, 1, :] = SWG.pf400_125
|
|
1340
|
+
pf_[0, 2, :] = SWG.pf400_225
|
|
1341
|
+
pf_[1, 0, :] = SWG.pf488_25
|
|
1342
|
+
pf_[1, 1, :] = SWG.pf488_125
|
|
1343
|
+
pf_[1, 2, :] = SWG.pf488_225
|
|
1344
|
+
pf = [
|
|
1345
|
+
scipy.interpolate.interp2d(w_.T, T_.T, pf_[:, :, i])(self.w, self.T)[0]
|
|
1346
|
+
for i in range(3)
|
|
1347
|
+
]
|
|
1348
|
+
|
|
1349
|
+
# transform the input in an ndarray
|
|
1350
|
+
wls = numpy.asarray(wls)
|
|
1351
|
+
|
|
1352
|
+
wls_nm = wls * 1e9
|
|
1353
|
+
neff = numpy.polyval(pf, wls_nm)
|
|
1354
|
+
# disp = dneff/dlambda [nm^-1]
|
|
1355
|
+
disp = numpy.polyval([2 * pf[0], pf[1]], wls_nm)
|
|
1356
|
+
# ng = neff - lambda * disp
|
|
1357
|
+
ng = neff - disp * wls_nm
|
|
1358
|
+
|
|
1359
|
+
self.neff = neff
|
|
1360
|
+
self.disp = disp
|
|
1361
|
+
self.ng = ng
|
|
1362
|
+
|
|
1363
|
+
return self
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
class Etalon(Device):
|
|
1367
|
+
|
|
1368
|
+
"""Etalon.
|
|
1369
|
+
|
|
1370
|
+
@ivar R: Reflectivity.
|
|
1371
|
+
@ivar theta: Angle of incidence.
|
|
1372
|
+
|
|
1373
|
+
@see: U{http://en.wikipedia.org/wiki/Etalon}
|
|
1374
|
+
|
|
1375
|
+
"""
|
|
1376
|
+
|
|
1377
|
+
def __init__(self, layer, R, theta):
|
|
1378
|
+
self.FWHMwl = self.FSRwl / self.FINESSE
|
|
1379
|
+
self.layer = layer
|
|
1380
|
+
self.R = R
|
|
1381
|
+
self.theta = theta
|
|
1382
|
+
|
|
1383
|
+
def sanity_check(self):
|
|
1384
|
+
pass
|
|
1385
|
+
|
|
1386
|
+
def solve(self, wls):
|
|
1387
|
+
"""Compute the frequency response of the etalon."""
|
|
1388
|
+
|
|
1389
|
+
# transform the input in an ndarray
|
|
1390
|
+
wls = numpy.asarray(wls)
|
|
1391
|
+
|
|
1392
|
+
n = self.layer.mat.n(wls)
|
|
1393
|
+
l = self.layer.thickness
|
|
1394
|
+
|
|
1395
|
+
# phase difference between any succeeding reflection
|
|
1396
|
+
self.delta = 2 * numpy.pi / wls * 2 * n * l * numpy.cos(self.theta)
|
|
1397
|
+
# coefficient of finesse
|
|
1398
|
+
self.F = 4.0 * self.R / (1 - self.R) ** 2
|
|
1399
|
+
# transmission function
|
|
1400
|
+
self.Te = 1.0 / (1.0 + self.F * numpy.sin(self.delta / 2.0) ** 2)
|
|
1401
|
+
self.Rmax = 1.0 - 1.0 / (1.0 + self.F)
|
|
1402
|
+
|
|
1403
|
+
self.wl0 = numpy.mean(wls)
|
|
1404
|
+
self.FSRwl = self.wl0**2 / (2 * n * l * numpy.cos(self.theta) + self.wl0)
|
|
1405
|
+
|
|
1406
|
+
(self.f0, self.FSR) = EMpy.utils.wl2f(self.wl0, self.FSRwl)
|
|
1407
|
+
|
|
1408
|
+
self.FINESSE = numpy.pi / (2 * numpy.arcsin(1.0 / numpy.sqrt(self.F)))
|
|
1409
|
+
# self.FINESSE = numpy.pi * numpy.sqrt(self.F) / 2.
|
|
1410
|
+
# self.FINESSE = numpy.pi * numpy.sqrt(self.R) / (1 - self.R)
|
|
1411
|
+
|
|
1412
|
+
self.FWHM = self.FSR / self.FINESSE
|
|
1413
|
+
|
|
1414
|
+
return self
|