emerge 0.5.1__py3-none-any.whl → 0.5.3__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.
Potentially problematic release.
This version of emerge might be problematic. Click here for more details.
- emerge/_emerge/bc.py +14 -20
- emerge/_emerge/const.py +5 -0
- emerge/_emerge/cs.py +2 -2
- emerge/_emerge/elements/femdata.py +14 -14
- emerge/_emerge/elements/index_interp.py +1 -1
- emerge/_emerge/elements/ned2_interp.py +1 -1
- emerge/_emerge/elements/nedelec2.py +4 -4
- emerge/_emerge/elements/nedleg2.py +10 -10
- emerge/_emerge/geo/horn.py +1 -1
- emerge/_emerge/geo/modeler.py +18 -19
- emerge/_emerge/geo/operations.py +13 -10
- emerge/_emerge/geo/pcb.py +180 -82
- emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
- emerge/_emerge/geo/pcb_tools/macro.py +14 -13
- emerge/_emerge/geo/pmlbox.py +1 -1
- emerge/_emerge/geometry.py +47 -33
- emerge/_emerge/logsettings.py +15 -16
- emerge/_emerge/material.py +15 -11
- emerge/_emerge/mesh3d.py +81 -59
- emerge/_emerge/mesher.py +26 -21
- emerge/_emerge/mth/integrals.py +1 -1
- emerge/_emerge/mth/pairing.py +2 -2
- emerge/_emerge/periodic.py +34 -31
- emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
- emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
- emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
- emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
- emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
- emerge/_emerge/physics/microwave/microwave_data.py +36 -38
- emerge/_emerge/physics/microwave/sc.py +26 -26
- emerge/_emerge/physics/microwave/simjob.py +20 -15
- emerge/_emerge/physics/microwave/sparam.py +12 -12
- emerge/_emerge/physics/microwave/touchstone.py +1 -1
- emerge/_emerge/plot/display.py +12 -6
- emerge/_emerge/plot/pyvista/display.py +44 -39
- emerge/_emerge/plot/pyvista/display_settings.py +1 -1
- emerge/_emerge/plot/simple_plots.py +15 -15
- emerge/_emerge/selection.py +35 -39
- emerge/_emerge/simmodel.py +41 -47
- emerge/_emerge/simulation_data.py +24 -15
- emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
- emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
- emerge/_emerge/solver.py +314 -136
- emerge/cli.py +1 -1
- emerge/lib.py +245 -248
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
- emerge-0.5.3.dist-info/RECORD +83 -0
- emerge/_emerge/plot/grapher.py +0 -93
- emerge-0.5.1.dist-info/RECORD +0 -82
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -27,10 +27,12 @@ from dataclasses import dataclass
|
|
|
27
27
|
from collections import defaultdict
|
|
28
28
|
from ...bc import BoundaryCondition, BoundaryConditionSet, Periodic
|
|
29
29
|
from ...periodic import PeriodicCell, HexCell, RectCell
|
|
30
|
+
from ...material import Material
|
|
31
|
+
from ...const import Z0, C0, PI, EPS0, MU0
|
|
30
32
|
|
|
31
33
|
class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
32
34
|
|
|
33
|
-
def __init__(self, periodic_cell: PeriodicCell):
|
|
35
|
+
def __init__(self, periodic_cell: PeriodicCell | None):
|
|
34
36
|
super().__init__()
|
|
35
37
|
|
|
36
38
|
self.PEC: type[PEC] = self._construct_bc(PEC)
|
|
@@ -39,13 +41,28 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
|
39
41
|
self.ModalPort: type[ModalPort] = self._construct_bc(ModalPort)
|
|
40
42
|
self.LumpedPort: type[LumpedPort] = self._construct_bc(LumpedPort)
|
|
41
43
|
self.LumpedElement: type[LumpedElement] = self._construct_bc(LumpedElement)
|
|
44
|
+
self.SurfaceImpedance: type[SurfaceImpedance] = self._construct_bc(SurfaceImpedance)
|
|
42
45
|
self.RectangularWaveguide: type[RectangularWaveguide] = self._construct_bc(RectangularWaveguide)
|
|
43
46
|
self.Periodic: type[Periodic] = self._construct_bc(Periodic)
|
|
44
47
|
self.FloquetPort: type[FloquetPort] = self._construct_bc(FloquetPort)
|
|
45
48
|
|
|
46
|
-
self._cell: PeriodicCell = None
|
|
49
|
+
self._cell: PeriodicCell | None = None
|
|
47
50
|
|
|
48
|
-
def
|
|
51
|
+
def get_conductors(self) -> list[BoundaryCondition]:
|
|
52
|
+
"""Returns a list of all boundary conditions that ought to be considered as a "conductor"
|
|
53
|
+
for the purpose of modal analyses.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
list[BoundaryCondition]: All conductor like boundary conditions
|
|
57
|
+
"""
|
|
58
|
+
bcs = self.oftype(PEC)
|
|
59
|
+
for bc in self.oftype(SurfaceImpedance):
|
|
60
|
+
if bc.material.cond > 1e3:
|
|
61
|
+
bcs.append(bc)
|
|
62
|
+
|
|
63
|
+
return bcs
|
|
64
|
+
|
|
65
|
+
def get_type(self, bctype: Literal['PEC','ModalPort','LumpedPort','PMC','LumpedElement','RectangularWaveguide','Periodic','FloquetPort','SurfaceImpedance']) -> FaceSelection:
|
|
49
66
|
tags = []
|
|
50
67
|
for bc in self.boundary_conditions:
|
|
51
68
|
if bctype in str(bc.__class__):
|
|
@@ -101,25 +118,25 @@ class RobinBC(BoundaryCondition):
|
|
|
101
118
|
"""
|
|
102
119
|
super().__init__(selection)
|
|
103
120
|
self.v_integration: bool = False
|
|
104
|
-
self.vintline: Line = None
|
|
121
|
+
self.vintline: Line | None = None
|
|
105
122
|
|
|
106
123
|
def get_basis(self) -> np.ndarray:
|
|
107
|
-
|
|
124
|
+
raise NotImplementedError('This method is not implemented')
|
|
108
125
|
|
|
109
|
-
def get_inv_basis(self) -> np.ndarray:
|
|
110
|
-
|
|
126
|
+
def get_inv_basis(self) -> np.ndarray | None :
|
|
127
|
+
raise NotImplementedError('This method is not implemented')
|
|
111
128
|
|
|
112
|
-
def get_beta(self, k0) -> float:
|
|
129
|
+
def get_beta(self, k0: float) -> float:
|
|
113
130
|
raise NotImplementedError('get_beta not implemented for Port class')
|
|
114
131
|
|
|
115
|
-
def get_gamma(self, k0) ->
|
|
132
|
+
def get_gamma(self, k0: float) -> complex:
|
|
116
133
|
raise NotImplementedError('get_gamma not implemented for Port class')
|
|
117
134
|
|
|
118
|
-
def get_Uinc(self, k0) -> np.ndarray:
|
|
135
|
+
def get_Uinc(self, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
|
|
119
136
|
raise NotImplementedError('get_Uinc not implemented for Port class')
|
|
120
137
|
|
|
121
138
|
class PortBC(RobinBC):
|
|
122
|
-
Zvac: float =
|
|
139
|
+
Zvac: float = Z0
|
|
123
140
|
def __init__(self, face: FaceSelection | GeoSurface):
|
|
124
141
|
"""(DO NOT USE) A generalization of the Port boundary condition.
|
|
125
142
|
|
|
@@ -130,19 +147,24 @@ class PortBC(RobinBC):
|
|
|
130
147
|
face (FaceSelection | GeoSurface): The port face
|
|
131
148
|
"""
|
|
132
149
|
super().__init__(face)
|
|
133
|
-
self.port_number: int =
|
|
134
|
-
self.cs: CoordinateSystem =
|
|
150
|
+
self.port_number: int = -1
|
|
151
|
+
self.cs: CoordinateSystem = GCS
|
|
135
152
|
self.selected_mode: int = 0
|
|
136
|
-
self.Z0 = None
|
|
137
|
-
self.active: bool = False
|
|
153
|
+
self.Z0: complex | float | None = None
|
|
154
|
+
self.active: bool | None = False
|
|
155
|
+
self.power: float = 1.0
|
|
138
156
|
|
|
157
|
+
@property
|
|
158
|
+
def voltage(self) -> complex | None:
|
|
159
|
+
return None
|
|
160
|
+
|
|
139
161
|
def get_basis(self) -> np.ndarray:
|
|
140
162
|
return self.cs._basis
|
|
141
163
|
|
|
142
164
|
def get_inv_basis(self) -> np.ndarray:
|
|
143
165
|
return self.cs._basis_inv
|
|
144
166
|
|
|
145
|
-
def portZ0(self, k0: float
|
|
167
|
+
def portZ0(self, k0: float) -> complex | float | None:
|
|
146
168
|
"""Returns the port characteristic impedance given a phase constant
|
|
147
169
|
|
|
148
170
|
Args:
|
|
@@ -160,11 +182,11 @@ class PortBC(RobinBC):
|
|
|
160
182
|
if self.modetype(k0)=='TEM':
|
|
161
183
|
return self.Zvac
|
|
162
184
|
elif self.modetype(k0)=='TE':
|
|
163
|
-
return k0*299792458/self.get_beta(k0) *
|
|
185
|
+
return k0*299792458/self.get_beta(k0) * MU0
|
|
164
186
|
elif self.modetype(k0)=='TM':
|
|
165
|
-
return self.get_beta(k0)/(k0*299792458*
|
|
187
|
+
return self.get_beta(k0)/(k0*299792458*EPS0)
|
|
166
188
|
else:
|
|
167
|
-
|
|
189
|
+
raise ValueError(f'Port mode type should be TEM, TE or TM but instead is {self.modetype(k0)}')
|
|
168
190
|
|
|
169
191
|
def _qmode(self, k0: float) -> float:
|
|
170
192
|
"""Computes a mode amplitude correction factor.
|
|
@@ -178,7 +200,7 @@ class PortBC(RobinBC):
|
|
|
178
200
|
Returns:
|
|
179
201
|
float: The mode amplitude correction factor.
|
|
180
202
|
"""
|
|
181
|
-
return np.sqrt(self.Zmode(k0)/
|
|
203
|
+
return np.sqrt(self.Zmode(k0)/Z0)
|
|
182
204
|
|
|
183
205
|
@property
|
|
184
206
|
def mode_number(self) -> int:
|
|
@@ -188,7 +210,7 @@ class PortBC(RobinBC):
|
|
|
188
210
|
''' Return the out of plane propagation constant. βz.'''
|
|
189
211
|
return k0
|
|
190
212
|
|
|
191
|
-
def get_gamma(self, k0):
|
|
213
|
+
def get_gamma(self, k0) -> complex:
|
|
192
214
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
193
215
|
|
|
194
216
|
Args:
|
|
@@ -226,7 +248,7 @@ class AbsorbingBoundary(RobinBC):
|
|
|
226
248
|
def __init__(self,
|
|
227
249
|
face: FaceSelection | GeoSurface,
|
|
228
250
|
order: int = 1,
|
|
229
|
-
origin: tuple = None):
|
|
251
|
+
origin: tuple | None = None):
|
|
230
252
|
"""Creates an AbsorbingBoundary condition.
|
|
231
253
|
|
|
232
254
|
Currently only a first order boundary condition is possible. Second order will be supported later.
|
|
@@ -239,19 +261,23 @@ class AbsorbingBoundary(RobinBC):
|
|
|
239
261
|
origin (tuple, optional): The radiation origin. Defaults to None.
|
|
240
262
|
"""
|
|
241
263
|
super().__init__(face)
|
|
242
|
-
|
|
264
|
+
if origin is None:
|
|
265
|
+
origin = (0., 0., 0.)
|
|
243
266
|
self.order: int = order
|
|
244
267
|
self.origin: tuple = origin
|
|
245
268
|
self.cs: CoordinateSystem = GCS
|
|
246
269
|
|
|
247
270
|
def get_basis(self) -> np.ndarray:
|
|
248
271
|
return np.eye(3)
|
|
272
|
+
|
|
273
|
+
def get_inv_basis(self) -> np.ndarray | None:
|
|
274
|
+
return None
|
|
249
275
|
|
|
250
|
-
def get_beta(self, k0) -> float:
|
|
276
|
+
def get_beta(self, k0: float) -> float:
|
|
251
277
|
''' Return the out of plane propagation constant. βz.'''
|
|
252
278
|
return k0
|
|
253
279
|
|
|
254
|
-
def get_gamma(self, k0):
|
|
280
|
+
def get_gamma(self, k0: float) -> complex:
|
|
255
281
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
256
282
|
|
|
257
283
|
Args:
|
|
@@ -262,7 +288,7 @@ class AbsorbingBoundary(RobinBC):
|
|
|
262
288
|
"""
|
|
263
289
|
return 1j*self.get_beta(k0)
|
|
264
290
|
|
|
265
|
-
def get_Uinc(self, x_local, y_local, k0) -> np.ndarray:
|
|
291
|
+
def get_Uinc(self, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
|
|
266
292
|
return np.zeros((3, len(x_local)), dtype=np.complex128)
|
|
267
293
|
|
|
268
294
|
@dataclass
|
|
@@ -273,13 +299,13 @@ class PortMode:
|
|
|
273
299
|
k0: float
|
|
274
300
|
beta: float
|
|
275
301
|
residual: float
|
|
276
|
-
energy: float =
|
|
302
|
+
energy: float = 0
|
|
277
303
|
norm_factor: float = 1
|
|
278
|
-
freq: float =
|
|
279
|
-
neff: float =
|
|
280
|
-
TEM: bool =
|
|
281
|
-
Z0: float =
|
|
282
|
-
polarity: float = 1
|
|
304
|
+
freq: float = 0
|
|
305
|
+
neff: float = 1
|
|
306
|
+
TEM: bool = True
|
|
307
|
+
Z0: float = 50.0
|
|
308
|
+
polarity: float = 1.0
|
|
283
309
|
modetype: Literal['TEM','TE','TM'] = 'TEM'
|
|
284
310
|
|
|
285
311
|
def __post_init__(self):
|
|
@@ -291,7 +317,7 @@ class PortMode:
|
|
|
291
317
|
|
|
292
318
|
def set_power(self, power: complex) -> None:
|
|
293
319
|
self.norm_factor = np.sqrt(1/np.abs(power))
|
|
294
|
-
logger.info(f'Setting port mode amplitude to: {self.norm_factor} ')
|
|
320
|
+
logger.info(f'Setting port mode amplitude to: {self.norm_factor:.2f} ')
|
|
295
321
|
|
|
296
322
|
class FloquetPort(PortBC):
|
|
297
323
|
_include_stiff: bool = True
|
|
@@ -301,28 +327,31 @@ class FloquetPort(PortBC):
|
|
|
301
327
|
def __init__(self,
|
|
302
328
|
face: FaceSelection | GeoSurface,
|
|
303
329
|
port_number: int,
|
|
304
|
-
cs: CoordinateSystem = None,
|
|
330
|
+
cs: CoordinateSystem | None = None,
|
|
305
331
|
power: float = 1.0,
|
|
306
332
|
er: float = 1.0):
|
|
307
333
|
super().__init__(face)
|
|
334
|
+
if cs is None:
|
|
335
|
+
cs = GCS
|
|
308
336
|
self.port_number: int= port_number
|
|
309
337
|
self.active: bool = True
|
|
310
338
|
self.power: float = power
|
|
311
339
|
self.type: str = 'TEM'
|
|
312
|
-
self._field_amplitude: np.ndarray = None
|
|
313
340
|
self.mode: tuple[int,int] = (1,0)
|
|
314
341
|
self.cs: CoordinateSystem = cs
|
|
315
342
|
self.scan_theta: float = 0
|
|
316
343
|
self.scan_phi: float = 0
|
|
317
|
-
self.pol_s: complex = 1.0
|
|
318
|
-
self.pol_p: complex =
|
|
319
|
-
self.Zdir: Axis = -1
|
|
344
|
+
self.pol_s: complex = 1.0 + 0j
|
|
345
|
+
self.pol_p: complex = 0j
|
|
320
346
|
self.area: float = 1
|
|
347
|
+
self.width: float | None = None
|
|
348
|
+
self.height: float | None = None
|
|
349
|
+
|
|
321
350
|
if self.cs is None:
|
|
322
351
|
self.cs = GCS
|
|
323
352
|
|
|
324
|
-
def portZ0(self, k0: float = None) -> complex:
|
|
325
|
-
return
|
|
353
|
+
def portZ0(self, k0: float | None = None) -> complex | float | None:
|
|
354
|
+
return Z0
|
|
326
355
|
|
|
327
356
|
def get_amplitude(self, k0: float) -> float:
|
|
328
357
|
return 1.0
|
|
@@ -331,7 +360,7 @@ class FloquetPort(PortBC):
|
|
|
331
360
|
''' Return the out of plane propagation constant. βz.'''
|
|
332
361
|
return k0*np.cos(self.scan_theta)
|
|
333
362
|
|
|
334
|
-
def get_gamma(self, k0: float):
|
|
363
|
+
def get_gamma(self, k0: float) -> complex:
|
|
335
364
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
336
365
|
|
|
337
366
|
Args:
|
|
@@ -360,7 +389,7 @@ class FloquetPort(PortBC):
|
|
|
360
389
|
P = self.pol_p
|
|
361
390
|
S = self.pol_s
|
|
362
391
|
|
|
363
|
-
E0 = self.get_amplitude(k0)*np.sqrt(2*
|
|
392
|
+
E0 = self.get_amplitude(k0)*np.sqrt(2*Z0/(self.area))
|
|
364
393
|
Ex = E0*(-S*np.sin(self.scan_phi) - P*np.cos(self.scan_theta)*np.cos(self.scan_phi))*phi
|
|
365
394
|
Ey = E0*(S*np.cos(self.scan_phi) - P*np.cos(self.scan_theta)*np.sin(self.scan_phi))*phi
|
|
366
395
|
Ez = E0*(-P*E0*np.sin(self.scan_theta))*phi
|
|
@@ -374,6 +403,8 @@ class FloquetPort(PortBC):
|
|
|
374
403
|
k0: float,
|
|
375
404
|
which: Literal['E','H'] = 'E') -> np.ndarray:
|
|
376
405
|
'''Compute the port mode field for global xyz coordinates.'''
|
|
406
|
+
if self.cs is None:
|
|
407
|
+
raise ValueError('No coordinate system is defined for this FloquetPort')
|
|
377
408
|
xl, yl, _ = self.cs.in_local_cs(x_global, y_global, z_global)
|
|
378
409
|
Ex, Ey, Ez = self.port_mode_3d(xl, yl, k0)
|
|
379
410
|
Exg, Eyg, Ezg = self.cs.in_global_basis(Ex, Ey, Ez)
|
|
@@ -389,7 +420,7 @@ class ModalPort(PortBC):
|
|
|
389
420
|
face: FaceSelection | GeoSurface,
|
|
390
421
|
port_number: int,
|
|
391
422
|
active: bool = False,
|
|
392
|
-
cs: CoordinateSystem = None,
|
|
423
|
+
cs: CoordinateSystem | None = None,
|
|
393
424
|
power: float = 1,
|
|
394
425
|
TEM: bool = False,
|
|
395
426
|
mixed_materials: bool = False):
|
|
@@ -418,7 +449,7 @@ class ModalPort(PortBC):
|
|
|
418
449
|
self.port_number: int= port_number
|
|
419
450
|
self.active: bool = active
|
|
420
451
|
self.power: float = power
|
|
421
|
-
|
|
452
|
+
|
|
422
453
|
|
|
423
454
|
self.selected_mode: int = 0
|
|
424
455
|
self.modes: dict[float, list[PortMode]] = defaultdict(list)
|
|
@@ -426,17 +457,18 @@ class ModalPort(PortBC):
|
|
|
426
457
|
self.TEM: bool = TEM
|
|
427
458
|
self.mixed_materials: bool = mixed_materials
|
|
428
459
|
self.initialized: bool = False
|
|
429
|
-
self._first_k0: float = None
|
|
430
|
-
self._last_k0: float = None
|
|
460
|
+
self._first_k0: float | None = None
|
|
461
|
+
self._last_k0: float | None = None
|
|
431
462
|
|
|
432
|
-
if
|
|
463
|
+
if cs is None:
|
|
433
464
|
logger.info('Constructing coordinate system from normal port')
|
|
434
|
-
self.cs = Axis(self.selection.normal).construct_cs()
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
self.
|
|
465
|
+
self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
|
|
466
|
+
else:
|
|
467
|
+
raise ValueError('No Coordinate System could be derived.')
|
|
468
|
+
self._er: np.ndarray | None = None
|
|
469
|
+
self._ur: np.ndarray | None = None
|
|
438
470
|
|
|
439
|
-
def portZ0(self, k0: float) -> complex:
|
|
471
|
+
def portZ0(self, k0: float) -> complex | float | None:
|
|
440
472
|
return self.get_mode(k0).Z0
|
|
441
473
|
|
|
442
474
|
def modetype(self, k0: float) -> Literal['TEM','TE','TM']:
|
|
@@ -444,8 +476,15 @@ class ModalPort(PortBC):
|
|
|
444
476
|
|
|
445
477
|
@property
|
|
446
478
|
def nmodes(self) -> int:
|
|
479
|
+
if self._last_k0 is None:
|
|
480
|
+
raise ValueError('ModalPort is not properly configured. No modes are defined.')
|
|
447
481
|
return len(self.modes[self._last_k0])
|
|
448
482
|
|
|
483
|
+
@property
|
|
484
|
+
def voltage(self) -> complex:
|
|
485
|
+
mode = self.get_mode(0)
|
|
486
|
+
return np.sqrt(mode.Z0)
|
|
487
|
+
|
|
449
488
|
def sort_modes(self) -> None:
|
|
450
489
|
"""Sorts the port modes based on total energy
|
|
451
490
|
"""
|
|
@@ -476,7 +515,7 @@ class ModalPort(PortBC):
|
|
|
476
515
|
|
|
477
516
|
def clear_modes(self) -> None:
|
|
478
517
|
"""Clear all port mode data"""
|
|
479
|
-
self.modes
|
|
518
|
+
self.modes = defaultdict(list)
|
|
480
519
|
self.initialized = False
|
|
481
520
|
|
|
482
521
|
def add_mode(self,
|
|
@@ -487,7 +526,7 @@ class ModalPort(PortBC):
|
|
|
487
526
|
k0: float,
|
|
488
527
|
residual: float,
|
|
489
528
|
TEM: bool,
|
|
490
|
-
freq: float) -> PortMode:
|
|
529
|
+
freq: float) -> PortMode | None:
|
|
491
530
|
"""Add a mode function to the ModalPort
|
|
492
531
|
|
|
493
532
|
Args:
|
|
@@ -523,6 +562,9 @@ class ModalPort(PortBC):
|
|
|
523
562
|
|
|
524
563
|
def get_basis(self) -> np.ndarray:
|
|
525
564
|
return self.cs._basis
|
|
565
|
+
|
|
566
|
+
def get_inv_basis(self) -> np.ndarray:
|
|
567
|
+
return self.cs._basis_inv
|
|
526
568
|
|
|
527
569
|
def get_beta(self, k0: float) -> float:
|
|
528
570
|
mode = self.get_mode(k0)
|
|
@@ -533,7 +575,7 @@ class ModalPort(PortBC):
|
|
|
533
575
|
beta = np.sqrt(mode.beta**2 + k0**2 * (1-((mode.freq/freq)**2)))
|
|
534
576
|
return beta
|
|
535
577
|
|
|
536
|
-
def get_gamma(self, k0: float):
|
|
578
|
+
def get_gamma(self, k0: float) -> complex:
|
|
537
579
|
return 1j*self.get_beta(k0)
|
|
538
580
|
|
|
539
581
|
def get_Uinc(self, x_local, y_local, k0) -> np.ndarray:
|
|
@@ -573,8 +615,8 @@ class RectangularWaveguide(PortBC):
|
|
|
573
615
|
face: FaceSelection | GeoSurface,
|
|
574
616
|
port_number: int,
|
|
575
617
|
active: bool = False,
|
|
576
|
-
cs: CoordinateSystem = None,
|
|
577
|
-
dims: tuple[float, float] = None,
|
|
618
|
+
cs: CoordinateSystem | None = None,
|
|
619
|
+
dims: tuple[float, float] | None = None,
|
|
578
620
|
power: float = 1):
|
|
579
621
|
"""Creates a rectangular waveguide as a port boundary condition.
|
|
580
622
|
|
|
@@ -593,19 +635,17 @@ class RectangularWaveguide(PortBC):
|
|
|
593
635
|
power (float): The port power. Default to 1.
|
|
594
636
|
"""
|
|
595
637
|
super().__init__(face)
|
|
596
|
-
|
|
638
|
+
|
|
597
639
|
self.port_number: int= port_number
|
|
598
640
|
self.active: bool = active
|
|
599
641
|
self.power: float = power
|
|
600
642
|
self.type: str = 'TE'
|
|
601
|
-
self._field_amplitude: np.ndarray = None
|
|
602
643
|
self.mode: tuple[int,int] = (1,0)
|
|
603
|
-
self.cs: CoordinateSystem = cs
|
|
604
644
|
|
|
605
645
|
if dims is None:
|
|
606
646
|
logger.info("Determining port face based on selection")
|
|
607
|
-
cs, (width, height) =
|
|
608
|
-
self.cs = cs
|
|
647
|
+
cs, (width, height) = self.selection.rect_basis() # type: ignore
|
|
648
|
+
self.cs = cs # type: ignore
|
|
609
649
|
self.dims = (width, height)
|
|
610
650
|
logger.debug(f'Port CS: {self.cs}')
|
|
611
651
|
logger.debug(f'Detected port {self.port_number} size = {width*1000:.1f} mm x {height*1000:.1f} mm')
|
|
@@ -614,32 +654,25 @@ class RectangularWaveguide(PortBC):
|
|
|
614
654
|
logger.info('Constructing coordinate system from normal port')
|
|
615
655
|
self.cs = Axis(self.selection.normal).construct_cs()
|
|
616
656
|
else:
|
|
617
|
-
self.cs: CoordinateSystem = cs
|
|
657
|
+
self.cs: CoordinateSystem = cs # type: ignore
|
|
618
658
|
|
|
619
|
-
def
|
|
620
|
-
return
|
|
659
|
+
def get_basis(self) -> np.ndarray:
|
|
660
|
+
return self.cs._basis
|
|
661
|
+
|
|
662
|
+
def get_inv_basis(self) -> np.ndarray:
|
|
663
|
+
return self.cs._basis_inv
|
|
664
|
+
|
|
665
|
+
def portZ0(self, k0: float) -> complex:
|
|
666
|
+
return k0*299792458 * MU0/self.get_beta(k0)
|
|
621
667
|
|
|
622
668
|
def modetype(self, k0):
|
|
623
669
|
return self.type
|
|
624
670
|
|
|
625
671
|
def get_amplitude(self, k0: float) -> float:
|
|
626
|
-
Zte =
|
|
672
|
+
Zte = Z0
|
|
627
673
|
amplitude= np.sqrt(self.power*4*Zte/(self.dims[0]*self.dims[1]))
|
|
628
674
|
return amplitude
|
|
629
|
-
|
|
630
|
-
def port_mode_2d(self, xs: np.ndarray, ys: np.ndarray, k0: float) -> tuple[np.ndarray, float]:
|
|
631
|
-
x0 = xs[0]
|
|
632
|
-
y0 = ys[0]
|
|
633
|
-
x1 = xs[-1]
|
|
634
|
-
y1 = ys[-1]
|
|
635
|
-
xc = 0.5*(x0+x1)
|
|
636
|
-
yc = 0.5*(y0+y1)
|
|
637
|
-
a = np.sqrt((x1-x0)**2 + (y1-y0)**2)
|
|
638
|
-
|
|
639
|
-
logger.debug(f'Detected port {self.port_number} width = {a*1000:.1f} mm')
|
|
640
|
-
ds = np.sqrt((xs-xc)**2 + (ys-yc)**2)
|
|
641
|
-
return self.amplitude*np.cos(ds*np.pi/a), np.sqrt(k0**2 - (np.pi/a)**2)
|
|
642
|
-
|
|
675
|
+
|
|
643
676
|
def get_beta(self, k0: float) -> float:
|
|
644
677
|
''' Return the out of plane propagation constant. βz.'''
|
|
645
678
|
width=self.dims[0]
|
|
@@ -647,7 +680,7 @@ class RectangularWaveguide(PortBC):
|
|
|
647
680
|
beta = np.sqrt(k0**2 - (np.pi*self.mode[0]/width)**2 - (np.pi*self.mode[1]/height)**2)
|
|
648
681
|
return beta
|
|
649
682
|
|
|
650
|
-
def get_gamma(self, k0: float):
|
|
683
|
+
def get_gamma(self, k0: float) -> complex:
|
|
651
684
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
652
685
|
|
|
653
686
|
Args:
|
|
@@ -699,10 +732,10 @@ class LumpedPort(PortBC):
|
|
|
699
732
|
def __init__(self,
|
|
700
733
|
face: FaceSelection | GeoSurface,
|
|
701
734
|
port_number: int,
|
|
702
|
-
width: float = None,
|
|
703
|
-
height: float = None,
|
|
704
|
-
direction: Axis = None,
|
|
705
|
-
Idirection: Axis = None,
|
|
735
|
+
width: float | None = None,
|
|
736
|
+
height: float | None = None,
|
|
737
|
+
direction: Axis | None = None,
|
|
738
|
+
Idirection: Axis | None = None,
|
|
706
739
|
active: bool = False,
|
|
707
740
|
power: float = 1,
|
|
708
741
|
Z0: float = 50):
|
|
@@ -733,26 +766,25 @@ class LumpedPort(PortBC):
|
|
|
733
766
|
if width is None or height is None or direction is None:
|
|
734
767
|
raise ValueError(f'The width, height and direction could not be extracted from {face}')
|
|
735
768
|
|
|
736
|
-
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm, direction={direction}')
|
|
769
|
+
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm, direction={direction}') # type: ignore
|
|
737
770
|
self.port_number: int= port_number
|
|
738
771
|
self.active: bool = active
|
|
739
772
|
|
|
740
773
|
self.power: float = power
|
|
741
774
|
self.Z0: float = Z0
|
|
742
775
|
|
|
743
|
-
self._field_amplitude: np.ndarray = None
|
|
744
776
|
self.width: float = width
|
|
745
|
-
self.height: float = height
|
|
746
|
-
self.Vdirection: Axis = direction
|
|
747
|
-
self.Idirection: Axis = Idirection
|
|
777
|
+
self.height: float = height # type: ignore
|
|
778
|
+
self.Vdirection: Axis = direction # type: ignore
|
|
779
|
+
self.Idirection: Axis = Idirection # type: ignore
|
|
748
780
|
self.type = 'TEM'
|
|
749
781
|
|
|
750
782
|
logger.info('Constructing coordinate system from normal port')
|
|
751
|
-
self.cs = Axis(self.selection.normal).construct_cs()
|
|
783
|
+
self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
|
|
752
784
|
|
|
753
|
-
self.vintline: Line = None
|
|
785
|
+
self.vintline: Line | None = None
|
|
754
786
|
self.v_integration = True
|
|
755
|
-
self.iintline: Line = None
|
|
787
|
+
self.iintline: Line | None = None
|
|
756
788
|
|
|
757
789
|
@property
|
|
758
790
|
def surfZ(self) -> float:
|
|
@@ -774,6 +806,9 @@ class LumpedPort(PortBC):
|
|
|
774
806
|
|
|
775
807
|
def get_basis(self) -> np.ndarray:
|
|
776
808
|
return self.cs._basis
|
|
809
|
+
|
|
810
|
+
def get_inv_basis(self) -> np.ndarray:
|
|
811
|
+
return self.cs._basis_inv
|
|
777
812
|
|
|
778
813
|
def get_beta(self, k0: float) -> float:
|
|
779
814
|
''' Return the out of plane propagation constant. βz.'''
|
|
@@ -789,10 +824,10 @@ class LumpedPort(PortBC):
|
|
|
789
824
|
Returns:
|
|
790
825
|
complex: The γ-constant
|
|
791
826
|
"""
|
|
792
|
-
return 1j*k0*
|
|
827
|
+
return 1j*k0*Z0/self.surfZ
|
|
793
828
|
|
|
794
829
|
def get_Uinc(self, x_local, y_local, k0) -> np.ndarray:
|
|
795
|
-
Emag = -1j*2*k0 * self.voltage/self.height * (
|
|
830
|
+
Emag = -1j*2*k0 * self.voltage/self.height * (Z0/self.surfZ)
|
|
796
831
|
return Emag*self.port_mode_3d(x_local, y_local, k0)
|
|
797
832
|
|
|
798
833
|
def port_mode_3d(self,
|
|
@@ -845,9 +880,9 @@ class LumpedElement(RobinBC):
|
|
|
845
880
|
|
|
846
881
|
def __init__(self,
|
|
847
882
|
face: FaceSelection | GeoSurface,
|
|
848
|
-
impedance_function: Callable = None,
|
|
849
|
-
width: float = None,
|
|
850
|
-
height: float = None,
|
|
883
|
+
impedance_function: Callable | None = None,
|
|
884
|
+
width: float | None = None,
|
|
885
|
+
height: float | None = None,
|
|
851
886
|
):
|
|
852
887
|
"""Generates a lumped power boundary condition.
|
|
853
888
|
|
|
@@ -876,23 +911,14 @@ class LumpedElement(RobinBC):
|
|
|
876
911
|
if width is None or height is None or impedance_function is None:
|
|
877
912
|
raise ValueError(f'The width, height and impedance function could not be extracted from {face}')
|
|
878
913
|
|
|
879
|
-
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm')
|
|
880
|
-
|
|
881
|
-
self.Z0: Callable = impedance_function
|
|
882
|
-
|
|
883
|
-
self._field_amplitude: np.ndarray = None
|
|
884
|
-
self.width: float = width
|
|
885
|
-
self.height: float = height
|
|
886
|
-
|
|
887
|
-
logger.info('Constructing coordinate system from normal port')
|
|
888
|
-
self.cs = Axis(self.selection.normal).construct_cs()
|
|
914
|
+
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm') # type: ignore
|
|
889
915
|
|
|
890
|
-
self.
|
|
891
|
-
self.
|
|
892
|
-
self.
|
|
916
|
+
self.Z0: Callable = impedance_function # type: ignore
|
|
917
|
+
self.width: float = width # type: ignore
|
|
918
|
+
self.height: float = height # type: ignore
|
|
893
919
|
|
|
894
920
|
def surfZ(self, k0: float) -> float:
|
|
895
|
-
"""The surface sheet impedance for the lumped
|
|
921
|
+
"""The surface sheet impedance for the lumped Element
|
|
896
922
|
|
|
897
923
|
Returns:
|
|
898
924
|
float: The surface sheet impedance
|
|
@@ -900,15 +926,17 @@ class LumpedElement(RobinBC):
|
|
|
900
926
|
Z0 = self.Z0(k0*299792458/(2*np.pi))*self.width/self.height
|
|
901
927
|
return Z0
|
|
902
928
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
929
|
+
def get_basis(self) -> np.ndarray | None:
|
|
930
|
+
return None
|
|
931
|
+
|
|
932
|
+
def get_inv_basis(self) -> np.ndarray | None:
|
|
933
|
+
return None
|
|
906
934
|
|
|
907
935
|
def get_beta(self, k0: float) -> float:
|
|
908
936
|
''' Return the out of plane propagation constant. βz.'''
|
|
909
937
|
|
|
910
938
|
return k0
|
|
911
|
-
|
|
939
|
+
|
|
912
940
|
def get_gamma(self, k0: float) -> complex:
|
|
913
941
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
914
942
|
|
|
@@ -918,6 +946,67 @@ class LumpedElement(RobinBC):
|
|
|
918
946
|
Returns:
|
|
919
947
|
complex: The γ-constant
|
|
920
948
|
"""
|
|
921
|
-
return 1j*k0*
|
|
949
|
+
return 1j*k0*Z0/self.surfZ(k0)
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
class SurfaceImpedance(RobinBC):
|
|
954
|
+
|
|
955
|
+
_include_stiff: bool = True
|
|
956
|
+
_include_mass: bool = False
|
|
957
|
+
_include_force: bool = False
|
|
958
|
+
|
|
959
|
+
def __init__(self,
|
|
960
|
+
face: FaceSelection | GeoSurface,
|
|
961
|
+
material: Material | None = None,
|
|
962
|
+
):
|
|
963
|
+
"""Generates a lumped power boundary condition.
|
|
964
|
+
|
|
965
|
+
The lumped port boundary condition assumes a uniform E-field along the "direction" axis.
|
|
966
|
+
The port with and height must be provided manually in meters. The height is the size
|
|
967
|
+
in the "direction" axis along which the potential is imposed. The width dimension
|
|
968
|
+
is orthogonal to that. For a rectangular face its the width and for a cyllindrical face
|
|
969
|
+
its the circumpherance.
|
|
970
|
+
|
|
971
|
+
Args:
|
|
972
|
+
face (FaceSelection, GeoSurface): The port surface
|
|
973
|
+
port_number (int): The port number
|
|
974
|
+
width (float): The port width (meters).
|
|
975
|
+
height (float): The port height (meters).
|
|
976
|
+
direction (Axis): The port direction as an Axis object (em.Axis(..) or em.ZAX)
|
|
977
|
+
active (bool, optional): Whether the port is active. Defaults to False.
|
|
978
|
+
power (float, optional): The port output power. Defaults to 1.
|
|
979
|
+
Z0 (float, optional): The port impedance. Defaults to 50.
|
|
980
|
+
"""
|
|
981
|
+
super().__init__(face)
|
|
922
982
|
|
|
983
|
+
self.material: Material = material
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def get_basis(self) -> np.ndarray | None:
|
|
987
|
+
return None
|
|
923
988
|
|
|
989
|
+
def get_inv_basis(self) -> np.ndarray | None:
|
|
990
|
+
return None
|
|
991
|
+
|
|
992
|
+
def get_beta(self, k0: float) -> float:
|
|
993
|
+
''' Return the out of plane propagation constant. βz.'''
|
|
994
|
+
|
|
995
|
+
return k0
|
|
996
|
+
|
|
997
|
+
def get_gamma(self, k0: float) -> complex:
|
|
998
|
+
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
k0 (float): The free space propagation constant.
|
|
1002
|
+
|
|
1003
|
+
Returns:
|
|
1004
|
+
complex: The γ-constant
|
|
1005
|
+
"""
|
|
1006
|
+
w0 = k0*C0
|
|
1007
|
+
sigma = self.material.cond
|
|
1008
|
+
rho = 1/sigma
|
|
1009
|
+
d_skin = (2*rho/(w0*MU0) * ((1+(w0*EPS0*rho)**2)**0.5 + rho*w0*EPS0))**0.5
|
|
1010
|
+
d_skin = (2*rho/(w0*MU0))**0.5
|
|
1011
|
+
R = rho/d_skin
|
|
1012
|
+
return 1j*k0*Z0/R
|