emsutil 0.1.0__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.
- emsutil/__init__.py +5 -0
- emsutil/const.py +5 -0
- emsutil/emdata.py +493 -0
- emsutil/isola.py +294 -0
- emsutil/lib.py +314 -0
- emsutil/material.py +439 -0
- emsutil/plot/__init__.py +0 -0
- emsutil/plot/plot2d.py +837 -0
- emsutil/rogers.py +58 -0
- emsutil-0.1.0.dist-info/METADATA +14 -0
- emsutil-0.1.0.dist-info/RECORD +12 -0
- emsutil-0.1.0.dist-info/WHEEL +4 -0
emsutil/__init__.py
ADDED
emsutil/const.py
ADDED
emsutil/emdata.py
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Literal, Callable
|
|
4
|
+
from .const import Z0, EPS0
|
|
5
|
+
from .lib import EISO, EOMNI
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FarFieldComponent:
|
|
9
|
+
F: np.ndarray
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def x(self) -> np.ndarray:
|
|
13
|
+
return self.F[0,:]
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def y(self) -> np.ndarray:
|
|
17
|
+
return self.F[1,:]
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def z(self) -> np.ndarray:
|
|
21
|
+
return self.F[2,:]
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def theta(self) -> np.ndarray:
|
|
25
|
+
thx = np.cos(self.theta)*np.cos(self.phi)
|
|
26
|
+
thy = np.cos(self.theta)*np.sin(self.phi)
|
|
27
|
+
thz = -np.sin(self.theta)
|
|
28
|
+
return thx*self.F[0,:] + thy*self.F[1,:] + thz*self.F[2,:]
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def phi(self) -> np.ndarray:
|
|
32
|
+
phx = -np.sin(self.phi)
|
|
33
|
+
phy = np.cos(self.phi)
|
|
34
|
+
phz = np.zeros_like(self.theta)
|
|
35
|
+
return phx*self.F[0,:] + phy*self.F[1,:] + phz*self.F[2,:]
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def rhcp(self) -> np.ndarray:
|
|
39
|
+
return (self.theta + 1j*self.phi)/np.sqrt(2)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def lhcp(self) -> np.ndarray:
|
|
43
|
+
return (self.theta - 1j*self.phi)/np.sqrt(2)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def AR(self) -> np.ndarray:
|
|
47
|
+
R = np.abs(self.rhcp)
|
|
48
|
+
L = np.abs(self.lchp)
|
|
49
|
+
return (R+L)/(R-L)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def norm(self) -> np.ndarray:
|
|
53
|
+
return np.sqrt(np.abs(self.F[0,:])**2 + np.abs(self.F[1,:])**2 + np.abs(self.F[2,:])**2)
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class FarFieldData:
|
|
57
|
+
_E: np.ndarray
|
|
58
|
+
_H: np.ndarray
|
|
59
|
+
theta: np.ndarray
|
|
60
|
+
phi: np.ndarray
|
|
61
|
+
Ptot: float
|
|
62
|
+
ang: np.ndarray | None = None
|
|
63
|
+
|
|
64
|
+
def total_radiated_power_integral(
|
|
65
|
+
self,
|
|
66
|
+
r: float = 1.0,
|
|
67
|
+
use: str = "EH", # "EH" or "E"
|
|
68
|
+
degrees: bool | None = None, # auto if None
|
|
69
|
+
) -> float:
|
|
70
|
+
E = np.asarray(self._E)
|
|
71
|
+
H = np.asarray(self._H)
|
|
72
|
+
th = np.asarray(self.theta, float)
|
|
73
|
+
ph = np.asarray(self.phi, float)
|
|
74
|
+
|
|
75
|
+
if E.shape[:1] != (3,) or E.ndim != 3:
|
|
76
|
+
raise ValueError(f"_E must be (3,N,M), got {E.shape}")
|
|
77
|
+
if th.shape != E.shape[1:] or ph.shape != E.shape[1:]:
|
|
78
|
+
raise ValueError(f"theta/phi must be (N,M) matching _E[1:], got {th.shape}, {ph.shape}")
|
|
79
|
+
if use.upper() == "EH" and H.shape != E.shape:
|
|
80
|
+
raise ValueError(f"_H must match _E for use='EH', got {H.shape}")
|
|
81
|
+
|
|
82
|
+
if degrees is None:
|
|
83
|
+
degrees = (np.nanmax(th) > 2*np.pi + 0.5) or (np.nanmax(ph) > 2*np.pi + 0.5)
|
|
84
|
+
if degrees:
|
|
85
|
+
th = np.deg2rad(th)
|
|
86
|
+
ph = np.deg2rad(ph)
|
|
87
|
+
|
|
88
|
+
# rhat(θ,φ)
|
|
89
|
+
rhat = np.stack(
|
|
90
|
+
[np.sin(th) * np.cos(ph), np.sin(th) * np.sin(ph), np.cos(th)],
|
|
91
|
+
axis=0,
|
|
92
|
+
) # (3,N,M)
|
|
93
|
+
|
|
94
|
+
# S_r
|
|
95
|
+
if use.upper() == "EH":
|
|
96
|
+
S = 0.5 * np.real(
|
|
97
|
+
np.cross(np.moveaxis(E, 0, -1), np.conj(np.moveaxis(H, 0, -1)))
|
|
98
|
+
) # (N,M,3)
|
|
99
|
+
Sr = np.sum(S * np.moveaxis(rhat, 0, -1), axis=-1) # (N,M)
|
|
100
|
+
elif use.upper() == "E":
|
|
101
|
+
Sr = (np.sum(np.abs(E) ** 2, axis=0) / (2.0 * Z0)).real # (N,M)
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError("use must be 'EH' or 'E'")
|
|
104
|
+
|
|
105
|
+
# dΩ for general (θ(i,j), φ(i,j)) grid: dΩ = sinθ * |∂(θ,φ)/∂(i,j)| di dj
|
|
106
|
+
dth_di, dth_dj = np.gradient(th, edge_order=2)
|
|
107
|
+
dph_di, dph_dj = np.gradient(ph, edge_order=2)
|
|
108
|
+
J = dth_di * dph_dj - dth_dj * dph_di # ∂(θ,φ)/∂(i,j)
|
|
109
|
+
dOmega = np.sin(th) * np.abs(J)
|
|
110
|
+
|
|
111
|
+
return float(r**2 * np.sum(Sr * dOmega))
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def E(self) -> np.ndarray:
|
|
115
|
+
return FarFieldComponent(self._E)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def H(self) -> np.ndarray:
|
|
119
|
+
return FarFieldComponent(self._H)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def Ex(self) -> np.ndarray:
|
|
123
|
+
return self._E[0,:]
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def Ey(self) -> np.ndarray:
|
|
127
|
+
return self._E[1,:]
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def Ez(self) -> np.ndarray:
|
|
131
|
+
return self._E[2,:]
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def Hx(self) -> np.ndarray:
|
|
135
|
+
return self._H[0,:]
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def Hy(self) -> np.ndarray:
|
|
139
|
+
return self._H[1,:]
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def Hz(self) -> np.ndarray:
|
|
143
|
+
return self._H[2,:]
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def Etheta(self) -> np.ndarray:
|
|
147
|
+
thx = np.cos(self.theta)*np.cos(self.phi)
|
|
148
|
+
thy = np.cos(self.theta)*np.sin(self.phi)
|
|
149
|
+
thz = -np.sin(self.theta)
|
|
150
|
+
return thx*self._E[0,:] + thy*self._E[1,:] + thz*self._E[2,:]
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def Ephi(self) -> np.ndarray:
|
|
154
|
+
phx = -np.sin(self.phi)
|
|
155
|
+
phy = np.cos(self.phi)
|
|
156
|
+
phz = np.zeros_like(self.theta)
|
|
157
|
+
return phx*self._E[0,:] + phy*self._E[1,:] + phz*self._E[2,:]
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def Erhcp(self) -> np.ndarray:
|
|
161
|
+
return (self.Etheta + 1j*self.Ephi)/np.sqrt(2)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def Elhcp(self) -> np.ndarray:
|
|
165
|
+
return (self.Etheta - 1j*self.Ephi)/np.sqrt(2)
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def AR(self) -> np.ndarray:
|
|
169
|
+
R = np.abs(self.Erhcp)
|
|
170
|
+
L = np.abs(self.Elhcp)
|
|
171
|
+
return (R+L)/(R-L)
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def gain(self, kind: Literal['iso','omni'] = 'iso') -> FarFieldComponent:
|
|
175
|
+
if kind=='iso':
|
|
176
|
+
return FarFieldComponent(self._E/EISO)
|
|
177
|
+
else:
|
|
178
|
+
return FarFieldComponent(self._E/EOMNI)
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def dir(self, kind: Literal['iso','omni'] = 'iso') -> FarFieldComponent:
|
|
182
|
+
if kind=='iso':
|
|
183
|
+
return FarFieldComponent(self._E/(EISO*(self.Ptot)**0.5))
|
|
184
|
+
else:
|
|
185
|
+
return FarFieldComponent(self._E/(EOMNI*(self.Ptot)**0.5))
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def normE(self) -> np.ndarray:
|
|
189
|
+
return np.sqrt(np.abs(self._E[0,:])**2 + np.abs(self._E[1,:])**2 + np.abs(self._E[2,:])**2)
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def normH(self) -> np.ndarray:
|
|
193
|
+
return np.sqrt(np.abs(self._H[0,:])**2 + np.abs(self._H[1,:])**2 + np.abs(self._H[2,:])**2)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def surfplot(self,
|
|
197
|
+
polarization: Literal['Ex','Ey','Ez','Etheta','Ephi','normE','Erhcp','Elhcp','AR'],
|
|
198
|
+
quantity: Literal['abs','real','imag','angle'] = 'abs',
|
|
199
|
+
isotropic: bool = True, dB: bool = False, dBfloor: float = -30, rmax: float | None = None,
|
|
200
|
+
offset: tuple[float, float, float] = (0,0,0)) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
201
|
+
"""Returns the parameters to be used as positional arguments for the display.add_surf() function.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> model.display.add_surf(*dataset.field[n].farfield_3d(...).surfplot())
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
polarization ('Ex','Ey','Ez','Etheta','Ephi','normE'): What quantity to plot
|
|
208
|
+
isotropic (bool, optional): Whether to look at the ratio with isotropic antennas. Defaults to True.
|
|
209
|
+
dB (bool, optional): Whether to plot in dB's. Defaults to False.
|
|
210
|
+
dBfloor (float, optional): The dB value to take as R=0. Defaults to -10.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: The X, Y, Z, F values
|
|
214
|
+
"""
|
|
215
|
+
fmap = {
|
|
216
|
+
'abs': np.abs,
|
|
217
|
+
'real': np.real,
|
|
218
|
+
'imag': np.imag,
|
|
219
|
+
'angle': np.angle,
|
|
220
|
+
}
|
|
221
|
+
mapping = fmap.get(quantity.lower(),np.abs)
|
|
222
|
+
|
|
223
|
+
F = mapping(getattr(self, polarization))
|
|
224
|
+
|
|
225
|
+
if isotropic:
|
|
226
|
+
F = F/np.sqrt(Z0/(2*np.pi))
|
|
227
|
+
if dB:
|
|
228
|
+
F = 20*np.log10(np.clip(np.abs(F), a_min=10**(dBfloor/20), a_max = 1e9))-dBfloor
|
|
229
|
+
if rmax is not None:
|
|
230
|
+
F = rmax * F/np.max(F)
|
|
231
|
+
xs = F*np.sin(self.theta)*np.cos(self.phi) + offset[0]
|
|
232
|
+
ys = F*np.sin(self.theta)*np.sin(self.phi) + offset[1]
|
|
233
|
+
zs = F*np.cos(self.theta) + offset[2]
|
|
234
|
+
|
|
235
|
+
return xs, ys, zs, F
|
|
236
|
+
|
|
237
|
+
@dataclass
|
|
238
|
+
class EHField:
|
|
239
|
+
x: np.ndarray
|
|
240
|
+
y: np.ndarray
|
|
241
|
+
z: np.ndarray
|
|
242
|
+
Ex: np.ndarray
|
|
243
|
+
Ey: np.ndarray
|
|
244
|
+
Ez: np.ndarray
|
|
245
|
+
Hx: np.ndarray
|
|
246
|
+
Hy: np.ndarray
|
|
247
|
+
Hz: np.ndarray
|
|
248
|
+
freq: float
|
|
249
|
+
er: np.ndarray
|
|
250
|
+
ur: np.ndarray
|
|
251
|
+
aux: dict[str, np.ndarray] = field(default_factory=dict)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def k0(self) -> float:
|
|
256
|
+
return self.freq*2*np.pi/299792458
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def Px(self) -> np.ndarray:
|
|
260
|
+
return EPS0*(self.er-1)*self.Ex
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def Py(self) -> np.ndarray:
|
|
264
|
+
return EPS0*(self.er-1)*self.Ey
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def Pz(self) -> np.ndarray:
|
|
268
|
+
return EPS0*(self.er-1)*self.Ez
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def Dx(self) -> np.ndarray:
|
|
272
|
+
return self.Ex*self.er
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def Dy(self) -> np.ndarray:
|
|
276
|
+
return self.Ey*self.er
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def Dz(self) -> np.ndarray:
|
|
280
|
+
return self.Ez*self.er
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def Bx(self) -> np.ndarray:
|
|
284
|
+
return self.Hx/self.ur
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def By(self) -> np.ndarray:
|
|
288
|
+
return self.Hy/self.ur
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def Bz(self) -> np.ndarray:
|
|
292
|
+
return self.Hz/self.ur
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def Emat(self) -> np.ndarray:
|
|
296
|
+
return np.array([self.Ex, self.Ey, self.Ez])
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def Hmat(self) -> np.ndarray:
|
|
300
|
+
return np.array([self.Hx, self.Hy, self.Hz])
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def Pmat(self) -> np.ndarray:
|
|
304
|
+
return np.array([self.Px, self.Py, self.Pz])
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def Bmat(self) -> np.ndarray:
|
|
308
|
+
return np.array([self.Bx, self.By, self.Bz])
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def Dmat(self) -> np.ndarray:
|
|
312
|
+
return np.array([self.Dx, self.Dy, self.Dz])
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def Smat(self) -> np.ndarray:
|
|
316
|
+
return np.array([self.Sx, self.Sy, self.Sz])
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def Smmat(self) -> np.ndarray:
|
|
320
|
+
return np.array([self.Smx, self.Smy, self.Smz])
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def EH(self) -> tuple[np.ndarray, np.ndarray]:
|
|
324
|
+
''' Return the electric and magnetic field as a tuple of numpy arrays '''
|
|
325
|
+
return np.array([self.Ex, self.Ey, self.Ez]), np.array([self.Hx, self.Hy, self.Hz])
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def E(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
329
|
+
''' Return the electric field as a tuple of numpy arrays '''
|
|
330
|
+
return self.Ex, self.Ey, self.Ez
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def Sx(self) -> np.ndarray:
|
|
334
|
+
return self.Ey*self.Hz - self.Ez*self.Hy
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def Sy(self) -> np.ndarray:
|
|
338
|
+
return self.Ez*self.Hx - self.Ex*self.Hz
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def Sz(self) -> np.ndarray:
|
|
342
|
+
return self.Ex*self.Hy - self.Ey*self.Hx
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def Smx(self) -> np.ndarray:
|
|
346
|
+
return 0.5*(self.Ey*np.conj(self.Hz) - self.Ez*np.conj(self.Hy))
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def Smy(self) -> np.ndarray:
|
|
350
|
+
return 0.5*(self.Ez*np.conj(self.Hx) - self.Ex*np.conj(self.Hz))
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def Smz(self) -> np.ndarray:
|
|
354
|
+
return 0.5*(self.Ex*np.conj(self.Hy) - self.Ey*np.conj(self.Hx))
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def B(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
358
|
+
''' Return the magnetic field as a tuple of numpy arrays '''
|
|
359
|
+
return self.Bx, self.By, self.Bz
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def P(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
363
|
+
''' Return the polarization field as a tuple of numpy arrays '''
|
|
364
|
+
return self.Px, self.Py, self.Pz
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def D(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
368
|
+
''' Return the electric displacement field as a tuple of numpy arrays '''
|
|
369
|
+
return self.Bx, self.By, self.Bz
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def H(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
373
|
+
''' Return the magnetic field as a tuple of numpy arrays '''
|
|
374
|
+
return self.Hx, self.Hy, self.Hz
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def S(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
378
|
+
''' Return the poynting vector field as a tuple of numpy arrays '''
|
|
379
|
+
return self.Sx, self.Sy, self.Sz
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def Sm(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
383
|
+
''' Return the poynting vector field as a tuple of numpy arrays '''
|
|
384
|
+
return self.Smx, self.Smy, self.Smz
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def normE(self) -> np.ndarray:
|
|
388
|
+
"""The complex norm of the E-field
|
|
389
|
+
"""
|
|
390
|
+
return np.sqrt(np.abs(self.Ex)**2 + np.abs(self.Ey)**2 + np.abs(self.Ez)**2)
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def normH(self) -> np.ndarray:
|
|
394
|
+
"""The complex norm of the H-field"""
|
|
395
|
+
return np.sqrt(np.abs(self.Hx)**2 + np.abs(self.Hy)**2 + np.abs(self.Hz)**2)
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def normP(self) -> np.ndarray:
|
|
399
|
+
"""The complex norm of the P-field
|
|
400
|
+
"""
|
|
401
|
+
return np.sqrt(np.abs(self.Px)**2 + np.abs(self.Py)**2 + np.abs(self.Pz)**2)
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def normB(self) -> np.ndarray:
|
|
405
|
+
"""The complex norm of the B-field
|
|
406
|
+
"""
|
|
407
|
+
return np.sqrt(np.abs(self.Bx)**2 + np.abs(self.By)**2 + np.abs(self.Bz)**2)
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def normD(self) -> np.ndarray:
|
|
411
|
+
"""The complex norm of the D-field
|
|
412
|
+
"""
|
|
413
|
+
return np.sqrt(np.abs(self.Dx)**2 + np.abs(self.Dy)**2 + np.abs(self.Dz)**2)
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def normS(self) -> np.ndarray:
|
|
417
|
+
"""The complex norm of the S-field
|
|
418
|
+
"""
|
|
419
|
+
return np.sqrt(np.abs(self.Sx)**2 + np.abs(self.Sy)**2 + np.abs(self.Sz)**2)
|
|
420
|
+
|
|
421
|
+
def _get_quantity(self, field: str, metric: str) -> np.ndarray:
|
|
422
|
+
field_arry = getattr(self, field)
|
|
423
|
+
if metric=='abs':
|
|
424
|
+
field = np.abs(field_arry)
|
|
425
|
+
elif metric=='real':
|
|
426
|
+
field = field_arry.real
|
|
427
|
+
elif metric=='imag':
|
|
428
|
+
field = field_arry.imag
|
|
429
|
+
elif metric=='complex':
|
|
430
|
+
field = field_arry
|
|
431
|
+
else:
|
|
432
|
+
field = field_arry
|
|
433
|
+
return field
|
|
434
|
+
|
|
435
|
+
def vector(self, field: Literal['E','H'], metric: Literal['real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray,np.ndarray,np.ndarray,np.ndarray,np.ndarray]:
|
|
436
|
+
"""Returns the X,Y,Z,Fx,Fy,Fz data to be directly cast into plot functions.
|
|
437
|
+
|
|
438
|
+
The field can be selected by a string literal. The metric of the complex vector field by the metric.
|
|
439
|
+
For animations, make sure to always use the complex metric.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
field ('E','H'): The field to return
|
|
443
|
+
metric ([]'real','imag','complex'], optional): the metric to impose on the field. Defaults to 'real'.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
tuple[np.ndarray,...]: The X,Y,Z,Fx,Fy,Fz arrays
|
|
447
|
+
"""
|
|
448
|
+
Fx, Fy, Fz = getattr(self, field)
|
|
449
|
+
|
|
450
|
+
if metric=='real':
|
|
451
|
+
Fx, Fy, Fz = Fx.real, Fy.real, Fz.real
|
|
452
|
+
elif metric=='imag':
|
|
453
|
+
Fx, Fy, Fz = Fx.imag, Fy.imag, Fz.imag
|
|
454
|
+
|
|
455
|
+
return self.x, self.y, self.z, Fx, Fy, Fz
|
|
456
|
+
|
|
457
|
+
def scalar(self, field: Literal['Ex','Ey','Ez','Hx','Hy','Hz','normE','normH'] | str, metric: Literal['abs','real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
458
|
+
"""Returns the data X, Y, Z, Field based on the interpolation
|
|
459
|
+
|
|
460
|
+
For animations, make sure to select the complex metric.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
field (str): The field to plot
|
|
464
|
+
metric (str, optional): The metric to impose on the plot. Defaults to 'real'.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
(X,Y,Z,Field): The coordinates plus field scalar
|
|
468
|
+
"""
|
|
469
|
+
if field in self.aux:
|
|
470
|
+
field_arry = self.aux[field]
|
|
471
|
+
else:
|
|
472
|
+
field_arry = getattr(self, field)
|
|
473
|
+
|
|
474
|
+
if metric=='abs':
|
|
475
|
+
field = np.abs(field_arry)
|
|
476
|
+
elif metric=='real':
|
|
477
|
+
field = field_arry.real
|
|
478
|
+
elif metric=='imag':
|
|
479
|
+
field = field_arry.imag
|
|
480
|
+
elif metric=='complex':
|
|
481
|
+
field = field_arry
|
|
482
|
+
return self.x, self.y, self.z, field_arry
|
|
483
|
+
|
|
484
|
+
def int(self, field: str | Callable, metric: Literal['abs','real','imag','complex',''] = '') -> float | complex:
|
|
485
|
+
if isinstance(field, Callable):
|
|
486
|
+
field = field(self)
|
|
487
|
+
else:
|
|
488
|
+
field = self._get_quantity(field, metric)
|
|
489
|
+
if len(field.shape)==2:
|
|
490
|
+
axis = 1
|
|
491
|
+
else:
|
|
492
|
+
axis = 0
|
|
493
|
+
return np.sum(field*self.aux['areas']*self.aux['weights'], axis=axis)
|