emerge 0.5.1__py3-none-any.whl → 0.5.2__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 +11 -8
- 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 +9 -9
- 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 +70 -69
- emerge/_emerge/geo/pcb_tools/macro.py +14 -13
- emerge/_emerge/geo/pmlbox.py +1 -1
- emerge/_emerge/geometry.py +46 -32
- emerge/_emerge/logsettings.py +3 -3
- emerge/_emerge/material.py +11 -11
- emerge/_emerge/mesh3d.py +81 -59
- emerge/_emerge/mesher.py +26 -21
- emerge/_emerge/mth/pairing.py +2 -2
- emerge/_emerge/periodic.py +34 -31
- emerge/_emerge/physics/microwave/adaptive_freq.py +14 -11
- emerge/_emerge/physics/microwave/assembly/assembler.py +61 -57
- 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 +40 -20
- emerge/_emerge/physics/microwave/microwave_bc.py +114 -95
- emerge/_emerge/physics/microwave/microwave_data.py +33 -33
- emerge/_emerge/physics/microwave/simjob.py +12 -12
- 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 +29 -39
- emerge/_emerge/simulation_data.py +19 -14
- emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
- emerge/_emerge/solver.py +52 -52
- emerge/lib.py +243 -243
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/METADATA +1 -1
- emerge-0.5.2.dist-info/RECORD +81 -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.2.dist-info}/WHEEL +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -30,7 +30,7 @@ from ...periodic import PeriodicCell, HexCell, RectCell
|
|
|
30
30
|
|
|
31
31
|
class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
32
32
|
|
|
33
|
-
def __init__(self, periodic_cell: PeriodicCell):
|
|
33
|
+
def __init__(self, periodic_cell: PeriodicCell | None):
|
|
34
34
|
super().__init__()
|
|
35
35
|
|
|
36
36
|
self.PEC: type[PEC] = self._construct_bc(PEC)
|
|
@@ -43,7 +43,7 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
|
43
43
|
self.Periodic: type[Periodic] = self._construct_bc(Periodic)
|
|
44
44
|
self.FloquetPort: type[FloquetPort] = self._construct_bc(FloquetPort)
|
|
45
45
|
|
|
46
|
-
self._cell: PeriodicCell = None
|
|
46
|
+
self._cell: PeriodicCell | None = None
|
|
47
47
|
|
|
48
48
|
def get_type(self, bctype: Literal['PEC','ModalPort','LumpedPort','PMC','LumpedElement','RectangularWaveguide','Periodic','FloquetPort']) -> FaceSelection:
|
|
49
49
|
tags = []
|
|
@@ -101,21 +101,21 @@ class RobinBC(BoundaryCondition):
|
|
|
101
101
|
"""
|
|
102
102
|
super().__init__(selection)
|
|
103
103
|
self.v_integration: bool = False
|
|
104
|
-
self.vintline: Line = None
|
|
104
|
+
self.vintline: Line | None = None
|
|
105
105
|
|
|
106
106
|
def get_basis(self) -> np.ndarray:
|
|
107
|
-
|
|
107
|
+
raise NotImplementedError('This method is not implemented')
|
|
108
108
|
|
|
109
|
-
def get_inv_basis(self) -> np.ndarray:
|
|
110
|
-
|
|
109
|
+
def get_inv_basis(self) -> np.ndarray | None :
|
|
110
|
+
raise NotImplementedError('This method is not implemented')
|
|
111
111
|
|
|
112
|
-
def get_beta(self, k0) -> float:
|
|
112
|
+
def get_beta(self, k0: float) -> float:
|
|
113
113
|
raise NotImplementedError('get_beta not implemented for Port class')
|
|
114
114
|
|
|
115
|
-
def get_gamma(self, k0) ->
|
|
115
|
+
def get_gamma(self, k0: float) -> complex:
|
|
116
116
|
raise NotImplementedError('get_gamma not implemented for Port class')
|
|
117
117
|
|
|
118
|
-
def get_Uinc(self, k0) -> np.ndarray:
|
|
118
|
+
def get_Uinc(self, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
|
|
119
119
|
raise NotImplementedError('get_Uinc not implemented for Port class')
|
|
120
120
|
|
|
121
121
|
class PortBC(RobinBC):
|
|
@@ -130,19 +130,24 @@ class PortBC(RobinBC):
|
|
|
130
130
|
face (FaceSelection | GeoSurface): The port face
|
|
131
131
|
"""
|
|
132
132
|
super().__init__(face)
|
|
133
|
-
self.port_number: int =
|
|
134
|
-
self.cs: CoordinateSystem =
|
|
133
|
+
self.port_number: int = -1
|
|
134
|
+
self.cs: CoordinateSystem = GCS
|
|
135
135
|
self.selected_mode: int = 0
|
|
136
|
-
self.Z0 = None
|
|
137
|
-
self.active: bool = False
|
|
136
|
+
self.Z0: complex | float | None = None
|
|
137
|
+
self.active: bool | None = False
|
|
138
|
+
self.power: float = 1.0
|
|
138
139
|
|
|
140
|
+
@property
|
|
141
|
+
def voltage(self) -> complex | None:
|
|
142
|
+
return None
|
|
143
|
+
|
|
139
144
|
def get_basis(self) -> np.ndarray:
|
|
140
145
|
return self.cs._basis
|
|
141
146
|
|
|
142
147
|
def get_inv_basis(self) -> np.ndarray:
|
|
143
148
|
return self.cs._basis_inv
|
|
144
149
|
|
|
145
|
-
def portZ0(self, k0: float
|
|
150
|
+
def portZ0(self, k0: float) -> complex | float | None:
|
|
146
151
|
"""Returns the port characteristic impedance given a phase constant
|
|
147
152
|
|
|
148
153
|
Args:
|
|
@@ -164,7 +169,7 @@ class PortBC(RobinBC):
|
|
|
164
169
|
elif self.modetype(k0)=='TM':
|
|
165
170
|
return self.get_beta(k0)/(k0*299792458*8.854187818814*1e-12)
|
|
166
171
|
else:
|
|
167
|
-
|
|
172
|
+
raise ValueError(f'Port mode type should be TEM, TE or TM but instead is {self.modetype(k0)}')
|
|
168
173
|
|
|
169
174
|
def _qmode(self, k0: float) -> float:
|
|
170
175
|
"""Computes a mode amplitude correction factor.
|
|
@@ -188,7 +193,7 @@ class PortBC(RobinBC):
|
|
|
188
193
|
''' Return the out of plane propagation constant. βz.'''
|
|
189
194
|
return k0
|
|
190
195
|
|
|
191
|
-
def get_gamma(self, k0):
|
|
196
|
+
def get_gamma(self, k0) -> complex:
|
|
192
197
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
193
198
|
|
|
194
199
|
Args:
|
|
@@ -226,7 +231,7 @@ class AbsorbingBoundary(RobinBC):
|
|
|
226
231
|
def __init__(self,
|
|
227
232
|
face: FaceSelection | GeoSurface,
|
|
228
233
|
order: int = 1,
|
|
229
|
-
origin: tuple = None):
|
|
234
|
+
origin: tuple | None = None):
|
|
230
235
|
"""Creates an AbsorbingBoundary condition.
|
|
231
236
|
|
|
232
237
|
Currently only a first order boundary condition is possible. Second order will be supported later.
|
|
@@ -239,19 +244,23 @@ class AbsorbingBoundary(RobinBC):
|
|
|
239
244
|
origin (tuple, optional): The radiation origin. Defaults to None.
|
|
240
245
|
"""
|
|
241
246
|
super().__init__(face)
|
|
242
|
-
|
|
247
|
+
if origin is None:
|
|
248
|
+
origin = (0., 0., 0.)
|
|
243
249
|
self.order: int = order
|
|
244
250
|
self.origin: tuple = origin
|
|
245
251
|
self.cs: CoordinateSystem = GCS
|
|
246
252
|
|
|
247
253
|
def get_basis(self) -> np.ndarray:
|
|
248
254
|
return np.eye(3)
|
|
255
|
+
|
|
256
|
+
def get_inv_basis(self) -> np.ndarray | None:
|
|
257
|
+
return None
|
|
249
258
|
|
|
250
|
-
def get_beta(self, k0) -> float:
|
|
259
|
+
def get_beta(self, k0: float) -> float:
|
|
251
260
|
''' Return the out of plane propagation constant. βz.'''
|
|
252
261
|
return k0
|
|
253
262
|
|
|
254
|
-
def get_gamma(self, k0):
|
|
263
|
+
def get_gamma(self, k0: float) -> complex:
|
|
255
264
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
256
265
|
|
|
257
266
|
Args:
|
|
@@ -262,7 +271,7 @@ class AbsorbingBoundary(RobinBC):
|
|
|
262
271
|
"""
|
|
263
272
|
return 1j*self.get_beta(k0)
|
|
264
273
|
|
|
265
|
-
def get_Uinc(self, x_local, y_local, k0) -> np.ndarray:
|
|
274
|
+
def get_Uinc(self, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
|
|
266
275
|
return np.zeros((3, len(x_local)), dtype=np.complex128)
|
|
267
276
|
|
|
268
277
|
@dataclass
|
|
@@ -273,13 +282,13 @@ class PortMode:
|
|
|
273
282
|
k0: float
|
|
274
283
|
beta: float
|
|
275
284
|
residual: float
|
|
276
|
-
energy: float =
|
|
285
|
+
energy: float = 0
|
|
277
286
|
norm_factor: float = 1
|
|
278
|
-
freq: float =
|
|
279
|
-
neff: float =
|
|
280
|
-
TEM: bool =
|
|
281
|
-
Z0: float =
|
|
282
|
-
polarity: float = 1
|
|
287
|
+
freq: float = 0
|
|
288
|
+
neff: float = 1
|
|
289
|
+
TEM: bool = True
|
|
290
|
+
Z0: float = 50.0
|
|
291
|
+
polarity: float = 1.0
|
|
283
292
|
modetype: Literal['TEM','TE','TM'] = 'TEM'
|
|
284
293
|
|
|
285
294
|
def __post_init__(self):
|
|
@@ -301,27 +310,30 @@ class FloquetPort(PortBC):
|
|
|
301
310
|
def __init__(self,
|
|
302
311
|
face: FaceSelection | GeoSurface,
|
|
303
312
|
port_number: int,
|
|
304
|
-
cs: CoordinateSystem = None,
|
|
313
|
+
cs: CoordinateSystem | None = None,
|
|
305
314
|
power: float = 1.0,
|
|
306
315
|
er: float = 1.0):
|
|
307
316
|
super().__init__(face)
|
|
317
|
+
if cs is None:
|
|
318
|
+
cs = GCS
|
|
308
319
|
self.port_number: int= port_number
|
|
309
320
|
self.active: bool = True
|
|
310
321
|
self.power: float = power
|
|
311
322
|
self.type: str = 'TEM'
|
|
312
|
-
self._field_amplitude: np.ndarray = None
|
|
313
323
|
self.mode: tuple[int,int] = (1,0)
|
|
314
324
|
self.cs: CoordinateSystem = cs
|
|
315
325
|
self.scan_theta: float = 0
|
|
316
326
|
self.scan_phi: float = 0
|
|
317
|
-
self.pol_s: complex = 1.0
|
|
318
|
-
self.pol_p: complex =
|
|
319
|
-
self.Zdir: Axis = -1
|
|
327
|
+
self.pol_s: complex = 1.0 + 0j
|
|
328
|
+
self.pol_p: complex = 0j
|
|
320
329
|
self.area: float = 1
|
|
330
|
+
self.width: float | None = None
|
|
331
|
+
self.height: float | None = None
|
|
332
|
+
|
|
321
333
|
if self.cs is None:
|
|
322
334
|
self.cs = GCS
|
|
323
335
|
|
|
324
|
-
def portZ0(self, k0: float = None) -> complex:
|
|
336
|
+
def portZ0(self, k0: float | None = None) -> complex | float | None:
|
|
325
337
|
return 376.73031341259
|
|
326
338
|
|
|
327
339
|
def get_amplitude(self, k0: float) -> float:
|
|
@@ -331,7 +343,7 @@ class FloquetPort(PortBC):
|
|
|
331
343
|
''' Return the out of plane propagation constant. βz.'''
|
|
332
344
|
return k0*np.cos(self.scan_theta)
|
|
333
345
|
|
|
334
|
-
def get_gamma(self, k0: float):
|
|
346
|
+
def get_gamma(self, k0: float) -> complex:
|
|
335
347
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
336
348
|
|
|
337
349
|
Args:
|
|
@@ -374,6 +386,8 @@ class FloquetPort(PortBC):
|
|
|
374
386
|
k0: float,
|
|
375
387
|
which: Literal['E','H'] = 'E') -> np.ndarray:
|
|
376
388
|
'''Compute the port mode field for global xyz coordinates.'''
|
|
389
|
+
if self.cs is None:
|
|
390
|
+
raise ValueError('No coordinate system is defined for this FloquetPort')
|
|
377
391
|
xl, yl, _ = self.cs.in_local_cs(x_global, y_global, z_global)
|
|
378
392
|
Ex, Ey, Ez = self.port_mode_3d(xl, yl, k0)
|
|
379
393
|
Exg, Eyg, Ezg = self.cs.in_global_basis(Ex, Ey, Ez)
|
|
@@ -389,7 +403,7 @@ class ModalPort(PortBC):
|
|
|
389
403
|
face: FaceSelection | GeoSurface,
|
|
390
404
|
port_number: int,
|
|
391
405
|
active: bool = False,
|
|
392
|
-
cs: CoordinateSystem = None,
|
|
406
|
+
cs: CoordinateSystem | None = None,
|
|
393
407
|
power: float = 1,
|
|
394
408
|
TEM: bool = False,
|
|
395
409
|
mixed_materials: bool = False):
|
|
@@ -418,7 +432,7 @@ class ModalPort(PortBC):
|
|
|
418
432
|
self.port_number: int= port_number
|
|
419
433
|
self.active: bool = active
|
|
420
434
|
self.power: float = power
|
|
421
|
-
|
|
435
|
+
|
|
422
436
|
|
|
423
437
|
self.selected_mode: int = 0
|
|
424
438
|
self.modes: dict[float, list[PortMode]] = defaultdict(list)
|
|
@@ -426,17 +440,18 @@ class ModalPort(PortBC):
|
|
|
426
440
|
self.TEM: bool = TEM
|
|
427
441
|
self.mixed_materials: bool = mixed_materials
|
|
428
442
|
self.initialized: bool = False
|
|
429
|
-
self._first_k0: float = None
|
|
430
|
-
self._last_k0: float = None
|
|
443
|
+
self._first_k0: float | None = None
|
|
444
|
+
self._last_k0: float | None = None
|
|
431
445
|
|
|
432
|
-
if
|
|
446
|
+
if cs is None:
|
|
433
447
|
logger.info('Constructing coordinate system from normal port')
|
|
434
|
-
self.cs = Axis(self.selection.normal).construct_cs()
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
self.
|
|
448
|
+
self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
|
|
449
|
+
else:
|
|
450
|
+
raise ValueError('No Coordinate System could be derived.')
|
|
451
|
+
self._er: np.ndarray | None = None
|
|
452
|
+
self._ur: np.ndarray | None = None
|
|
438
453
|
|
|
439
|
-
def portZ0(self, k0: float) -> complex:
|
|
454
|
+
def portZ0(self, k0: float) -> complex | float | None:
|
|
440
455
|
return self.get_mode(k0).Z0
|
|
441
456
|
|
|
442
457
|
def modetype(self, k0: float) -> Literal['TEM','TE','TM']:
|
|
@@ -444,8 +459,15 @@ class ModalPort(PortBC):
|
|
|
444
459
|
|
|
445
460
|
@property
|
|
446
461
|
def nmodes(self) -> int:
|
|
462
|
+
if self._last_k0 is None:
|
|
463
|
+
raise ValueError('ModalPort is not properly configured. No modes are defined.')
|
|
447
464
|
return len(self.modes[self._last_k0])
|
|
448
465
|
|
|
466
|
+
@property
|
|
467
|
+
def voltage(self) -> complex:
|
|
468
|
+
mode = self.get_mode(0)
|
|
469
|
+
return np.sqrt(mode.Z0)
|
|
470
|
+
|
|
449
471
|
def sort_modes(self) -> None:
|
|
450
472
|
"""Sorts the port modes based on total energy
|
|
451
473
|
"""
|
|
@@ -476,7 +498,7 @@ class ModalPort(PortBC):
|
|
|
476
498
|
|
|
477
499
|
def clear_modes(self) -> None:
|
|
478
500
|
"""Clear all port mode data"""
|
|
479
|
-
self.modes
|
|
501
|
+
self.modes = defaultdict(list)
|
|
480
502
|
self.initialized = False
|
|
481
503
|
|
|
482
504
|
def add_mode(self,
|
|
@@ -487,7 +509,7 @@ class ModalPort(PortBC):
|
|
|
487
509
|
k0: float,
|
|
488
510
|
residual: float,
|
|
489
511
|
TEM: bool,
|
|
490
|
-
freq: float) -> PortMode:
|
|
512
|
+
freq: float) -> PortMode | None:
|
|
491
513
|
"""Add a mode function to the ModalPort
|
|
492
514
|
|
|
493
515
|
Args:
|
|
@@ -523,6 +545,9 @@ class ModalPort(PortBC):
|
|
|
523
545
|
|
|
524
546
|
def get_basis(self) -> np.ndarray:
|
|
525
547
|
return self.cs._basis
|
|
548
|
+
|
|
549
|
+
def get_inv_basis(self) -> np.ndarray:
|
|
550
|
+
return self.cs._basis_inv
|
|
526
551
|
|
|
527
552
|
def get_beta(self, k0: float) -> float:
|
|
528
553
|
mode = self.get_mode(k0)
|
|
@@ -533,7 +558,7 @@ class ModalPort(PortBC):
|
|
|
533
558
|
beta = np.sqrt(mode.beta**2 + k0**2 * (1-((mode.freq/freq)**2)))
|
|
534
559
|
return beta
|
|
535
560
|
|
|
536
|
-
def get_gamma(self, k0: float):
|
|
561
|
+
def get_gamma(self, k0: float) -> complex:
|
|
537
562
|
return 1j*self.get_beta(k0)
|
|
538
563
|
|
|
539
564
|
def get_Uinc(self, x_local, y_local, k0) -> np.ndarray:
|
|
@@ -573,8 +598,8 @@ class RectangularWaveguide(PortBC):
|
|
|
573
598
|
face: FaceSelection | GeoSurface,
|
|
574
599
|
port_number: int,
|
|
575
600
|
active: bool = False,
|
|
576
|
-
cs: CoordinateSystem = None,
|
|
577
|
-
dims: tuple[float, float] = None,
|
|
601
|
+
cs: CoordinateSystem | None = None,
|
|
602
|
+
dims: tuple[float, float] | None = None,
|
|
578
603
|
power: float = 1):
|
|
579
604
|
"""Creates a rectangular waveguide as a port boundary condition.
|
|
580
605
|
|
|
@@ -593,19 +618,17 @@ class RectangularWaveguide(PortBC):
|
|
|
593
618
|
power (float): The port power. Default to 1.
|
|
594
619
|
"""
|
|
595
620
|
super().__init__(face)
|
|
596
|
-
|
|
621
|
+
|
|
597
622
|
self.port_number: int= port_number
|
|
598
623
|
self.active: bool = active
|
|
599
624
|
self.power: float = power
|
|
600
625
|
self.type: str = 'TE'
|
|
601
|
-
self._field_amplitude: np.ndarray = None
|
|
602
626
|
self.mode: tuple[int,int] = (1,0)
|
|
603
|
-
self.cs: CoordinateSystem = cs
|
|
604
627
|
|
|
605
628
|
if dims is None:
|
|
606
629
|
logger.info("Determining port face based on selection")
|
|
607
|
-
cs, (width, height) =
|
|
608
|
-
self.cs = cs
|
|
630
|
+
cs, (width, height) = self.selection.rect_basis() # type: ignore
|
|
631
|
+
self.cs = cs # type: ignore
|
|
609
632
|
self.dims = (width, height)
|
|
610
633
|
logger.debug(f'Port CS: {self.cs}')
|
|
611
634
|
logger.debug(f'Detected port {self.port_number} size = {width*1000:.1f} mm x {height*1000:.1f} mm')
|
|
@@ -614,9 +637,15 @@ class RectangularWaveguide(PortBC):
|
|
|
614
637
|
logger.info('Constructing coordinate system from normal port')
|
|
615
638
|
self.cs = Axis(self.selection.normal).construct_cs()
|
|
616
639
|
else:
|
|
617
|
-
self.cs: CoordinateSystem = cs
|
|
640
|
+
self.cs: CoordinateSystem = cs # type: ignore
|
|
618
641
|
|
|
619
|
-
def
|
|
642
|
+
def get_basis(self) -> np.ndarray:
|
|
643
|
+
return self.cs._basis
|
|
644
|
+
|
|
645
|
+
def get_inv_basis(self) -> np.ndarray:
|
|
646
|
+
return self.cs._basis_inv
|
|
647
|
+
|
|
648
|
+
def portZ0(self, k0: float) -> complex:
|
|
620
649
|
return k0*299792458 * 4*np.pi*1e-7/self.get_beta(k0)
|
|
621
650
|
|
|
622
651
|
def modetype(self, k0):
|
|
@@ -626,20 +655,7 @@ class RectangularWaveguide(PortBC):
|
|
|
626
655
|
Zte = 376.73031341259
|
|
627
656
|
amplitude= np.sqrt(self.power*4*Zte/(self.dims[0]*self.dims[1]))
|
|
628
657
|
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
|
-
|
|
658
|
+
|
|
643
659
|
def get_beta(self, k0: float) -> float:
|
|
644
660
|
''' Return the out of plane propagation constant. βz.'''
|
|
645
661
|
width=self.dims[0]
|
|
@@ -647,7 +663,7 @@ class RectangularWaveguide(PortBC):
|
|
|
647
663
|
beta = np.sqrt(k0**2 - (np.pi*self.mode[0]/width)**2 - (np.pi*self.mode[1]/height)**2)
|
|
648
664
|
return beta
|
|
649
665
|
|
|
650
|
-
def get_gamma(self, k0: float):
|
|
666
|
+
def get_gamma(self, k0: float) -> complex:
|
|
651
667
|
"""Computes the γ-constant for matrix assembly. This constant is required for the Robin boundary condition.
|
|
652
668
|
|
|
653
669
|
Args:
|
|
@@ -699,10 +715,10 @@ class LumpedPort(PortBC):
|
|
|
699
715
|
def __init__(self,
|
|
700
716
|
face: FaceSelection | GeoSurface,
|
|
701
717
|
port_number: int,
|
|
702
|
-
width: float = None,
|
|
703
|
-
height: float = None,
|
|
704
|
-
direction: Axis = None,
|
|
705
|
-
Idirection: Axis = None,
|
|
718
|
+
width: float | None = None,
|
|
719
|
+
height: float | None = None,
|
|
720
|
+
direction: Axis | None = None,
|
|
721
|
+
Idirection: Axis | None = None,
|
|
706
722
|
active: bool = False,
|
|
707
723
|
power: float = 1,
|
|
708
724
|
Z0: float = 50):
|
|
@@ -733,26 +749,25 @@ class LumpedPort(PortBC):
|
|
|
733
749
|
if width is None or height is None or direction is None:
|
|
734
750
|
raise ValueError(f'The width, height and direction could not be extracted from {face}')
|
|
735
751
|
|
|
736
|
-
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm, direction={direction}')
|
|
752
|
+
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm, direction={direction}') # type: ignore
|
|
737
753
|
self.port_number: int= port_number
|
|
738
754
|
self.active: bool = active
|
|
739
755
|
|
|
740
756
|
self.power: float = power
|
|
741
757
|
self.Z0: float = Z0
|
|
742
758
|
|
|
743
|
-
self._field_amplitude: np.ndarray = None
|
|
744
759
|
self.width: float = width
|
|
745
|
-
self.height: float = height
|
|
746
|
-
self.Vdirection: Axis = direction
|
|
747
|
-
self.Idirection: Axis = Idirection
|
|
760
|
+
self.height: float = height # type: ignore
|
|
761
|
+
self.Vdirection: Axis = direction # type: ignore
|
|
762
|
+
self.Idirection: Axis = Idirection # type: ignore
|
|
748
763
|
self.type = 'TEM'
|
|
749
764
|
|
|
750
765
|
logger.info('Constructing coordinate system from normal port')
|
|
751
|
-
self.cs = Axis(self.selection.normal).construct_cs()
|
|
766
|
+
self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
|
|
752
767
|
|
|
753
|
-
self.vintline: Line = None
|
|
768
|
+
self.vintline: Line | None = None
|
|
754
769
|
self.v_integration = True
|
|
755
|
-
self.iintline: Line = None
|
|
770
|
+
self.iintline: Line | None = None
|
|
756
771
|
|
|
757
772
|
@property
|
|
758
773
|
def surfZ(self) -> float:
|
|
@@ -774,6 +789,9 @@ class LumpedPort(PortBC):
|
|
|
774
789
|
|
|
775
790
|
def get_basis(self) -> np.ndarray:
|
|
776
791
|
return self.cs._basis
|
|
792
|
+
|
|
793
|
+
def get_inv_basis(self) -> np.ndarray:
|
|
794
|
+
return self.cs._basis_inv
|
|
777
795
|
|
|
778
796
|
def get_beta(self, k0: float) -> float:
|
|
779
797
|
''' Return the out of plane propagation constant. βz.'''
|
|
@@ -845,9 +863,9 @@ class LumpedElement(RobinBC):
|
|
|
845
863
|
|
|
846
864
|
def __init__(self,
|
|
847
865
|
face: FaceSelection | GeoSurface,
|
|
848
|
-
impedance_function: Callable = None,
|
|
849
|
-
width: float = None,
|
|
850
|
-
height: float = None,
|
|
866
|
+
impedance_function: Callable | None = None,
|
|
867
|
+
width: float | None = None,
|
|
868
|
+
height: float | None = None,
|
|
851
869
|
):
|
|
852
870
|
"""Generates a lumped power boundary condition.
|
|
853
871
|
|
|
@@ -876,20 +894,18 @@ class LumpedElement(RobinBC):
|
|
|
876
894
|
if width is None or height is None or impedance_function is None:
|
|
877
895
|
raise ValueError(f'The width, height and impedance function could not be extracted from {face}')
|
|
878
896
|
|
|
879
|
-
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm')
|
|
897
|
+
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm') # type: ignore
|
|
880
898
|
|
|
881
|
-
self.Z0: Callable = impedance_function
|
|
882
|
-
|
|
883
|
-
self.
|
|
884
|
-
self.width: float = width
|
|
885
|
-
self.height: float = height
|
|
899
|
+
self.Z0: Callable = impedance_function # type: ignore
|
|
900
|
+
self.width: float = width # type: ignore
|
|
901
|
+
self.height: float = height # type: ignore
|
|
886
902
|
|
|
887
903
|
logger.info('Constructing coordinate system from normal port')
|
|
888
|
-
self.cs = Axis(self.selection.normal).construct_cs()
|
|
904
|
+
self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
|
|
889
905
|
|
|
890
|
-
self.vintline: Line = None
|
|
906
|
+
self.vintline: Line | None = None
|
|
891
907
|
self.v_integration = True
|
|
892
|
-
self.iintline: Line = None
|
|
908
|
+
self.iintline: Line | None = None
|
|
893
909
|
|
|
894
910
|
def surfZ(self, k0: float) -> float:
|
|
895
911
|
"""The surface sheet impedance for the lumped port
|
|
@@ -903,6 +919,9 @@ class LumpedElement(RobinBC):
|
|
|
903
919
|
|
|
904
920
|
def get_basis(self) -> np.ndarray:
|
|
905
921
|
return self.cs._basis
|
|
922
|
+
|
|
923
|
+
def get_inv_basis(self) -> np.ndarray:
|
|
924
|
+
return self.cs._basis_inv
|
|
906
925
|
|
|
907
926
|
def get_beta(self, k0: float) -> float:
|
|
908
927
|
''' Return the out of plane propagation constant. βz.'''
|
|
@@ -20,7 +20,7 @@ from ...simulation_data import BaseDataset, DataContainer
|
|
|
20
20
|
from ...elements.femdata import FEMBasis
|
|
21
21
|
from dataclasses import dataclass
|
|
22
22
|
import numpy as np
|
|
23
|
-
from typing import Literal
|
|
23
|
+
from typing import Literal
|
|
24
24
|
from loguru import logger
|
|
25
25
|
from .adaptive_freq import SparamModel
|
|
26
26
|
from ...cs import Axis, _parse_axis
|
|
@@ -128,7 +128,7 @@ def generate_ndim(
|
|
|
128
128
|
outer_data: dict[str, list[float]],
|
|
129
129
|
inner_data: list[float],
|
|
130
130
|
outer_labels: tuple[str, ...]
|
|
131
|
-
) -> np.ndarray:
|
|
131
|
+
) -> tuple[np.ndarray,...]:
|
|
132
132
|
"""
|
|
133
133
|
Generates an N-dimensional grid of values from flattened data, and returns each axis array plus the grid.
|
|
134
134
|
|
|
@@ -231,10 +231,10 @@ class Sparam:
|
|
|
231
231
|
|
|
232
232
|
@dataclass
|
|
233
233
|
class PortProperties:
|
|
234
|
-
port_number: int
|
|
234
|
+
port_number: int = -1
|
|
235
235
|
k0: float | None= None
|
|
236
236
|
beta: float | None = None
|
|
237
|
-
Z0: float | None = None
|
|
237
|
+
Z0: float | complex | None = None
|
|
238
238
|
Pout: float | None = None
|
|
239
239
|
mode_number: int = 1
|
|
240
240
|
|
|
@@ -259,7 +259,7 @@ class FarfieldData:
|
|
|
259
259
|
|
|
260
260
|
def surfplot(self,
|
|
261
261
|
polarization: Literal['Ex','Ey','Ez','Etheta','Ephi','normE'],
|
|
262
|
-
isotropic: bool = True, dB: bool = False, dBfloor: float = -30, rmax: float = None,
|
|
262
|
+
isotropic: bool = True, dB: bool = False, dBfloor: float = -30, rmax: float | None = None,
|
|
263
263
|
offset: tuple[float, float, float] = (0,0,0)) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
264
264
|
"""Returns the parameters to be used as positional arguments for the display.add_surf() function.
|
|
265
265
|
|
|
@@ -345,7 +345,7 @@ class EHField:
|
|
|
345
345
|
|
|
346
346
|
@property
|
|
347
347
|
def Dy(self) -> np.ndarray:
|
|
348
|
-
return self.
|
|
348
|
+
return self.Ey*self.er
|
|
349
349
|
|
|
350
350
|
@property
|
|
351
351
|
def Dz(self) -> np.ndarray:
|
|
@@ -499,16 +499,16 @@ class EHField:
|
|
|
499
499
|
Returns:
|
|
500
500
|
(X,Y,Z,Field): The coordinates plus field scalar
|
|
501
501
|
"""
|
|
502
|
-
|
|
502
|
+
field_arry = getattr(self, field)
|
|
503
503
|
if metric=='abs':
|
|
504
|
-
field = np.abs(
|
|
504
|
+
field = np.abs(field_arry)
|
|
505
505
|
elif metric=='real':
|
|
506
|
-
field =
|
|
506
|
+
field = field_arry.real
|
|
507
507
|
elif metric=='imag':
|
|
508
|
-
field =
|
|
508
|
+
field = field_arry.imag
|
|
509
509
|
elif metric=='complex':
|
|
510
|
-
field =
|
|
511
|
-
return self.x, self.y, self.z,
|
|
510
|
+
field = field_arry
|
|
511
|
+
return self.x, self.y, self.z, field_arry
|
|
512
512
|
|
|
513
513
|
class _EHSign:
|
|
514
514
|
"""A small class to manage the sign of field components when computing the far-field with Stratton-Chu
|
|
@@ -588,7 +588,7 @@ class MWField:
|
|
|
588
588
|
mode_number: int,
|
|
589
589
|
k0: float,
|
|
590
590
|
beta: float,
|
|
591
|
-
Z0: float,
|
|
591
|
+
Z0: float | complex | None,
|
|
592
592
|
Pout: float) -> None:
|
|
593
593
|
self.port_modes.append(PortProperties(port_number=port_number,
|
|
594
594
|
mode_number=mode_number,
|
|
@@ -609,7 +609,7 @@ class MWField:
|
|
|
609
609
|
def _field(self) -> np.ndarray:
|
|
610
610
|
if self._mode_field is not None:
|
|
611
611
|
return self._mode_field
|
|
612
|
-
return sum([self.excitation[mode.port_number]*self._fields[mode.port_number] for mode in self.port_modes])
|
|
612
|
+
return sum([self.excitation[mode.port_number]*self._fields[mode.port_number] for mode in self.port_modes]) # type: ignore
|
|
613
613
|
|
|
614
614
|
def set_field_vector(self) -> None:
|
|
615
615
|
"""Defines the default excitation coefficients for the current dataset"""
|
|
@@ -678,9 +678,9 @@ class MWField:
|
|
|
678
678
|
|
|
679
679
|
def cutplane(self,
|
|
680
680
|
ds: float,
|
|
681
|
-
x: float=None,
|
|
682
|
-
y: float=None,
|
|
683
|
-
z: float=None) -> EHField:
|
|
681
|
+
x: float | None = None,
|
|
682
|
+
y: float | None = None,
|
|
683
|
+
z: float | None = None) -> EHField:
|
|
684
684
|
xb, yb, zb = self.basis.bounds
|
|
685
685
|
xs = np.linspace(xb[0], xb[1], int((xb[1]-xb[0])/ds))
|
|
686
686
|
ys = np.linspace(yb[0], yb[1], int((yb[1]-yb[0])/ds))
|
|
@@ -765,8 +765,8 @@ class MWField:
|
|
|
765
765
|
faces: FaceSelection | GeoSurface,
|
|
766
766
|
ang_range: tuple[float, float] = (-180, 180),
|
|
767
767
|
Npoints: int = 201,
|
|
768
|
-
origin: tuple[float, float, float] = None,
|
|
769
|
-
syms: list[Literal['Ex','Ey','Ez', 'Hx','Hy','Hz']] = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
768
|
+
origin: tuple[float, float, float] | None = None,
|
|
769
|
+
syms: list[Literal['Ex','Ey','Ez', 'Hx','Hy','Hz']] | None = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
770
770
|
"""Compute the farfield electric and magnetic field defined by a circle.
|
|
771
771
|
|
|
772
772
|
Args:
|
|
@@ -782,18 +782,18 @@ class MWField:
|
|
|
782
782
|
tuple[np.ndarray, np.ndarray, np.ndarray]: _description_
|
|
783
783
|
"""
|
|
784
784
|
refdir = _parse_axis(ref_direction).np
|
|
785
|
-
|
|
786
|
-
theta, phi = arc_on_plane(refdir,
|
|
785
|
+
plane_normal_parsed = _parse_axis(plane_normal).np
|
|
786
|
+
theta, phi = arc_on_plane(refdir, plane_normal_parsed, ang_range, Npoints)
|
|
787
787
|
E,H = self.farfield(theta, phi, faces, origin, syms = syms)
|
|
788
788
|
angs = np.linspace(*ang_range, Npoints)*np.pi/180
|
|
789
789
|
return angs, E ,H
|
|
790
790
|
|
|
791
791
|
def farfield_3d(self,
|
|
792
792
|
faces: FaceSelection | GeoSurface,
|
|
793
|
-
thetas: np.ndarray = None,
|
|
794
|
-
phis: np.ndarray = None,
|
|
795
|
-
origin: tuple[float, float, float] = None,
|
|
796
|
-
syms: list[Literal['Ex','Ey','Ez', 'Hx','Hy','Hz']] = None) -> FarfieldData:
|
|
793
|
+
thetas: np.ndarray | None = None,
|
|
794
|
+
phis: np.ndarray | None = None,
|
|
795
|
+
origin: tuple[float, float, float] | None = None,
|
|
796
|
+
syms: list[Literal['Ex','Ey','Ez', 'Hx','Hy','Hz']] | None = None) -> FarfieldData:
|
|
797
797
|
"""Compute the farfield in a 3D angular grid
|
|
798
798
|
|
|
799
799
|
If thetas and phis are not provided, they default to a sample space of 2 degrees.
|
|
@@ -823,8 +823,8 @@ class MWField:
|
|
|
823
823
|
def farfield(self, theta: np.ndarray,
|
|
824
824
|
phi: np.ndarray,
|
|
825
825
|
faces: FaceSelection | GeoSurface,
|
|
826
|
-
origin: tuple[float, float, float] = None,
|
|
827
|
-
syms: list[Literal['Ex','Ey','Ez', 'Hx','Hy','Hz']] = None) -> tuple[np.ndarray, np.ndarray]:
|
|
826
|
+
origin: tuple[float, float, float] | None = None,
|
|
827
|
+
syms: list[Literal['Ex','Ey','Ez', 'Hx','Hy','Hz']] | None = None) -> tuple[np.ndarray, np.ndarray]:
|
|
828
828
|
"""Compute the farfield at the provided theta/phi coordinates
|
|
829
829
|
|
|
830
830
|
Args:
|
|
@@ -874,7 +874,7 @@ class MWField:
|
|
|
874
874
|
|
|
875
875
|
return Eff, Hff
|
|
876
876
|
|
|
877
|
-
def optycal(self, faces: FaceSelection | GeoSurface = None) -> tuple:
|
|
877
|
+
def optycal(self, faces: FaceSelection | GeoSurface | None = None) -> tuple:
|
|
878
878
|
"""Export this models exterior to an Optical acceptable dataset
|
|
879
879
|
|
|
880
880
|
Args:
|
|
@@ -953,7 +953,7 @@ class MWScalar:
|
|
|
953
953
|
mode_number: int,
|
|
954
954
|
k0: float,
|
|
955
955
|
beta: float,
|
|
956
|
-
Z0: float,
|
|
956
|
+
Z0: float | complex,
|
|
957
957
|
Pout: float) -> None:
|
|
958
958
|
i = self._portmap[port_number]
|
|
959
959
|
self.beta[i] = beta
|
|
@@ -1026,9 +1026,9 @@ class MWScalarNdim:
|
|
|
1026
1026
|
|
|
1027
1027
|
def export_touchstone(self,
|
|
1028
1028
|
filename: str,
|
|
1029
|
-
Z0ref: float = None,
|
|
1029
|
+
Z0ref: float | None = None,
|
|
1030
1030
|
format: Literal['RI','MA','DB'] = 'RI',
|
|
1031
|
-
custom_comments: list[str] = None,
|
|
1031
|
+
custom_comments: list[str] | None = None,
|
|
1032
1032
|
funit: Literal['HZ','KHZ','MHZ','GHZ'] = 'GHZ'):
|
|
1033
1033
|
"""Export the S-parameter data to a touchstone file
|
|
1034
1034
|
|
|
@@ -1063,9 +1063,9 @@ class MWScalarNdim:
|
|
|
1063
1063
|
filename: str,
|
|
1064
1064
|
Smatrix: np.ndarray,
|
|
1065
1065
|
frequencies: np.ndarray,
|
|
1066
|
-
Z0ref: float = None,
|
|
1066
|
+
Z0ref: float | None = None,
|
|
1067
1067
|
format: Literal['RI','MA','DB'] = 'RI',
|
|
1068
|
-
custom_comments: list[str] = None,
|
|
1068
|
+
custom_comments: list[str] | None = None,
|
|
1069
1069
|
funit: Literal['HZ','KHZ','MHZ','GHZ'] = 'GHZ') -> None:
|
|
1070
1070
|
"""Save an S-parameter matrix to a touchstone file.
|
|
1071
1071
|
|