emerge 1.0.0__py3-none-any.whl → 1.0.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.

Files changed (39) hide show
  1. emerge/__init__.py +7 -8
  2. emerge/_emerge/elements/femdata.py +4 -3
  3. emerge/_emerge/elements/nedelec2.py +8 -4
  4. emerge/_emerge/elements/nedleg2.py +6 -2
  5. emerge/_emerge/geo/__init__.py +1 -1
  6. emerge/_emerge/geo/pcb.py +149 -66
  7. emerge/_emerge/geo/pcb_tools/dxf.py +361 -0
  8. emerge/_emerge/geo/polybased.py +23 -74
  9. emerge/_emerge/geo/shapes.py +31 -16
  10. emerge/_emerge/geometry.py +120 -21
  11. emerge/_emerge/mesh3d.py +62 -43
  12. emerge/_emerge/{_cache_check.py → mth/_cache_check.py} +2 -2
  13. emerge/_emerge/mth/optimized.py +69 -3
  14. emerge/_emerge/periodic.py +19 -17
  15. emerge/_emerge/physics/microwave/__init__.py +0 -1
  16. emerge/_emerge/physics/microwave/assembly/assembler.py +27 -5
  17. emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +2 -3
  18. emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -1
  19. emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +375 -0
  20. emerge/_emerge/physics/microwave/assembly/robinbc.py +37 -38
  21. emerge/_emerge/physics/microwave/microwave_3d.py +11 -19
  22. emerge/_emerge/physics/microwave/microwave_bc.py +38 -21
  23. emerge/_emerge/physics/microwave/microwave_data.py +3 -26
  24. emerge/_emerge/physics/microwave/port_functions.py +4 -4
  25. emerge/_emerge/plot/pyvista/display.py +13 -2
  26. emerge/_emerge/plot/simple_plots.py +4 -1
  27. emerge/_emerge/selection.py +12 -9
  28. emerge/_emerge/simmodel.py +68 -34
  29. emerge/_emerge/solver.py +28 -16
  30. emerge/beta/dxf.py +1 -0
  31. emerge/lib.py +1 -0
  32. emerge/materials/__init__.py +1 -0
  33. emerge/materials/isola.py +294 -0
  34. emerge/materials/rogers.py +58 -0
  35. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/METADATA +18 -4
  36. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/RECORD +39 -33
  37. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/WHEEL +0 -0
  38. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/entry_points.txt +0 -0
  39. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/licenses/LICENSE +0 -0
@@ -312,34 +312,31 @@ def compute_bc_entries_excited(vertices_global, tris, Bmat, Bvec, surf_triangle_
312
312
 
313
313
 
314
314
  @njit(c16[:,:](f8[:,:], f8[:], c16), cache=True, nogil=True, parallel=False)
315
- def ned2_tri_stiff(vertices, edge_lengths, gamma):
315
+ def ned2_tri_stiff(glob_vertices, edge_lengths, gamma):
316
316
  ''' Nedelec-2 Triangle Stiffness matrix and forcing vector (For Boundary Condition of the Third Kind)
317
317
 
318
318
  '''
319
319
  local_edge_map = np.array([[0,1,0],[1,2,2]])
320
320
  Bmat = np.zeros((8,8), dtype=np.complex128)
321
321
 
322
- xs = vertices[0,:]
323
- ys = vertices[1,:]
324
- zs = vertices[2,:]
325
-
326
- ax1 = np.array([xs[1]-xs[0], ys[1]-ys[0], zs[1]-zs[0]])
327
- ax2 = np.array([xs[2]-xs[0], ys[2]-ys[0], zs[2]-zs[0]])
328
- ax1 = ax1/np.linalg.norm(ax1)
329
- ax2 = ax2/np.linalg.norm(ax2)
330
-
331
- axn = cross(ax1, ax2)
332
- ax2 = -cross(axn, ax1)
322
+ orig = glob_vertices[:,0]
323
+ v2 = glob_vertices[:,1]
324
+ v3 = glob_vertices[:,2]
325
+
326
+ e1 = v2-orig
327
+ e2 = v3-orig
328
+ zhat = normalize(cross(e1, e2))
329
+ xhat = normalize(e1)
330
+ yhat = normalize(cross(zhat, xhat))
333
331
  basis = np.zeros((3,3), dtype=np.float64)
334
- basis[:,0] = ax1
335
- basis[:,1] = ax2
336
- basis[:,2] = axn
337
- basis = np.linalg.pinv(basis)
338
- lcs_vertices = basis @ np.ascontiguousarray(vertices)
339
-
332
+ basis[0,:] = xhat
333
+ basis[1,:] = yhat
334
+ basis[2,:] = zhat
335
+ lcs_vertices = optim_matmul(basis, glob_vertices - orig[:,np.newaxis])
336
+
340
337
  xs = lcs_vertices[0,:]
341
338
  ys = lcs_vertices[1,:]
342
-
339
+
343
340
  x1, x2, x3 = xs
344
341
  y1, y2, y3 = ys
345
342
 
@@ -359,7 +356,7 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
359
356
  GLs = (GL1, GL2, GL3)
360
357
 
361
358
  Area = 0.5 * np.abs((x1 - x3) * (y2 - y1) - (x1 - x2) * (y3 - y1))
362
-
359
+
363
360
  letters = [1,2,3,4,5,6]
364
361
 
365
362
  tA, tB, tC = letters[0], letters[1], letters[2]
@@ -367,11 +364,12 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
367
364
 
368
365
  Lt1, Lt2 = Ds[2, 0], Ds[1, 0]
369
366
 
367
+
370
368
  COEFF = gamma/(2*Area)**2
371
369
  AREA_COEFF = AREA_COEFF_CACHE_BASE * Area
372
370
  for ei in range(3):
373
371
  ei1, ei2 = local_edge_map[:, ei]
374
- Li = edge_lengths[ei]
372
+ Li = Ds[ei1, ei2]
375
373
 
376
374
  A = letters[ei1]
377
375
  B = letters[ei2]
@@ -381,24 +379,25 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
381
379
 
382
380
  for ej in range(3):
383
381
  ej1, ej2 = local_edge_map[:, ej]
384
- Lj = edge_lengths[ej]
382
+ Lj = Ds[ej1, ej2]
385
383
 
386
384
  C = letters[ej1]
387
385
  D = letters[ej2]
388
386
 
389
387
  GC = GLs[ej1]
390
388
  GD = GLs[ej2]
389
+
391
390
  DAC = dot(GA,GC)
392
391
  DAD = dot(GA,GD)
393
392
  DBC = dot(GB,GC)
394
393
  DBD = dot(GB,GD)
395
394
  LL = Li*Lj
396
-
397
- Bmat[ei,ej] += LL*COEFF*(AREA_COEFF[A,B,C,D]*DAC-AREA_COEFF[A,B,C,C]*DAD-AREA_COEFF[A,A,C,D]*DBC+AREA_COEFF[A,A,C,C]*DBD)
398
- Bmat[ei,ej+4] += LL*COEFF*(AREA_COEFF[A,B,D,D]*DAC-AREA_COEFF[A,B,C,D]*DAD-AREA_COEFF[A,A,D,D]*DBC+AREA_COEFF[A,A,C,D]*DBD)
399
- Bmat[ei+4,ej] += LL*COEFF*(AREA_COEFF[B,B,C,D]*DAC-AREA_COEFF[B,B,C,C]*DAD-AREA_COEFF[A,B,C,D]*DBC+AREA_COEFF[A,B,C,C]*DBD)
400
- Bmat[ei+4,ej+4] += LL*COEFF*(AREA_COEFF[B,B,D,D]*DAC-AREA_COEFF[B,B,C,D]*DAD-AREA_COEFF[A,B,D,D]*DBC+AREA_COEFF[A,B,C,D]*DBD)
401
-
395
+
396
+ Bmat[ei,ej] += LL*(AREA_COEFF[A,B,C,D]*DAC-AREA_COEFF[A,B,C,C]*DAD-AREA_COEFF[A,A,C,D]*DBC+AREA_COEFF[A,A,C,C]*DBD)
397
+ Bmat[ei,ej+4] += LL*(AREA_COEFF[A,B,D,D]*DAC-AREA_COEFF[A,B,C,D]*DAD-AREA_COEFF[A,A,D,D]*DBC+AREA_COEFF[A,A,C,D]*DBD)
398
+ Bmat[ei+4,ej] += LL*(AREA_COEFF[B,B,C,D]*DAC-AREA_COEFF[B,B,C,C]*DAD-AREA_COEFF[A,B,C,D]*DBC+AREA_COEFF[A,B,C,C]*DBD)
399
+ Bmat[ei+4,ej+4] += LL*(AREA_COEFF[B,B,D,D]*DAC-AREA_COEFF[B,B,C,D]*DAD-AREA_COEFF[A,B,D,D]*DBC+AREA_COEFF[A,B,C,D]*DBD)
400
+
402
401
  FA = dot(GA,GtC)
403
402
  FB = dot(GA,GtA)
404
403
  FC = dot(GB,GtC)
@@ -406,15 +405,15 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
406
405
  FE = dot(GA,GtB)
407
406
  FF = dot(GB,GtB)
408
407
 
409
- Bmat[ei,3] += Li*Lt1*COEFF*(AREA_COEFF[A,B,tA,tB]*FA-AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,A,tA,tB]*FC+AREA_COEFF[A,A,tB,tC]*FD)
410
- Bmat[ei,7] += Li*Lt2*COEFF*(AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,B,tC,tA]*FE-AREA_COEFF[A,A,tB,tC]*FD+AREA_COEFF[A,A,tC,tA]*FF)
411
- Bmat[3,ei] += Lt1*Li*COEFF*(AREA_COEFF[tA,tB,A,B]*FA-AREA_COEFF[tA,tB,A,A]*FC-AREA_COEFF[tB,tC,A,B]*FB+AREA_COEFF[tB,tC,A,A]*FD)
412
- Bmat[7,ei] += Lt2*Li*COEFF*(AREA_COEFF[tB,tC,A,B]*FB-AREA_COEFF[tB,tC,A,A]*FD-AREA_COEFF[tC,tA,A,B]*FE+AREA_COEFF[tC,tA,A,A]*FF)
413
- Bmat[ei+4,3] += Li*Lt1*COEFF*(AREA_COEFF[B,B,tA,tB]*FA-AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[A,B,tA,tB]*FC+AREA_COEFF[A,B,tB,tC]*FD)
414
- Bmat[ei+4,7] += Li*Lt2*COEFF*(AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[B,B,tC,tA]*FE-AREA_COEFF[A,B,tB,tC]*FD+AREA_COEFF[A,B,tC,tA]*FF)
415
- Bmat[3,ei+4] += Lt1*Li*COEFF*(AREA_COEFF[tA,tB,B,B]*FA-AREA_COEFF[tA,tB,A,B]*FC-AREA_COEFF[tB,tC,B,B]*FB+AREA_COEFF[tB,tC,A,B]*FD)
416
- Bmat[7,ei+4] += Lt2*Li*COEFF*(AREA_COEFF[tB,tC,B,B]*FB-AREA_COEFF[tB,tC,A,B]*FD-AREA_COEFF[tC,tA,B,B]*FE+AREA_COEFF[tC,tA,A,B]*FF)
417
-
408
+ Bmat[ei,3] += Li*Lt1*(AREA_COEFF[A,B,tA,tB]*FA-AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,A,tA,tB]*FC+AREA_COEFF[A,A,tB,tC]*FD)
409
+ Bmat[ei,7] += Li*Lt2*(AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,B,tC,tA]*FE-AREA_COEFF[A,A,tB,tC]*FD+AREA_COEFF[A,A,tC,tA]*FF)
410
+ Bmat[3,ei] += Lt1*Li*(AREA_COEFF[tA,tB,A,B]*FA-AREA_COEFF[tA,tB,A,A]*FC-AREA_COEFF[tB,tC,A,B]*FB+AREA_COEFF[tB,tC,A,A]*FD)
411
+ Bmat[7,ei] += Lt2*Li*(AREA_COEFF[tB,tC,A,B]*FB-AREA_COEFF[tB,tC,A,A]*FD-AREA_COEFF[tC,tA,A,B]*FE+AREA_COEFF[tC,tA,A,A]*FF)
412
+ Bmat[ei+4,3] += Li*Lt1*(AREA_COEFF[B,B,tA,tB]*FA-AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[A,B,tA,tB]*FC+AREA_COEFF[A,B,tB,tC]*FD)
413
+ Bmat[ei+4,7] += Li*Lt2*(AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[B,B,tC,tA]*FE-AREA_COEFF[A,B,tB,tC]*FD+AREA_COEFF[A,B,tC,tA]*FF)
414
+ Bmat[3,ei+4] += Lt1*Li*(AREA_COEFF[tA,tB,B,B]*FA-AREA_COEFF[tA,tB,A,B]*FC-AREA_COEFF[tB,tC,B,B]*FB+AREA_COEFF[tB,tC,A,B]*FD)
415
+ Bmat[7,ei+4] += Lt2*Li*(AREA_COEFF[tB,tC,B,B]*FB-AREA_COEFF[tB,tC,A,B]*FD-AREA_COEFF[tC,tA,B,B]*FE+AREA_COEFF[tC,tA,A,B]*FF)
416
+
418
417
  H1 = dot(GtA,GtC)
419
418
  H2 = dot(GtA,GtA)
420
419
  H3 = dot(GtA,GtB)
@@ -423,8 +422,8 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
423
422
  Bmat[3,7] += Lt1*Lt2*(AREA_COEFF[tA,tB,tB,tC]*H1-AREA_COEFF[tA,tB,tC,tA]*dot(GtB,GtC)-AREA_COEFF[tB,tC,tB,tC]*H2+AREA_COEFF[tB,tC,tC,tA]*H3)
424
423
  Bmat[7,3] += Lt2*Lt1*(AREA_COEFF[tB,tC,tA,tB]*H1-AREA_COEFF[tB,tC,tB,tC]*H2-AREA_COEFF[tC,tA,tA,tB]*dot(GtB,GtC)+AREA_COEFF[tC,tA,tB,tC]*H3)
425
424
  Bmat[7,7] += Lt2*Lt2*(AREA_COEFF[tB,tC,tB,tC]*H2-AREA_COEFF[tB,tC,tC,tA]*H3-AREA_COEFF[tC,tA,tB,tC]*H3+AREA_COEFF[tC,tA,tC,tA]*dot(GtB,GtB))
426
-
427
425
 
426
+ Bmat = Bmat * COEFF
428
427
  return Bmat
429
428
 
430
429
  @njit(c16[:](f8[:,:], i8[:,:], c16[:], f8[:,:], i8[:], c16), cache=True, nogil=True, parallel=False)
@@ -25,7 +25,6 @@ from ...elements.nedelec2 import Nedelec2
25
25
  from ...solver import DEFAULT_ROUTINE, SolveRoutine
26
26
  from ...system import called_from_main_function
27
27
  from ...selection import FaceSelection
28
- from ...mth.optimized import compute_distances
29
28
  from ...settings import Settings
30
29
  from .microwave_bc import MWBoundaryConditionSet, PEC, ModalPort, LumpedPort, PortBC
31
30
  from .microwave_data import MWData
@@ -101,21 +100,6 @@ def shortest_path(xyz1: np.ndarray, xyz2: np.ndarray, Npts: int) -> np.ndarray:
101
100
  path = (1 - t) * p1[:, np.newaxis] + t * p2[:, np.newaxis]
102
101
 
103
102
  return path
104
-
105
- def _pick_central(vertices: np.ndarray) -> np.ndarray:
106
- """Computes the coordinate in the vertex set that has the shortest square distance to all other points.
107
-
108
-
109
- Args:
110
- vertices (np.ndarray): The set of coordinates [3,:]
111
-
112
- Returns:
113
- np.ndarray: The most central point
114
- """
115
- Ds = compute_distances(vertices[0,:], vertices[1,:], vertices[2,:])
116
- sumDs = np.sum(Ds**2, axis=1)
117
- id_central = np.argwhere(sumDs==np.min(sumDs)).flatten()[0]
118
- return vertices[:, id_central].squeeze()
119
103
 
120
104
  class Microwave3D:
121
105
  """The Electrodynamics time harmonic physics class.
@@ -248,6 +232,14 @@ class Microwave3D:
248
232
  self.set_frequency(np.linspace(fmin, fmax, Npoints))
249
233
 
250
234
  def fdense(self, Npoints: int) -> np.ndarray:
235
+ """Return a resampled version of the current frequency range
236
+
237
+ Args:
238
+ Npoints (int): The new number of points
239
+
240
+ Returns:
241
+ np.ndarray: The new frequency axis
242
+ """
251
243
  if len(self.frequencies) == 1:
252
244
  raise ValueError('Only 1 frequency point known. At least two need to be defined.')
253
245
  fmin = min(self.frequencies)
@@ -497,7 +489,7 @@ class Microwave3D:
497
489
  if freq is None:
498
490
  freq = self.frequencies[0]
499
491
 
500
- materials = self.mesh.retreive(self.mesher.volumes)
492
+ materials = self.mesh._get_material_assignment(self.mesher.volumes)
501
493
 
502
494
  ertet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
503
495
  tandtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
@@ -655,7 +647,7 @@ class Microwave3D:
655
647
  if self.basis is None:
656
648
  raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
657
649
 
658
- materials = self.mesh.retreive(self.mesher.volumes)
650
+ materials = self.mesh._get_material_assignment(self.mesher.volumes)
659
651
 
660
652
  ### Does this move
661
653
  logger.debug('Initializing frequency domain sweep.')
@@ -853,7 +845,7 @@ class Microwave3D:
853
845
  if self.basis is None:
854
846
  raise SimulationError('Cannot proceed. The simulation basis class is undefined.')
855
847
 
856
- materials = self.mesh.retreive(self.mesher.volumes)
848
+ materials = self.mesh._get_material_assignment(self.mesher.volumes)
857
849
 
858
850
  # er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
859
851
  # ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
@@ -19,19 +19,17 @@ from __future__ import annotations
19
19
  import numpy as np
20
20
  from loguru import logger
21
21
  from typing import Callable, Literal
22
+ from dataclasses import dataclass
23
+ from collections import defaultdict
22
24
  from ...selection import Selection, FaceSelection
23
25
  from ...cs import CoordinateSystem, Axis, GCS, _parse_axis
24
26
  from ...coord import Line
25
27
  from ...geometry import GeoSurface, GeoObject
26
- from dataclasses import dataclass
27
- from collections import defaultdict
28
28
  from ...bc import BoundaryCondition, BoundaryConditionSet, Periodic
29
29
  from ...periodic import PeriodicCell, HexCell, RectCell
30
30
  from ...material import Material
31
31
  from ...const import Z0, C0, EPS0, MU0
32
32
 
33
-
34
-
35
33
  ############################################################
36
34
  # UTILITY FUNCTIONS #
37
35
  ############################################################
@@ -125,6 +123,7 @@ class RobinBC(BoundaryCondition):
125
123
  _include_stiff: bool = False
126
124
  _include_mass: bool = False
127
125
  _include_force: bool = False
126
+ _isabc: bool = False
128
127
 
129
128
  def __init__(self, selection: GeoSurface | Selection):
130
129
  """A Generalization of any boundary condition of the third kind (Robin).
@@ -264,11 +263,13 @@ class AbsorbingBoundary(RobinBC):
264
263
  _include_stiff: bool = True
265
264
  _include_mass: bool = True
266
265
  _include_force: bool = False
267
-
266
+ _isabc: bool = True
267
+
268
268
  def __init__(self,
269
269
  face: FaceSelection | GeoSurface,
270
- order: int = 1,
271
- origin: tuple | None = None):
270
+ order: int = 2,
271
+ origin: tuple | None = None,
272
+ abctype: Literal['A','B','C','D','E'] = 'B'):
272
273
  """Creates an AbsorbingBoundary condition.
273
274
 
274
275
  Currently only a first order boundary condition is possible. Second order will be supported later.
@@ -286,7 +287,15 @@ class AbsorbingBoundary(RobinBC):
286
287
  self.order: int = order
287
288
  self.origin: tuple = origin
288
289
  self.cs: CoordinateSystem = GCS
289
-
290
+
291
+ self.abctype: Literal['A','B','C','D','E'] = abctype
292
+ self.o2coeffs: tuple[float, float] = {'A': (1.0, -0.5),
293
+ 'B': (1.00023, -0.51555),
294
+ 'C': (1.03084, -0.73631),
295
+ 'D': (1.06103, -0.84883),
296
+ 'E': (1.12500, -1.00000)
297
+ }
298
+
290
299
  def get_basis(self) -> np.ndarray:
291
300
  return np.eye(3)
292
301
 
@@ -306,15 +315,10 @@ class AbsorbingBoundary(RobinBC):
306
315
  Returns:
307
316
  complex: The γ-constant
308
317
  """
309
- if self.order == 2:
310
-
311
- p0 = 1.06103
312
- p2 = -0.84883
313
- ky = k0*0.5
314
- return 1j*k0*p0 - 1j*p2*ky**2/k0
315
- else:
316
- Factor = 1
317
- return 1j*self.get_beta(k0)*Factor
318
+ if self.order==1:
319
+ return 1j*k0
320
+
321
+ return 1j*k0*self.o2coeffs[self.abctype][0]
318
322
 
319
323
 
320
324
  @dataclass
@@ -1002,6 +1006,7 @@ class SurfaceImpedance(RobinBC):
1002
1006
  material: Material | None = None,
1003
1007
  surface_conductance: float | None = None,
1004
1008
  surface_roughness: float = 0,
1009
+ thickness: float | None = None,
1005
1010
  sr_model: Literal['Hammerstad-Jensen'] = 'Hammerstad-Jensen',
1006
1011
  ):
1007
1012
  """Generates a SurfaceImpedance bounary condition.
@@ -1021,7 +1026,8 @@ class SurfaceImpedance(RobinBC):
1021
1026
  material (Material | None, optional): The matrial to assign. Defaults to None.
1022
1027
  surface_conductance (float | None, optional): The specific bulk conductivity to use. Defaults to None.
1023
1028
  surface_roughness (float, optional): The surface roughness. Defaults to 0.
1024
- sr_model (Literal['Hammerstad, optional): The surface roughness model. Defaults to 'Hammerstad-Jensen'.
1029
+ thickness (float | None, optional): The layer thickness. Defaults to None
1030
+ sr_model (Literal["Hammerstad-Jensen", optional): The surface roughness model. Defaults to 'Hammerstad-Jensen'.
1025
1031
  """
1026
1032
  super().__init__(face)
1027
1033
 
@@ -1029,7 +1035,11 @@ class SurfaceImpedance(RobinBC):
1029
1035
  self._mur: float | complex = 1.0
1030
1036
  self._epsr: float | complex = 1.0
1031
1037
  self.sigma: float = 0.0
1038
+ self.thickness: float | None = thickness
1032
1039
 
1040
+ if isinstance(face, GeoObject) and thickness is None:
1041
+ self.thickness = face._load('thickness')
1042
+
1033
1043
  if material is not None:
1034
1044
  self.sigma = material.cond.scalar(1e9)
1035
1045
  self._mur = material.ur
@@ -1067,10 +1077,17 @@ class SurfaceImpedance(RobinBC):
1067
1077
  sigma = self.sigma
1068
1078
  mur = self._material.ur.scalar(f0)
1069
1079
  er = self._material.er.scalar(f0)
1070
-
1080
+ eps = EPS0*er
1081
+ mu = MU0*mur
1071
1082
  rho = 1/sigma
1072
- d_skin = (2*rho/(w0*MU0*mur) * ((1+(w0*EPS0*er*rho)**2)**0.5 + rho*w0*EPS0*er))**0.5
1073
- R = rho/d_skin
1083
+ d_skin = (2*rho/(w0*mu) * ((1+(w0*eps*rho)**2)**0.5 + rho*w0*eps))**0.5
1084
+ logger.debug(f'Computed skin depth δ={d_skin*1e6:.2}μm')
1085
+ R = (1+1j)*rho/d_skin
1086
+ if self.thickness is not None:
1087
+ eps_c = eps - 1j * sigma / w0
1088
+ gamma_m = 1j * w0 * np.sqrt(mu*eps_c)
1089
+ R = R / np.tanh(gamma_m * self.thickness)
1090
+ logger.debug(f'Impedance scaler due to thickness: {1/ np.tanh(gamma_m * self.thickness) :.4f}')
1074
1091
  if self._sr_model=='Hammerstad-Jensen' and self._sr > 0.0:
1075
1092
  R = R * (1 + 2/np.pi * np.arctan(1.4*(self._sr/d_skin)**2))
1076
1093
  return 1j*k0*Z0/R
@@ -1036,32 +1036,6 @@ class MWField:
1036
1036
  return E[0,:], E[1,:], E[2,:], H[0,:], H[1,:], H[2,:]
1037
1037
 
1038
1038
  return dict(freq=freq, ff_function=function)
1039
-
1040
- # def surface_integral(self, faces: FaceSelection | GeoSurface, fieldfunction: Callable) -> float | complex:
1041
- # """Computes a surface integral on the selected faces.
1042
-
1043
- # The fieldfunction argument must be a callable of a single argument x, which will
1044
- # be of type EHField which is restuned by the field.interpolate(x,y,z) function. It has
1045
- # fields like Ez, Ey, Sx etc that can be called.
1046
-
1047
- # Args:
1048
- # faces (FaceSelection | GeoSurface): _description_
1049
- # fieldfunction (Callable): _description_
1050
-
1051
- # Returns:
1052
- # float | complex: _description_
1053
- # """
1054
- # from ...mth.integrals import surface_integral
1055
-
1056
- # def ff(x, y, z):
1057
- # fieldobj = self.interpolate(x,y,z)
1058
- # return fieldfunction(fieldobj)
1059
-
1060
- # nodes = self.mesh.get_nodes(faces.tags)
1061
- # triangles = self.mesh.get_triangles(faces.tags)
1062
-
1063
- # return surface_integral(nodes, triangles, ff)
1064
-
1065
1039
 
1066
1040
  class MWScalar:
1067
1041
  """The MWDataSet class stores solution data of FEM Time Harmonic simulations.
@@ -1137,6 +1111,9 @@ class MWScalarNdim:
1137
1111
  self._portmap: dict[int, float|int] = dict()
1138
1112
  self._portnumbers: list[int | float] = []
1139
1113
 
1114
+ def dense_f(self, N: int) -> np.ndarray:
1115
+ return np.linspace(np.min(self.freq), np.max(self.freq), N)
1116
+
1140
1117
  def S(self, i1: int, i2: int) -> np.ndarray:
1141
1118
  return self.Sp[...,self._portmap[i1], self._portmap[i2]]
1142
1119
 
@@ -17,11 +17,10 @@
17
17
 
18
18
  import numpy as np
19
19
  from ...elements.nedleg2 import NedelecLegrange2
20
- from ...mth.integrals import surface_integral
21
-
22
20
 
23
21
  def compute_avg_power_flux(field: NedelecLegrange2, mode: np.ndarray, k0: float, ur: np.ndarray, beta: float):
24
-
22
+ from ...mth.integrals import surface_integral
23
+
25
24
  Efunc = field.interpolate_Ef(mode)
26
25
  Hfunc = field.interpolate_Hf(mode, k0, ur, beta)
27
26
  nx, ny, nz = field.mesh.normals[:,0]
@@ -37,7 +36,8 @@ def compute_avg_power_flux(field: NedelecLegrange2, mode: np.ndarray, k0: float,
37
36
  return Ptot
38
37
 
39
38
  def compute_peak_power_flux(field: NedelecLegrange2, mode: np.ndarray, k0: float, ur: np.ndarray, beta: float):
40
-
39
+ from ...mth.integrals import surface_integral
40
+
41
41
  Efunc = field.interpolate_Ef(mode)
42
42
  Hfunc = field.interpolate_Hf(mode, k0, ur, beta)
43
43
  nx, ny, nz = field.mesh.normals[:,0]
@@ -435,7 +435,7 @@ class PVDisplay(BaseDisplay):
435
435
  return None
436
436
 
437
437
  ## OBLIGATORY METHODS
438
- def add_object(self, obj: GeoObject | Selection, mesh: bool = False, volume_mesh: bool = True, *args, **kwargs):
438
+ def add_object(self, obj: GeoObject | Selection, mesh: bool = False, volume_mesh: bool = True, label: bool = False, *args, **kwargs):
439
439
 
440
440
  show_edges = False
441
441
  opacity = obj.opacity
@@ -471,7 +471,10 @@ class PVDisplay(BaseDisplay):
471
471
  line_width=line_width,
472
472
  show_edges=show_edges,
473
473
  pickable=True,
474
+ smooth_shading=False,
475
+ split_sharp_edges=True,
474
476
  style=style)
477
+
475
478
  mesh_obj = self.mesh(obj)
476
479
 
477
480
  if mesh is True and volume_mesh is True:
@@ -485,7 +488,15 @@ class PVDisplay(BaseDisplay):
485
488
  mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(1,0.5)
486
489
 
487
490
  self._plot.add_mesh(self._volume_edges(_select(obj)), color='#000000', line_width=2, show_edges=True)
488
-
491
+
492
+ if isinstance(obj, GeoObject) and label:
493
+ points = []
494
+ labels = []
495
+ for dt in obj.dimtags:
496
+ points.append(self._mesh.dimtag_to_center[dt])
497
+ labels.append(obj.name)
498
+ self._plot.add_point_labels(points, labels, shape_color='white')
499
+
489
500
  def add_objects(self, *objects, **kwargs) -> None:
490
501
  """Add a series of objects provided as a list of arguments
491
502
  """
@@ -317,6 +317,7 @@ and sparse frequency annotations (e.g., labeled by frequency).
317
317
  colors_list = _broadcast(colors, None, 'colors')
318
318
  lw_list = _broadcast(linewidth, None, 'linewidth')
319
319
  labels_list: Optional[List[Optional[str]]]
320
+
320
321
  if labels is None:
321
322
  labels_list = None
322
323
  else:
@@ -377,7 +378,9 @@ and sparse frequency annotations (e.g., labeled by frequency).
377
378
 
378
379
  # frequency labels (sparse)
379
380
  fi = fs_list[i]
380
- if fi[0] is not None and n_flabels > 0 and len(s) > 0 and len(fi) > 0:
381
+ if fi is None:
382
+ continue
383
+ if n_flabels > 0 and len(s) > 0 and len(fi) > 0:
381
384
  n = min(len(s), len(fi))
382
385
  step = max(1, int(round(n / n_flabels))) if n_flabels > 0 else n # avoid step=0
383
386
  idx = np.arange(0, n, step)
@@ -18,7 +18,6 @@
18
18
  from __future__ import annotations
19
19
  import gmsh # type: ignore
20
20
  import numpy as np
21
- from scipy.spatial import ConvexHull # type: ignore
22
21
  from .cs import Axis, CoordinateSystem, _parse_vector, Plane
23
22
  from typing import Callable, TypeVar, Iterable, Any
24
23
 
@@ -34,6 +33,8 @@ def align_rectangle_frame(pts3d: np.ndarray, normal: np.ndarray) -> dict[str, An
34
33
  """
35
34
 
36
35
  # 1. centroid
36
+ from scipy.spatial import ConvexHull # type: ignore
37
+
37
38
  Omat = np.squeeze(np.mean(pts3d, axis=0))
38
39
 
39
40
  # 2. build e_x, e_y
@@ -537,9 +538,8 @@ class Selector:
537
538
  x: float,
538
539
  y: float,
539
540
  z: float,
540
- nx: float,
541
- ny: float,
542
- nz: float,
541
+ normal_axis: Axis | tuple[float, float, float] | None = None,
542
+ plane: Plane | None = None,
543
543
  tolerance: float = 1e-8) -> FaceSelection:
544
544
  """Returns a FaceSelection for all faces that lie in a provided infinite plane
545
545
  specified by an origin plus a plane normal vector.
@@ -548,17 +548,20 @@ class Selector:
548
548
  x (float): The plane origin X-coordinate
549
549
  y (float): The plane origin Y-coordinate
550
550
  z (float): The plane origin Z-coordinate
551
- nx (float): The plane normal X-component
552
- ny (float): The plane normal Y-component
553
- nz (float): The plane normal Z-component
551
+ normal_axis (Axis, tuple): The plane normal vector
554
552
  tolerance (float, optional): An in plane tolerance (displacement and normal dot product). Defaults to 1e-6.
555
553
 
556
554
  Returns:
557
555
  FaceSelection: All faces that lie in the specified plane
558
556
  """
559
557
  orig = np.array([x,y,z])
560
- norm = np.array([nx,ny,nz])
561
- norm = norm/np.linalg.norm(norm)
558
+ if plane is not None:
559
+ norm = plane.normal.np
560
+ elif normal_axis is not None:
561
+ norm = _parse_vector(normal_axis)
562
+ norm = norm/np.linalg.norm(norm)
563
+ else:
564
+ raise RuntimeError('No plane or axis defined for selection.')
562
565
 
563
566
  dimtags = gmsh.model.getEntities(2)
564
567
  coords = [gmsh.model.occ.getCenterOfMass(*tag) for tag in dimtags]