emerge 0.6.11__py3-none-any.whl → 1.0.1__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.

@@ -19,12 +19,14 @@ from ...mesher import Mesher
19
19
  from ...material import Material
20
20
  from ...mesh3d import Mesh3D
21
21
  from ...coord import Line
22
+ from ...geometry import GeoSurface
22
23
  from ...elements.femdata import FEMBasis
23
24
  from ...elements.nedelec2 import Nedelec2
24
25
  from ...solver import DEFAULT_ROUTINE, SolveRoutine
25
26
  from ...system import called_from_main_function
26
27
  from ...selection import FaceSelection
27
28
  from ...mth.optimized import compute_distances
29
+ from ...settings import Settings
28
30
  from .microwave_bc import MWBoundaryConditionSet, PEC, ModalPort, LumpedPort, PortBC
29
31
  from .microwave_data import MWData
30
32
  from .assembly.assembler import Assembler
@@ -122,16 +124,16 @@ class Microwave3D:
122
124
  formulation.
123
125
 
124
126
  """
125
- def __init__(self, mesher: Mesher, mwdata: MWData, order: int = 2):
127
+ def __init__(self, mesher: Mesher, settings: Settings, mwdata: MWData, order: int = 2):
126
128
  self.frequencies: list[float] = []
127
129
  self.current_frequency = 0
128
130
  self.order: int = order
129
131
  self.resolution: float = 1
130
-
132
+ self._settings: Settings = settings
131
133
  self.mesher: Mesher = mesher
132
134
  self.mesh: Mesh3D = Mesh3D(self.mesher)
133
135
 
134
- self.assembler: Assembler = Assembler()
136
+ self.assembler: Assembler = Assembler(self._settings)
135
137
  self.bc: MWBoundaryConditionSet = MWBoundaryConditionSet(None)
136
138
  self.basis: Nedelec2 | None = None
137
139
  self.solveroutine: SolveRoutine = DEFAULT_ROUTINE
@@ -190,14 +192,28 @@ class Microwave3D:
190
192
  return sorted(self.bc.oftype(PortBC), key=lambda x: x.number) # type: ignore
191
193
 
192
194
 
193
- def _initialize_bcs(self) -> None:
195
+ def _initialize_bcs(self, surfaces: list[GeoSurface]) -> None:
194
196
  """Initializes the boundary conditions to set PEC as all exterior boundaries.
195
197
  """
196
198
  logger.debug('Initializing boundary conditions.')
197
199
 
198
200
  tags = self.mesher.domain_boundary_face_tags
201
+
202
+ # Assigning surface impedance boundary condition
203
+ if self._settings.mw_2dbc:
204
+ for surf in surfaces:
205
+ if surf.material.cond.scalar(1e9) > self._settings.mw_2dbc_peclim:
206
+ logger.debug(f'Assinging PEC to {surf}')
207
+ self.bc.PEC(surf)
208
+ elif surf.material.cond.scalar(1e9) > self._settings.mw_2dbc_lim:
209
+ logger.debug(f'Assigning SurfaceImpedance to {surf}')
210
+ self.bc.SurfaceImpedance(surf, surf.material)
211
+
212
+
199
213
  tags = [tag for tag in tags if tag not in self.bc.assigned(2)]
214
+
200
215
  self.bc.PEC(FaceSelection(tags))
216
+
201
217
  logger.info(f'Adding PEC boundary condition with tags {tags}.')
202
218
  if self.mesher.periodic_cell is not None:
203
219
  self.mesher.periodic_cell.generate_bcs()
@@ -247,6 +263,12 @@ class Microwave3D:
247
263
  resolution (float): The desired wavelength fraction.
248
264
 
249
265
  """
266
+ if resolution > 0.5:
267
+ logger.warning('Resolutions greater than 0.5 cannot yield accurate results, capping resolution to 0.4')
268
+ resolution = 0.4
269
+ elif resolution > 0.334:
270
+ logger.warning('A resolution greater than 0.33 may cause accuracy issues.')
271
+
250
272
  self.resolution = resolution
251
273
 
252
274
  def set_conductivity_limit(self, condutivity: float) -> None:
@@ -292,6 +314,7 @@ class Microwave3D:
292
314
  ''' Initializes auxilliary required boundary condition information before running simulations.
293
315
  '''
294
316
  logger.debug('Initializing boundary conditions')
317
+ self.bc.cleanup()
295
318
  for port in self.bc.oftype(LumpedPort):
296
319
  self.define_lumped_port_integration_points(port)
297
320
 
@@ -572,10 +595,10 @@ class Microwave3D:
572
595
  elif TEM:
573
596
  G1, G2 = self._find_tem_conductors(port, sigtri=cond)
574
597
  cs, dls = self._compute_integration_line(G1,G2)
575
- mode.modetype='TEM'
598
+ mode.modetype = 'TEM'
576
599
  Ex, Ey, Ez = portfE(cs[0,:], cs[1,:], cs[2,:])
577
600
  voltage = np.sum(Ex*dls[0,:] + Ey*dls[1,:] + Ez*dls[2,:])
578
- mode.Z0 = voltage**2/(2*P)
601
+ mode.Z0 = abs(voltage**2/(2*P))
579
602
  logger.debug(f'Port Z0 = {mode.Z0}')
580
603
 
581
604
  mode.set_power(P*port._qmode(k0)**2)
@@ -73,11 +73,11 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
73
73
  """
74
74
  bcs = self.oftype(PEC)
75
75
  for bc in self.oftype(SurfaceImpedance):
76
- if bc.sigma > 1e3:
76
+ if bc.sigma > 10.0:
77
77
  bcs.append(bc)
78
78
 
79
79
  return bcs
80
-
80
+
81
81
  def get_type(self, bctype: Literal['PEC','ModalPort','LumpedPort','PMC','LumpedElement','RectangularWaveguide','Periodic','FloquetPort','SurfaceImpedance']) -> FaceSelection:
82
82
  tags = []
83
83
  for bc in self.boundary_conditions:
@@ -545,7 +545,7 @@ class ModalPort(PortBC):
545
545
  self.modes[k0] = new_modes
546
546
  return
547
547
  for k0, modes in self.modes.items():
548
- self.modes[k0] = sorted(modes, key=lambda m: m.energy, reverse=True)
548
+ self.modes[k0] = sorted(modes, key=lambda m: m.beta, reverse=True)
549
549
 
550
550
  def get_mode(self, k0: float, i=None) -> PortMode:
551
551
  """Returns a given mode solution in the form of a PortMode object.
@@ -1002,6 +1002,7 @@ class SurfaceImpedance(RobinBC):
1002
1002
  material: Material | None = None,
1003
1003
  surface_conductance: float | None = None,
1004
1004
  surface_roughness: float = 0,
1005
+ thickness: float | None = None,
1005
1006
  sr_model: Literal['Hammerstad-Jensen'] = 'Hammerstad-Jensen',
1006
1007
  ):
1007
1008
  """Generates a SurfaceImpedance bounary condition.
@@ -1021,7 +1022,8 @@ class SurfaceImpedance(RobinBC):
1021
1022
  material (Material | None, optional): The matrial to assign. Defaults to None.
1022
1023
  surface_conductance (float | None, optional): The specific bulk conductivity to use. Defaults to None.
1023
1024
  surface_roughness (float, optional): The surface roughness. Defaults to 0.
1024
- sr_model (Literal['Hammerstad, optional): The surface roughness model. Defaults to 'Hammerstad-Jensen'.
1025
+ thickness (float | None, optional): The layer thickness. Defaults to None
1026
+ sr_model (Literal["Hammerstad-Jensen", optional): The surface roughness model. Defaults to 'Hammerstad-Jensen'.
1025
1027
  """
1026
1028
  super().__init__(face)
1027
1029
 
@@ -1029,9 +1031,13 @@ class SurfaceImpedance(RobinBC):
1029
1031
  self._mur: float | complex = 1.0
1030
1032
  self._epsr: float | complex = 1.0
1031
1033
  self.sigma: float = 0.0
1034
+ self.thickness: float | None = thickness
1032
1035
 
1036
+ if isinstance(face, GeoObject) and thickness is None:
1037
+ self.thickness = face._load('thickness')
1038
+
1033
1039
  if material is not None:
1034
- self.sigma = material.cond
1040
+ self.sigma = material.cond.scalar(1e9)
1035
1041
  self._mur = material.ur
1036
1042
  self._epsr = material.er
1037
1043
 
@@ -1064,13 +1070,20 @@ class SurfaceImpedance(RobinBC):
1064
1070
 
1065
1071
  w0 = k0*C0
1066
1072
  f0 = w0/(2*np.pi)
1067
- sigma = self.sigma.scalar(f0)
1073
+ sigma = self.sigma
1068
1074
  mur = self._material.ur.scalar(f0)
1069
1075
  er = self._material.er.scalar(f0)
1070
-
1076
+ eps = EPS0*er
1077
+ mu = MU0*mur
1071
1078
  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
1079
+ d_skin = (2*rho/(w0*mu) * ((1+(w0*eps*rho)**2)**0.5 + rho*w0*eps))**0.5
1080
+ logger.debug(f'Computed skin depth δ={d_skin*1e6:.2}μm')
1081
+ R = (1+1j)*rho/d_skin
1082
+ if self.thickness is not None:
1083
+ eps_c = eps - 1j * sigma / w0
1084
+ gamma_m = 1j * w0 * np.sqrt(mu*eps_c)
1085
+ R = R / np.tanh(gamma_m * self.thickness)
1086
+ logger.debug(f'Impedance scaler due to thickness: {1/ np.tanh(gamma_m * self.thickness) :.4f}')
1074
1087
  if self._sr_model=='Hammerstad-Jensen' and self._sr > 0.0:
1075
1088
  R = R * (1 + 2/np.pi * np.arctan(1.4*(self._sr/d_skin)**2))
1076
1089
  return 1j*k0*Z0/R
@@ -1137,6 +1137,9 @@ class MWScalarNdim:
1137
1137
  self._portmap: dict[int, float|int] = dict()
1138
1138
  self._portnumbers: list[int | float] = []
1139
1139
 
1140
+ def dense_f(self, N: int) -> np.ndarray:
1141
+ return np.linspace(np.min(self.freq), np.max(self.freq), N)
1142
+
1140
1143
  def S(self, i1: int, i2: int) -> np.ndarray:
1141
1144
  return self.Sp[...,self._portmap[i1], self._portmap[i2]]
1142
1145
 
@@ -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
@@ -446,13 +446,13 @@ class PVDisplay(BaseDisplay):
446
446
 
447
447
  # Default render settings
448
448
  metallic = 0.05
449
- roughness = 0.5
449
+ roughness = 0.0
450
450
  pbr = False
451
451
 
452
452
  if metal:
453
453
  pbr = True
454
454
  metallic = 0.8
455
- roughness = 0.3
455
+ roughness = self.set.metal_roughness
456
456
 
457
457
  # Default keyword arguments when plotting Mesh mode.
458
458
  if mesh is True:
@@ -482,10 +482,24 @@ class PVDisplay(BaseDisplay):
482
482
  if obj.dim==3:
483
483
  mapper = actor.GetMapper()
484
484
  mapper.SetResolveCoincidentTopology(1)
485
- mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(1,1)
485
+ mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(1,0.5)
486
486
 
487
487
  self._plot.add_mesh(self._volume_edges(_select(obj)), color='#000000', line_width=2, show_edges=True)
488
488
 
489
+ if isinstance(obj, GeoObject) and label:
490
+ points = []
491
+ labels = []
492
+ for dt in obj.dimtags:
493
+ points.append(self._mesh.dimtag_to_center[dt])
494
+ labels.append(obj.name)
495
+ self._plot.add_point_labels(points, labels, shape_color='white')
496
+
497
+ def add_objects(self, *objects, **kwargs) -> None:
498
+ """Add a series of objects provided as a list of arguments
499
+ """
500
+ for obj in objects:
501
+ self.add_object(obj, **kwargs)
502
+
489
503
  def add_scatter(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray):
490
504
  """Adds a scatter point cloud
491
505
 
@@ -569,10 +583,10 @@ class PVDisplay(BaseDisplay):
569
583
  z: np.ndarray,
570
584
  field: np.ndarray,
571
585
  scale: Literal['lin','log','symlog'] = 'lin',
572
- cmap: cmap_names = 'coolwarm',
586
+ cmap: cmap_names = 'viridis',
573
587
  clim: tuple[float, float] | None = None,
574
588
  opacity: float = 1.0,
575
- symmetrize: bool = True,
589
+ symmetrize: bool = False,
576
590
  _fieldname: str | None = None,
577
591
  **kwargs,):
578
592
  """Add a surface plot to the display
@@ -23,6 +23,7 @@ class PVDisplaySettings:
23
23
  self.background_bottom: str = "#c0d2e8"
24
24
  self.background_top: str = "#ffffff"
25
25
  self.grid_line_color: str = "#8e8e8e"
26
- self.z_boost: float = 0#1e-9
26
+ self.z_boost: float = 0.0
27
27
  self.depth_peeling: bool = True
28
28
  self.anti_aliassing: Literal["msaa","ssaa",'fxaa'] = "msaa"
29
+ self.metal_roughness: float = 0.3
@@ -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)
@@ -537,9 +537,8 @@ class Selector:
537
537
  x: float,
538
538
  y: float,
539
539
  z: float,
540
- nx: float,
541
- ny: float,
542
- nz: float,
540
+ normal_axis: Axis | tuple[float, float, float] | None = None,
541
+ plane: Plane | None = None,
543
542
  tolerance: float = 1e-8) -> FaceSelection:
544
543
  """Returns a FaceSelection for all faces that lie in a provided infinite plane
545
544
  specified by an origin plus a plane normal vector.
@@ -548,17 +547,20 @@ class Selector:
548
547
  x (float): The plane origin X-coordinate
549
548
  y (float): The plane origin Y-coordinate
550
549
  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
550
+ normal_axis (Axis, tuple): The plane normal vector
554
551
  tolerance (float, optional): An in plane tolerance (displacement and normal dot product). Defaults to 1e-6.
555
552
 
556
553
  Returns:
557
554
  FaceSelection: All faces that lie in the specified plane
558
555
  """
559
556
  orig = np.array([x,y,z])
560
- norm = np.array([nx,ny,nz])
561
- norm = norm/np.linalg.norm(norm)
557
+ if plane is not None:
558
+ norm = plane.normal.np
559
+ elif normal_axis is not None:
560
+ norm = _parse_vector(normal_axis)
561
+ norm = norm/np.linalg.norm(norm)
562
+ else:
563
+ raise RuntimeError('No plane or axis defined for selection.')
562
564
 
563
565
  dimtags = gmsh.model.getEntities(2)
564
566
  coords = [gmsh.model.occ.getCenterOfMass(*tag) for tag in dimtags]
@@ -0,0 +1,12 @@
1
+
2
+ from typing import Literal
3
+
4
+ class Settings:
5
+
6
+ def __init__(self):
7
+ self.mw_2dbc: bool = True
8
+ self.mw_2dbc_lim: float = 10.0
9
+ self.mw_2dbc_peclim: float = 1e8
10
+ self.mw_3d_peclim: float = 1e7
11
+
12
+ DEFAULT_SETTINGS = Settings()