emerge 1.0.0__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/geo/__init__.py +1 -1
- emerge/_emerge/geo/pcb.py +145 -66
- 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 +120 -21
- emerge/_emerge/mesh3d.py +39 -12
- emerge/_emerge/periodic.py +19 -17
- emerge/_emerge/physics/microwave/microwave_bc.py +17 -4
- emerge/_emerge/physics/microwave/microwave_data.py +3 -0
- emerge/_emerge/plot/pyvista/display.py +9 -1
- emerge/_emerge/plot/simple_plots.py +4 -1
- emerge/_emerge/selection.py +10 -8
- emerge/_emerge/simmodel.py +67 -33
- emerge/_emerge/solver.py +9 -2
- emerge/beta/dxf.py +1 -0
- emerge/lib.py +3 -0
- emerge/materials/__init__.py +1 -0
- emerge/materials/isola.py +294 -0
- emerge/materials/rogers.py +58 -0
- {emerge-1.0.0.dist-info → emerge-1.0.1.dist-info}/METADATA +3 -1
- {emerge-1.0.0.dist-info → emerge-1.0.1.dist-info}/RECORD +26 -21
- {emerge-1.0.0.dist-info → emerge-1.0.1.dist-info}/WHEEL +0 -0
- {emerge-1.0.0.dist-info → emerge-1.0.1.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.0.dist-info → emerge-1.0.1.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/geometry.py
CHANGED
|
@@ -33,6 +33,7 @@ def _map_tags(tags: list[int], mapping: dict[int, list[int]]):
|
|
|
33
33
|
|
|
34
34
|
def _bbcenter(x1, y1, z1, x2, y2, z2):
|
|
35
35
|
return np.array([(x1+x2)/2, (y1+y2)/2, (z1+z2)/2])
|
|
36
|
+
|
|
36
37
|
FaceNames = Literal['back','front','left','right','top','bottom']
|
|
37
38
|
|
|
38
39
|
class _KEY_GENERATOR:
|
|
@@ -47,8 +48,9 @@ class _KEY_GENERATOR:
|
|
|
47
48
|
class _GeometryManager:
|
|
48
49
|
|
|
49
50
|
def __init__(self):
|
|
50
|
-
self.geometry_list: dict[str,
|
|
51
|
+
self.geometry_list: dict[str, dict[str, GeoObject]] = dict()
|
|
51
52
|
self.active: str = ''
|
|
53
|
+
self.geometry_names: dict[str, set[str]] = dict()
|
|
52
54
|
|
|
53
55
|
def get_surfaces(self) -> list[GeoSurface]:
|
|
54
56
|
return [geo for geo in self.all_geometries() if geo.dim==2]
|
|
@@ -56,23 +58,40 @@ class _GeometryManager:
|
|
|
56
58
|
def all_geometries(self, model: str | None = None) -> list[GeoObject]:
|
|
57
59
|
if model is None:
|
|
58
60
|
model = self.active
|
|
59
|
-
return [geo for geo in self.geometry_list[model] if geo._exists]
|
|
61
|
+
return [geo for geo in self.geometry_list[model].values() if geo._exists]
|
|
60
62
|
|
|
63
|
+
def all_names(self, model: str | None = None) -> set[str]:
|
|
64
|
+
if model is None:
|
|
65
|
+
model = self.active
|
|
66
|
+
return self.geometry_names[model]
|
|
67
|
+
|
|
68
|
+
def get_name(self, suggestion: str, model: str | None = None) -> str:
|
|
69
|
+
names = self.all_names(model)
|
|
70
|
+
if suggestion not in names:
|
|
71
|
+
return suggestion
|
|
72
|
+
for i in range(1_000_000):
|
|
73
|
+
if f'{suggestion}_{i}' not in names:
|
|
74
|
+
return f'{suggestion}_{i}'
|
|
75
|
+
raise RuntimeError('Cannot generate a unique name.')
|
|
76
|
+
|
|
61
77
|
def submit_geometry(self, geo: GeoObject, model: str | None = None) -> None:
|
|
62
78
|
if model is None:
|
|
63
79
|
model = self.active
|
|
64
|
-
self.geometry_list[model].
|
|
80
|
+
self.geometry_list[model][geo.name] = geo
|
|
81
|
+
self.geometry_names[model].add(geo.name)
|
|
65
82
|
|
|
66
83
|
def sign_in(self, modelname: str) -> None:
|
|
67
84
|
# if modelname not in self.geometry_list:
|
|
68
85
|
# self.geometry_list[modelname] = []
|
|
69
86
|
if modelname is self.geometry_list:
|
|
70
87
|
logger.warning(f'{modelname} already exist, Geometries will be reset.')
|
|
71
|
-
self.geometry_list[modelname] =
|
|
88
|
+
self.geometry_list[modelname] = dict()
|
|
89
|
+
self.geometry_names[modelname] = set()
|
|
72
90
|
self.active = modelname
|
|
73
91
|
|
|
74
92
|
def reset(self, modelname: str) -> None:
|
|
75
|
-
self.geometry_list[modelname] =
|
|
93
|
+
self.geometry_list[modelname] = dict()
|
|
94
|
+
self.geometry_names[modelname] = set()
|
|
76
95
|
|
|
77
96
|
def lowest_priority(self) -> int:
|
|
78
97
|
return min([geo._priority for geo in self.all_geometries()])
|
|
@@ -219,7 +238,8 @@ class GeoObject:
|
|
|
219
238
|
"""A generalization of any OpenCASCADE entity described by a dimension and a set of tags.
|
|
220
239
|
"""
|
|
221
240
|
dim: int = -1
|
|
222
|
-
|
|
241
|
+
_default_name: str = 'GeoObject'
|
|
242
|
+
def __init__(self, tags: list[int] | None = None, name: str | None = None):
|
|
223
243
|
if tags is None:
|
|
224
244
|
tags = []
|
|
225
245
|
self.old_tags: list[int] = []
|
|
@@ -238,18 +258,63 @@ class GeoObject:
|
|
|
238
258
|
self._priority: int = 10
|
|
239
259
|
|
|
240
260
|
self._exists: bool = True
|
|
261
|
+
|
|
262
|
+
self.give_name(name)
|
|
241
263
|
_GEOMANAGER.submit_geometry(self)
|
|
242
264
|
|
|
265
|
+
def _store(self, name: str, data: Any) -> None:
|
|
266
|
+
"""Store a property as auxilliary data under a given name
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
name (str): Name field
|
|
270
|
+
data (Any): Data to store
|
|
271
|
+
"""
|
|
272
|
+
self._aux_data[name] = data
|
|
273
|
+
|
|
274
|
+
def _load(self, name: str) -> Any | None:
|
|
275
|
+
"""Load data with a given name. If it doesn't exist, it returns None
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
name (str): The property to retreive
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Any | None: The property
|
|
282
|
+
"""
|
|
283
|
+
return self._aux_data.get(name, None)
|
|
284
|
+
|
|
285
|
+
def give_name(self, name: str | None = None) -> GeoObject:
|
|
286
|
+
"""Assign a name to this object
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
name (str | None, optional): The name for the object. Defaults to None.
|
|
290
|
+
"""
|
|
291
|
+
if name is None:
|
|
292
|
+
name = self._default_name
|
|
293
|
+
self.name: str = _GEOMANAGER.get_name(name)
|
|
294
|
+
return self
|
|
295
|
+
|
|
243
296
|
@property
|
|
244
297
|
def color_rgb(self) -> tuple[float, float, float]:
|
|
298
|
+
"""The color of the object in RGB float tuple
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
tuple[float, float, float]: The color
|
|
302
|
+
"""
|
|
245
303
|
return self.material.color_rgb
|
|
246
304
|
|
|
247
305
|
@property
|
|
248
306
|
def opacity(self) -> float:
|
|
307
|
+
"""The opacity of the object
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
float: The opacity
|
|
311
|
+
"""
|
|
249
312
|
return self.material.opacity
|
|
250
313
|
|
|
251
314
|
@property
|
|
252
315
|
def _metal(self) -> bool:
|
|
316
|
+
"""If the material should be rendered as metal
|
|
317
|
+
"""
|
|
253
318
|
return self.material._metal
|
|
254
319
|
|
|
255
320
|
@property
|
|
@@ -266,6 +331,14 @@ class GeoObject:
|
|
|
266
331
|
|
|
267
332
|
@staticmethod
|
|
268
333
|
def merged(objects: list[GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject]) -> list[GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject] | GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject:
|
|
334
|
+
"""Create a GeoObject by merging an iterable of GeoObjects
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
objects (list[GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject]): A list of geo objects
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject: The resultant object
|
|
341
|
+
"""
|
|
269
342
|
dim = objects[0].dim
|
|
270
343
|
tags = []
|
|
271
344
|
out: GeoObject | None = None
|
|
@@ -291,6 +364,17 @@ class GeoObject:
|
|
|
291
364
|
origin: np.ndarray | None = None,
|
|
292
365
|
normal: np.ndarray | None = None,
|
|
293
366
|
tag: int | None = None):
|
|
367
|
+
"""Adds a face identifier (face pointer) to this object
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
name (str): The name for the face
|
|
371
|
+
origin (np.ndarray | None, optional): A point on the object. Defaults to None.
|
|
372
|
+
normal (np.ndarray | None, optional): The normal of the face. Defaults to None.
|
|
373
|
+
tag (int | None, optional): The tace tag used to extract the origin and normal. Defaults to None.
|
|
374
|
+
|
|
375
|
+
Raises:
|
|
376
|
+
ValueError: _description_
|
|
377
|
+
"""
|
|
294
378
|
if tag is not None:
|
|
295
379
|
o = gmsh.model.occ.get_center_of_mass(2, tag)
|
|
296
380
|
n = gmsh.model.get_normal(tag, (0,0))
|
|
@@ -302,6 +386,7 @@ class GeoObject:
|
|
|
302
386
|
raise ValueError('Eitehr a tag or an origin + normal must be provided!')
|
|
303
387
|
|
|
304
388
|
def make_copy(self) -> GeoObject:
|
|
389
|
+
""" Copies this object and returns a new object (also in GMSH)"""
|
|
305
390
|
new_dimtags = gmsh.model.occ.copy(self.dimtags)
|
|
306
391
|
new_obj = GeoObject.from_dimtags(new_dimtags)
|
|
307
392
|
new_obj.material = self.material
|
|
@@ -319,6 +404,11 @@ class GeoObject:
|
|
|
319
404
|
return new_obj
|
|
320
405
|
|
|
321
406
|
def replace_tags(self, tagmap: dict[int, list[int]]):
|
|
407
|
+
"""Replaces the GMSH tags assigned to this objects
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
tagmap (dict[int, list[int]]): A map that shows which tag is mapped to which set of new tags.
|
|
411
|
+
"""
|
|
322
412
|
self.old_tags = self.tags
|
|
323
413
|
newtags = []
|
|
324
414
|
for tag in self.tags:
|
|
@@ -454,7 +544,9 @@ class GeoObject:
|
|
|
454
544
|
self._priority = _GEOMANAGER.highest_priority()+10
|
|
455
545
|
return self
|
|
456
546
|
|
|
457
|
-
def boundary(self, exclude: tuple[FaceNames,...] | None = None,
|
|
547
|
+
def boundary(self, exclude: tuple[FaceNames,...] | None = None,
|
|
548
|
+
tags: list[int] | None = None,
|
|
549
|
+
tool: GeoObject | None = None) -> FaceSelection:
|
|
458
550
|
"""Returns the complete set of boundary faces.
|
|
459
551
|
|
|
460
552
|
If implemented, it is possible to exclude a set of faces based on their name
|
|
@@ -471,7 +563,7 @@ class GeoObject:
|
|
|
471
563
|
|
|
472
564
|
|
|
473
565
|
for name in exclude:
|
|
474
|
-
tags.extend(self.face(name).tags)
|
|
566
|
+
tags.extend(self.face(name, tool=tool).tags)
|
|
475
567
|
dimtags = gmsh.model.get_boundary(self.dimtags, True, False)
|
|
476
568
|
return FaceSelection([t for d,t in dimtags if t not in tags])
|
|
477
569
|
|
|
@@ -535,8 +627,10 @@ class GeoVolume(GeoObject):
|
|
|
535
627
|
'''GeoVolume is an interface to the GMSH CAD kernel. It does not represent EMerge
|
|
536
628
|
specific geometry data.'''
|
|
537
629
|
dim = 3
|
|
538
|
-
|
|
539
|
-
|
|
630
|
+
_default_name: str = 'GeoVolume'
|
|
631
|
+
def __init__(self, tag: int | Iterable[int], name: str | None = None):
|
|
632
|
+
super().__init__(name=name)
|
|
633
|
+
|
|
540
634
|
self.tags: list[int] = []
|
|
541
635
|
if isinstance(tag, Iterable):
|
|
542
636
|
self.tags = list(tag)
|
|
@@ -549,13 +643,14 @@ class GeoVolume(GeoObject):
|
|
|
549
643
|
|
|
550
644
|
class GeoPoint(GeoObject):
|
|
551
645
|
dim = 0
|
|
552
|
-
|
|
646
|
+
_default_name: str = 'GeoPoint'
|
|
647
|
+
|
|
553
648
|
@property
|
|
554
649
|
def selection(self) -> PointSelection:
|
|
555
650
|
return PointSelection(self.tags)
|
|
556
651
|
|
|
557
|
-
def __init__(self, tag: int | list[int]):
|
|
558
|
-
super().__init__()
|
|
652
|
+
def __init__(self, tag: int | list[int], name: str | None = None):
|
|
653
|
+
super().__init__(name=name)
|
|
559
654
|
|
|
560
655
|
self.tags: list[int] = []
|
|
561
656
|
if isinstance(tag, Iterable):
|
|
@@ -565,13 +660,14 @@ class GeoPoint(GeoObject):
|
|
|
565
660
|
|
|
566
661
|
class GeoEdge(GeoObject):
|
|
567
662
|
dim = 1
|
|
568
|
-
|
|
663
|
+
_default_name: str = 'GeoEdge'
|
|
664
|
+
|
|
569
665
|
@property
|
|
570
666
|
def selection(self) -> EdgeSelection:
|
|
571
667
|
return EdgeSelection(self.tags)
|
|
572
668
|
|
|
573
|
-
def __init__(self, tag: int | list[int]):
|
|
574
|
-
super().__init__()
|
|
669
|
+
def __init__(self, tag: int | list[int], name: str | None = None):
|
|
670
|
+
super().__init__(name=name)
|
|
575
671
|
self.tags: list[int] = []
|
|
576
672
|
if isinstance(tag, Iterable):
|
|
577
673
|
self.tags = list(tag)
|
|
@@ -583,13 +679,14 @@ class GeoSurface(GeoObject):
|
|
|
583
679
|
'''GeoVolume is an interface to the GMSH CAD kernel. It does not reprsent Emerge
|
|
584
680
|
specific geometry data.'''
|
|
585
681
|
dim = 2
|
|
586
|
-
|
|
682
|
+
_default_name: str = 'GeoSurface'
|
|
683
|
+
|
|
587
684
|
@property
|
|
588
685
|
def selection(self) -> FaceSelection:
|
|
589
686
|
return FaceSelection(self.tags)
|
|
590
687
|
|
|
591
|
-
def __init__(self, tag: int | list[int]):
|
|
592
|
-
super().__init__()
|
|
688
|
+
def __init__(self, tag: int | list[int], name: str | None = None):
|
|
689
|
+
super().__init__(name=name)
|
|
593
690
|
self.tags: list[int] = []
|
|
594
691
|
if isinstance(tag, Iterable):
|
|
595
692
|
self.tags = list(tag)
|
|
@@ -597,10 +694,12 @@ class GeoSurface(GeoObject):
|
|
|
597
694
|
self.tags = [tag,]
|
|
598
695
|
|
|
599
696
|
class GeoPolygon(GeoSurface):
|
|
697
|
+
_default_name: str = 'GeoPolygon'
|
|
600
698
|
|
|
601
699
|
def __init__(self,
|
|
602
|
-
tags: list[int]
|
|
603
|
-
|
|
700
|
+
tags: list[int],
|
|
701
|
+
name: str | None = None):
|
|
702
|
+
super().__init__(tags, name=name)
|
|
604
703
|
self.points: list[int] = []
|
|
605
704
|
self.lines: list[int] = []
|
|
606
705
|
|
emerge/_emerge/mesh3d.py
CHANGED
|
@@ -122,7 +122,6 @@ class Mesh3D(Mesh):
|
|
|
122
122
|
self.inv_tets: dict = dict()
|
|
123
123
|
|
|
124
124
|
# Mappings
|
|
125
|
-
|
|
126
125
|
self.tet_to_edge: np.ndarray = np.array([])
|
|
127
126
|
self.tet_to_edge_sign: np.ndarray = np.array([])
|
|
128
127
|
self.tet_to_tri: np.ndarray = np.array([])
|
|
@@ -133,7 +132,6 @@ class Mesh3D(Mesh):
|
|
|
133
132
|
self.node_to_edge: defaultdict | dict = defaultdict()
|
|
134
133
|
|
|
135
134
|
# Physics mappings
|
|
136
|
-
|
|
137
135
|
self.tet_to_field: np.ndarray = np.array([])
|
|
138
136
|
self.edge_to_field: np.ndarray = np.array([])
|
|
139
137
|
self.tri_to_field: np.ndarray = np.array([])
|
|
@@ -149,6 +147,10 @@ class Mesh3D(Mesh):
|
|
|
149
147
|
self.vtag_to_tet: dict[int, list[int]] = dict()
|
|
150
148
|
self.etag_to_edge: dict[int, list[int]] = dict()
|
|
151
149
|
|
|
150
|
+
## Dervied
|
|
151
|
+
self.dimtag_to_center: dict[tuple[int, int], tuple[float, float, float]] = dict()
|
|
152
|
+
self.dimtag_to_edges: dict[tuple[int, int], np.ndarray] = dict()
|
|
153
|
+
|
|
152
154
|
self.exterior_face_tags: list[int] = []
|
|
153
155
|
|
|
154
156
|
@property
|
|
@@ -242,7 +244,7 @@ class Mesh3D(Mesh):
|
|
|
242
244
|
|
|
243
245
|
return np.array(indices)
|
|
244
246
|
|
|
245
|
-
def
|
|
247
|
+
def _domain_edge(self, dimtag: tuple[int,int]) -> np.ndarray:
|
|
246
248
|
"""Returns a np.ndarray of all edge indices corresponding to a set of dimension tags.
|
|
247
249
|
|
|
248
250
|
Args:
|
|
@@ -252,21 +254,39 @@ class Mesh3D(Mesh):
|
|
|
252
254
|
np.ndarray: The list of mesh edge element indices.
|
|
253
255
|
"""
|
|
254
256
|
dimtags_edge = []
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
257
|
+
d,t = dimtag
|
|
258
|
+
if d==0:
|
|
259
|
+
return np.ndarray([], dtype=np.int64)
|
|
260
|
+
if d==1:
|
|
261
|
+
dimtags_edge.append((1,t))
|
|
262
|
+
if d==2:
|
|
263
|
+
dimtags_edge.extend(gmsh.model.getBoundary([(d,t),], False, False))
|
|
264
|
+
if d==3:
|
|
265
|
+
dts = gmsh.model.getBoundary([(d,t),], False, False)
|
|
266
|
+
dimtags_edge.extend(gmsh.model.getBoundary(dts, False, False))
|
|
267
|
+
|
|
264
268
|
edge_ids = []
|
|
265
269
|
for tag in dimtags_edge:
|
|
266
270
|
edge_ids.extend(self.etag_to_edge[tag[1]])
|
|
267
271
|
edge_ids = np.array(edge_ids)
|
|
268
272
|
return edge_ids
|
|
273
|
+
|
|
274
|
+
def domain_edges(self, dimtags: list[tuple[int,int]]) -> np.ndarray:
|
|
275
|
+
"""Returns a np.ndarray of all edge indices corresponding to a set of dimension tags.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
dimtags (list[tuple[int,int]]): A list of dimtags.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
np.ndarray: The list of mesh edge element indices.
|
|
282
|
+
"""
|
|
269
283
|
|
|
284
|
+
edge_ids = []
|
|
285
|
+
for dt in dimtags:
|
|
286
|
+
edge_ids.extend(self.dimtag_to_edges[dt])
|
|
287
|
+
edge_ids = np.array(edge_ids)
|
|
288
|
+
return edge_ids
|
|
289
|
+
|
|
270
290
|
def get_face_tets(self, *taglist: list[int]) -> np.ndarray:
|
|
271
291
|
''' Return a list of a tetrahedrons that share a node with any of the nodes in the provided face.'''
|
|
272
292
|
nodes: set = set()
|
|
@@ -487,6 +507,13 @@ class Mesh3D(Mesh):
|
|
|
487
507
|
self.vtag_to_tet[t] = [self.get_tet(node_tags[0,i], node_tags[1,i], node_tags[2,i], node_tags[3,i]) for i in range(node_tags.shape[1])]
|
|
488
508
|
|
|
489
509
|
self.defined = True
|
|
510
|
+
|
|
511
|
+
for dim in (0,1,2,3):
|
|
512
|
+
dts= gmsh.model.get_entities(dim)
|
|
513
|
+
for dt in dts:
|
|
514
|
+
self.dimtag_to_center[dt] = gmsh.model.occ.get_center_of_mass(*dt)
|
|
515
|
+
self.dimtag_to_edges[dt] = self._domain_edge(dt)
|
|
516
|
+
|
|
490
517
|
logger.trace('Done analyzing mesh.')
|
|
491
518
|
|
|
492
519
|
|
emerge/_emerge/periodic.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
from .cs import Axis, _parse_axis, GCS, _parse_vector
|
|
19
|
-
from .selection import SELECTOR_OBJ, Selection
|
|
19
|
+
from .selection import SELECTOR_OBJ, Selection, FaceSelection
|
|
20
20
|
from .geo import GeoPrism, XYPolygon, Alignment, XYPlate
|
|
21
21
|
from .bc import BoundaryCondition
|
|
22
22
|
from typing import Generator
|
|
@@ -50,8 +50,8 @@ def _pair_selection(f1: Selection,
|
|
|
50
50
|
for t1, c1 in zip(f1.tags, c1s):
|
|
51
51
|
for t2, c2 in zip(f2.tags, c2s):
|
|
52
52
|
if np.linalg.norm((c1 + ds)-c2) < 1e-8:
|
|
53
|
-
f1s.append(
|
|
54
|
-
f2s.append(
|
|
53
|
+
f1s.append(FaceSelection([t1,]))
|
|
54
|
+
f2s.append(FaceSelection([t2,]))
|
|
55
55
|
return f1s, f2s
|
|
56
56
|
|
|
57
57
|
|
|
@@ -69,7 +69,7 @@ class PeriodicCell:
|
|
|
69
69
|
|
|
70
70
|
self.origins: list[tuple[float, float, float]] = [_parse_vector(origin) for origin in origins] # type: ignore
|
|
71
71
|
self.vectors: list[Axis] = [_parse_axis(vec) for vec in vectors]
|
|
72
|
-
self.
|
|
72
|
+
self.included_faces: Selection | None = None
|
|
73
73
|
self._bcs: list[Periodic] = []
|
|
74
74
|
self._ports: list[BoundaryCondition] = []
|
|
75
75
|
|
|
@@ -105,9 +105,11 @@ class PeriodicCell:
|
|
|
105
105
|
for f1, f2, a in self.cell_data():
|
|
106
106
|
f1_new = f1
|
|
107
107
|
f2_new = f2
|
|
108
|
-
if self.
|
|
109
|
-
f1_new = f1
|
|
110
|
-
f2_new = f2
|
|
108
|
+
if self.included_faces is not None:
|
|
109
|
+
f1_new = f1 & self.included_faces # type: ignore
|
|
110
|
+
f2_new = f2 & self.included_faces # type: ignore
|
|
111
|
+
if len(f1_new.tags)==0:
|
|
112
|
+
continue
|
|
111
113
|
bcs.append(Periodic(f1_new, f2_new, tuple(a)))
|
|
112
114
|
self._bcs = bcs
|
|
113
115
|
return bcs
|
|
@@ -191,16 +193,16 @@ class RectCell(PeriodicCell):
|
|
|
191
193
|
return XYPlate(self.width, self.height, position=(0,0,z), alignment=Alignment.CENTER)
|
|
192
194
|
|
|
193
195
|
def cell_data(self):
|
|
194
|
-
f1s = SELECTOR_OBJ.inplane(*self.fleft[0],
|
|
195
|
-
f2s = SELECTOR_OBJ.inplane(*self.fright[0],
|
|
196
|
+
f1s = SELECTOR_OBJ.inplane(*self.fleft[0], self.fleft[1])
|
|
197
|
+
f2s = SELECTOR_OBJ.inplane(*self.fright[0], self.fright[1])
|
|
196
198
|
vec = (self.fright[0][0]-self.fleft[0][0],
|
|
197
199
|
self.fright[0][1]-self.fleft[0][1],
|
|
198
200
|
self.fright[0][2]-self.fleft[0][2])
|
|
199
201
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
|
|
200
202
|
yield f1, f2, vec
|
|
201
203
|
|
|
202
|
-
f1s = SELECTOR_OBJ.inplane(*self.fbot[0],
|
|
203
|
-
f2s = SELECTOR_OBJ.inplane(*self.ftop[0],
|
|
204
|
+
f1s = SELECTOR_OBJ.inplane(*self.fbot[0], self.fbot[1])
|
|
205
|
+
f2s = SELECTOR_OBJ.inplane(*self.ftop[0], self.ftop[1])
|
|
204
206
|
vec = (self.ftop[0][0]-self.fbot[0][0],
|
|
205
207
|
self.ftop[0][1]-self.fbot[0][1],
|
|
206
208
|
self.ftop[0][2]-self.fbot[0][2])
|
|
@@ -273,9 +275,9 @@ class HexCell(PeriodicCell):
|
|
|
273
275
|
o = self.o1[:-1]
|
|
274
276
|
n = self.f11[1][:-1]
|
|
275
277
|
w = nrm(self.p2-self.p1)/2
|
|
276
|
-
f1s = SELECTOR_OBJ.inplane(*self.f11[0],
|
|
278
|
+
f1s = SELECTOR_OBJ.inplane(*self.f11[0], self.f11[1])\
|
|
277
279
|
.exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
|
|
278
|
-
f2s = SELECTOR_OBJ.inplane(*self.f12[0],
|
|
280
|
+
f2s = SELECTOR_OBJ.inplane(*self.f12[0], self.f12[1])\
|
|
279
281
|
.exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
|
|
280
282
|
vec = - (self.p1 + self.p2)
|
|
281
283
|
|
|
@@ -285,9 +287,9 @@ class HexCell(PeriodicCell):
|
|
|
285
287
|
o = self.o2[:-1]
|
|
286
288
|
n = self.f21[1][:-1]
|
|
287
289
|
w = nrm(self.p3-self.p2)/2
|
|
288
|
-
f1s = SELECTOR_OBJ.inplane(*self.f21[0],
|
|
290
|
+
f1s = SELECTOR_OBJ.inplane(*self.f21[0], self.f21[1])\
|
|
289
291
|
.exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
|
|
290
|
-
f2s = SELECTOR_OBJ.inplane(*self.f22[0],
|
|
292
|
+
f2s = SELECTOR_OBJ.inplane(*self.f22[0], self.f22[1])\
|
|
291
293
|
.exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
|
|
292
294
|
vec = - (self.p2 + self.p3)
|
|
293
295
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
|
|
@@ -296,9 +298,9 @@ class HexCell(PeriodicCell):
|
|
|
296
298
|
o = self.o3[:-1]
|
|
297
299
|
n = self.f31[1][:-1]
|
|
298
300
|
w = nrm(-self.p1-self.p3)/2
|
|
299
|
-
f1s = SELECTOR_OBJ.inplane(*self.f31[0],
|
|
301
|
+
f1s = SELECTOR_OBJ.inplane(*self.f31[0], self.f31[1])\
|
|
300
302
|
.exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
|
|
301
|
-
f2s = SELECTOR_OBJ.inplane(*self.f32[0],
|
|
303
|
+
f2s = SELECTOR_OBJ.inplane(*self.f32[0], self.f32[1])\
|
|
302
304
|
.exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
|
|
303
305
|
vec = - (self.p3 - self.p1)
|
|
304
306
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
|
|
@@ -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,7 +1031,11 @@ 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
1040
|
self.sigma = material.cond.scalar(1e9)
|
|
1035
1041
|
self._mur = material.ur
|
|
@@ -1067,10 +1073,17 @@ class SurfaceImpedance(RobinBC):
|
|
|
1067
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
|
|
@@ -486,6 +486,14 @@ class PVDisplay(BaseDisplay):
|
|
|
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
|
+
|
|
489
497
|
def add_objects(self, *objects, **kwargs) -> None:
|
|
490
498
|
"""Add a series of objects provided as a list of arguments
|
|
491
499
|
"""
|
|
@@ -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]
|