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.

Files changed (54) hide show
  1. emerge/_emerge/bc.py +14 -20
  2. emerge/_emerge/const.py +5 -0
  3. emerge/_emerge/cs.py +2 -2
  4. emerge/_emerge/elements/femdata.py +14 -14
  5. emerge/_emerge/elements/index_interp.py +1 -1
  6. emerge/_emerge/elements/ned2_interp.py +1 -1
  7. emerge/_emerge/elements/nedelec2.py +4 -4
  8. emerge/_emerge/elements/nedleg2.py +10 -10
  9. emerge/_emerge/geo/horn.py +1 -1
  10. emerge/_emerge/geo/modeler.py +18 -19
  11. emerge/_emerge/geo/operations.py +13 -10
  12. emerge/_emerge/geo/pcb.py +180 -82
  13. emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
  14. emerge/_emerge/geo/pcb_tools/macro.py +14 -13
  15. emerge/_emerge/geo/pmlbox.py +1 -1
  16. emerge/_emerge/geometry.py +47 -33
  17. emerge/_emerge/logsettings.py +15 -16
  18. emerge/_emerge/material.py +15 -11
  19. emerge/_emerge/mesh3d.py +81 -59
  20. emerge/_emerge/mesher.py +26 -21
  21. emerge/_emerge/mth/integrals.py +1 -1
  22. emerge/_emerge/mth/pairing.py +2 -2
  23. emerge/_emerge/periodic.py +34 -31
  24. emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
  25. emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
  26. emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
  27. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
  28. emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
  29. emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
  30. emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
  31. emerge/_emerge/physics/microwave/microwave_data.py +36 -38
  32. emerge/_emerge/physics/microwave/sc.py +26 -26
  33. emerge/_emerge/physics/microwave/simjob.py +20 -15
  34. emerge/_emerge/physics/microwave/sparam.py +12 -12
  35. emerge/_emerge/physics/microwave/touchstone.py +1 -1
  36. emerge/_emerge/plot/display.py +12 -6
  37. emerge/_emerge/plot/pyvista/display.py +44 -39
  38. emerge/_emerge/plot/pyvista/display_settings.py +1 -1
  39. emerge/_emerge/plot/simple_plots.py +15 -15
  40. emerge/_emerge/selection.py +35 -39
  41. emerge/_emerge/simmodel.py +41 -47
  42. emerge/_emerge/simulation_data.py +24 -15
  43. emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
  44. emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
  45. emerge/_emerge/solver.py +314 -136
  46. emerge/cli.py +1 -1
  47. emerge/lib.py +245 -248
  48. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
  49. emerge-0.5.3.dist-info/RECORD +83 -0
  50. emerge/_emerge/plot/grapher.py +0 -93
  51. emerge-0.5.1.dist-info/RECORD +0 -82
  52. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
  53. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
  54. {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 get_type(self, bctype: Literal['PEC','ModalPort','LumpedPort','PMC','LumpedElement','RectangularWaveguide','Periodic','FloquetPort']) -> FaceSelection:
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
- return None
124
+ raise NotImplementedError('This method is not implemented')
108
125
 
109
- def get_inv_basis(self) -> np.ndarray:
110
- return None
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) -> float:
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 = 376.730313412
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 = None
134
- self.cs: CoordinateSystem = None
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 = None) -> complex:
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) * 4*np.pi*1e-7
185
+ return k0*299792458/self.get_beta(k0) * MU0
164
186
  elif self.modetype(k0)=='TM':
165
- return self.get_beta(k0)/(k0*299792458*8.854187818814*1e-12)
187
+ return self.get_beta(k0)/(k0*299792458*EPS0)
166
188
  else:
167
- return ValueError(f'Port mode type should be TEM, TE or TM but instead is {self.modetype(k0)}')
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)/376.73031341259)
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 = None
302
+ energy: float = 0
277
303
  norm_factor: float = 1
278
- freq: float = None
279
- neff: float = None
280
- TEM: bool = None
281
- Z0: float = None
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 = 0.0
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 376.73031341259
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*376.73031341259/(self.area))
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
- self.cs: CoordinateSystem = cs
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 self.cs is None:
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
- self._er: np.ndarray = None
437
- self._ur: np.ndarray = None
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: dict[float, list[PortMode]] = defaultdict(list)
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) = face.rect_basis()
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 portZ0(self, k0: float = None) -> complex:
620
- return k0*299792458 * 4*np.pi*1e-7/self.get_beta(k0)
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 = 376.73031341259
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*376.730313412/self.surfZ
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 * (376.730313412/self.surfZ)
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.vintline: Line = None
891
- self.v_integration = True
892
- self.iintline: Line = None
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 port
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
- def get_basis(self) -> np.ndarray:
905
- return self.cs._basis
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*376.730313412/self.surfZ(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