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.
- emerge/__init__.py +2 -2
- emerge/_emerge/bc.py +8 -3
- emerge/_emerge/cacherun.py +79 -0
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/pcb.py +160 -71
- emerge/_emerge/geo/pcb_tools/dxf.py +360 -0
- emerge/_emerge/geo/polybased.py +21 -15
- emerge/_emerge/geo/shapes.py +31 -16
- emerge/_emerge/geometry.py +147 -21
- emerge/_emerge/material.py +2 -1
- emerge/_emerge/mesh3d.py +39 -12
- emerge/_emerge/periodic.py +19 -17
- emerge/_emerge/physics/microwave/assembly/assembler.py +12 -12
- emerge/_emerge/physics/microwave/microwave_3d.py +29 -6
- emerge/_emerge/physics/microwave/microwave_bc.py +22 -9
- emerge/_emerge/physics/microwave/microwave_data.py +3 -0
- emerge/_emerge/plot/pyvista/display.py +20 -6
- emerge/_emerge/plot/pyvista/display_settings.py +2 -1
- emerge/_emerge/plot/simple_plots.py +4 -1
- emerge/_emerge/selection.py +10 -8
- emerge/_emerge/settings.py +12 -0
- emerge/_emerge/simmodel.py +182 -48
- emerge/_emerge/solver.py +9 -2
- emerge/beta/dxf.py +1 -0
- emerge/lib.py +4 -1
- emerge/materials/__init__.py +1 -0
- emerge/materials/isola.py +294 -0
- emerge/materials/rogers.py +58 -0
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/METADATA +6 -8
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/RECORD +33 -26
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/WHEEL +0 -0
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/entry_points.txt +0 -0
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -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 >
|
|
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.
|
|
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
|
-
|
|
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
|
|
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*
|
|
1073
|
-
|
|
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.
|
|
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 =
|
|
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,
|
|
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 = '
|
|
586
|
+
cmap: cmap_names = 'viridis',
|
|
573
587
|
clim: tuple[float, float] | None = None,
|
|
574
588
|
opacity: float = 1.0,
|
|
575
|
-
symmetrize: bool =
|
|
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
|
|
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
|
|
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)
|
emerge/_emerge/selection.py
CHANGED
|
@@ -537,9 +537,8 @@ class Selector:
|
|
|
537
537
|
x: float,
|
|
538
538
|
y: float,
|
|
539
539
|
z: float,
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
561
|
-
|
|
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]
|