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.
- emerge/_emerge/bc.py +14 -20
- emerge/_emerge/const.py +5 -0
- emerge/_emerge/cs.py +2 -2
- emerge/_emerge/elements/femdata.py +14 -14
- emerge/_emerge/elements/index_interp.py +1 -1
- emerge/_emerge/elements/ned2_interp.py +1 -1
- emerge/_emerge/elements/nedelec2.py +4 -4
- emerge/_emerge/elements/nedleg2.py +10 -10
- emerge/_emerge/geo/horn.py +1 -1
- emerge/_emerge/geo/modeler.py +18 -19
- emerge/_emerge/geo/operations.py +13 -10
- emerge/_emerge/geo/pcb.py +180 -82
- emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
- emerge/_emerge/geo/pcb_tools/macro.py +14 -13
- emerge/_emerge/geo/pmlbox.py +1 -1
- emerge/_emerge/geometry.py +47 -33
- emerge/_emerge/logsettings.py +15 -16
- emerge/_emerge/material.py +15 -11
- emerge/_emerge/mesh3d.py +81 -59
- emerge/_emerge/mesher.py +26 -21
- emerge/_emerge/mth/integrals.py +1 -1
- emerge/_emerge/mth/pairing.py +2 -2
- emerge/_emerge/periodic.py +34 -31
- emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
- emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
- emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
- emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
- emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
- emerge/_emerge/physics/microwave/microwave_data.py +36 -38
- emerge/_emerge/physics/microwave/sc.py +26 -26
- emerge/_emerge/physics/microwave/simjob.py +20 -15
- emerge/_emerge/physics/microwave/sparam.py +12 -12
- emerge/_emerge/physics/microwave/touchstone.py +1 -1
- emerge/_emerge/plot/display.py +12 -6
- emerge/_emerge/plot/pyvista/display.py +44 -39
- emerge/_emerge/plot/pyvista/display_settings.py +1 -1
- emerge/_emerge/plot/simple_plots.py +15 -15
- emerge/_emerge/selection.py +35 -39
- emerge/_emerge/simmodel.py +41 -47
- emerge/_emerge/simulation_data.py +24 -15
- emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
- emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
- emerge/_emerge/solver.py +314 -136
- emerge/cli.py +1 -1
- emerge/lib.py +245 -248
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
- emerge-0.5.3.dist-info/RECORD +83 -0
- emerge/_emerge/plot/grapher.py +0 -93
- emerge-0.5.1.dist-info/RECORD +0 -82
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/geometry.py
CHANGED
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
|
-
import gmsh
|
|
19
|
+
import gmsh # type: ignore
|
|
20
20
|
from .material import Material, AIR
|
|
21
|
-
from .selection import FaceSelection, DomainSelection, EdgeSelection, PointSelection
|
|
21
|
+
from .selection import FaceSelection, DomainSelection, EdgeSelection, PointSelection, Selection
|
|
22
22
|
from loguru import logger
|
|
23
|
-
from typing import Literal, Any
|
|
23
|
+
from typing import Literal, Any, Iterable, TypeVar
|
|
24
24
|
import numpy as np
|
|
25
25
|
|
|
26
|
+
|
|
27
|
+
|
|
26
28
|
def _map_tags(tags: list[int], mapping: dict[int, list[int]]):
|
|
27
29
|
new_tags = []
|
|
28
30
|
for tag in tags:
|
|
@@ -48,12 +50,12 @@ class _GeometryManager:
|
|
|
48
50
|
self.geometry_list: dict[str, list[GeoObject]] = dict()
|
|
49
51
|
self.active: str = ''
|
|
50
52
|
|
|
51
|
-
def all_geometries(self, model: str = None) -> list[GeoObject]:
|
|
53
|
+
def all_geometries(self, model: str | None = None) -> list[GeoObject]:
|
|
52
54
|
if model is None:
|
|
53
55
|
model = self.active
|
|
54
56
|
return [geo for geo in self.geometry_list[model] if geo._exists]
|
|
55
57
|
|
|
56
|
-
def submit_geometry(self, geo: GeoObject, model: str = None) -> None:
|
|
58
|
+
def submit_geometry(self, geo: GeoObject, model: str | None = None) -> None:
|
|
57
59
|
if model is None:
|
|
58
60
|
model = self.active
|
|
59
61
|
self.geometry_list[model].append(geo)
|
|
@@ -135,7 +137,7 @@ class _FacePointer:
|
|
|
135
137
|
def translate(self, dx, dy, dz):
|
|
136
138
|
self.o = self.o + np.array([dx, dy, dz])
|
|
137
139
|
|
|
138
|
-
def mirror(self, c0: np.ndarray, pln: np.ndarray):
|
|
140
|
+
def mirror(self, c0: np.ndarray, pln: np.ndarray) -> None:
|
|
139
141
|
"""
|
|
140
142
|
Reflect self.o and self.n across the plane passing through c0
|
|
141
143
|
with normal pln.
|
|
@@ -205,9 +207,11 @@ class GeoObject:
|
|
|
205
207
|
"""A generalization of any OpenCASCADE entity described by a dimension and a set of tags.
|
|
206
208
|
"""
|
|
207
209
|
dim: int = -1
|
|
208
|
-
def __init__(self):
|
|
210
|
+
def __init__(self, tags: list[int] | None = None):
|
|
211
|
+
if tags is None:
|
|
212
|
+
tags = []
|
|
209
213
|
self.old_tags: list[int] = []
|
|
210
|
-
self.tags: list[int] =
|
|
214
|
+
self.tags: list[int] = tags
|
|
211
215
|
self.material: Material = AIR
|
|
212
216
|
self.mesh_multiplier: float = 1.0
|
|
213
217
|
self.max_meshsize: float = 1e9
|
|
@@ -225,7 +229,7 @@ class GeoObject:
|
|
|
225
229
|
_GEOMANAGER.submit_geometry(self)
|
|
226
230
|
|
|
227
231
|
@property
|
|
228
|
-
def color_rgb(self) -> tuple[
|
|
232
|
+
def color_rgb(self) -> tuple[float, float, float]:
|
|
229
233
|
return self.material.color_rgb
|
|
230
234
|
|
|
231
235
|
@property
|
|
@@ -233,7 +237,7 @@ class GeoObject:
|
|
|
233
237
|
return self.material.opacity
|
|
234
238
|
|
|
235
239
|
@property
|
|
236
|
-
def select(self) ->
|
|
240
|
+
def select(self) -> Selection:
|
|
237
241
|
'''Returns a corresponding Face/Domain or Edge Selection object'''
|
|
238
242
|
if self.dim==1:
|
|
239
243
|
return EdgeSelection(self.tags)
|
|
@@ -241,11 +245,14 @@ class GeoObject:
|
|
|
241
245
|
return FaceSelection(self.tags)
|
|
242
246
|
elif self.dim==3:
|
|
243
247
|
return DomainSelection(self.tags)
|
|
248
|
+
else:
|
|
249
|
+
return Selection(self.tags)
|
|
244
250
|
|
|
245
251
|
@staticmethod
|
|
246
|
-
def merged(objects: list[GeoObject]) -> list[GeoObject]:
|
|
252
|
+
def merged(objects: list[GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject]) -> list[GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject] | GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject:
|
|
247
253
|
dim = objects[0].dim
|
|
248
254
|
tags = []
|
|
255
|
+
out: GeoObject | None = None
|
|
249
256
|
for obj in objects:
|
|
250
257
|
tags.extend(obj.tags)
|
|
251
258
|
if dim==2:
|
|
@@ -260,8 +267,8 @@ class GeoObject:
|
|
|
260
267
|
def __repr__(self) -> str:
|
|
261
268
|
return f'{self.__class__.__name__}({self.dim},{self.tags})'
|
|
262
269
|
|
|
263
|
-
def _data(self, *labels) -> tuple[Any]:
|
|
264
|
-
return tuple([self._aux_data
|
|
270
|
+
def _data(self, *labels) -> tuple[Any | None, ...]:
|
|
271
|
+
return tuple([self._aux_data[lab] for lab in labels])
|
|
265
272
|
|
|
266
273
|
def _add_face_pointer(self,
|
|
267
274
|
name: str,
|
|
@@ -292,7 +299,7 @@ class GeoObject:
|
|
|
292
299
|
for tag in self.tags:
|
|
293
300
|
newtags.extend(tagmap.get(tag, [tag,]))
|
|
294
301
|
self.tags = newtags
|
|
295
|
-
logger.debug(f'Replaced {self.old_tags}
|
|
302
|
+
logger.debug(f'{self} Replaced {self.old_tags} -> {self.tags}')
|
|
296
303
|
|
|
297
304
|
def update_tags(self, tag_mapping: dict[int,dict]) -> GeoObject:
|
|
298
305
|
''' Update the tag definition of a GeoObject after fragementation.'''
|
|
@@ -325,7 +332,7 @@ class GeoObject:
|
|
|
325
332
|
self._tools.update(obj._tools)
|
|
326
333
|
return self
|
|
327
334
|
|
|
328
|
-
def _face_tags(self, name: FaceNames, tool: GeoObject = None) -> list[int]:
|
|
335
|
+
def _face_tags(self, name: FaceNames, tool: GeoObject | None = None) -> list[int]:
|
|
329
336
|
names = self._all_pointer_names
|
|
330
337
|
if name not in names:
|
|
331
338
|
raise ValueError(f'The face {name} does not exist in {self}')
|
|
@@ -380,7 +387,7 @@ class GeoObject:
|
|
|
380
387
|
self._priority -= 1
|
|
381
388
|
return self
|
|
382
389
|
|
|
383
|
-
def outside(self, *exclude: FaceNames, tags: list[int] = None) -> FaceSelection:
|
|
390
|
+
def outside(self, *exclude: FaceNames, tags: list[int] | None = None) -> FaceSelection:
|
|
384
391
|
"""Returns the complete set of outside faces.
|
|
385
392
|
|
|
386
393
|
If implemented, it is possible to exclude a set of faces based on their name
|
|
@@ -394,7 +401,7 @@ class GeoObject:
|
|
|
394
401
|
dimtags = gmsh.model.get_boundary(self.dimtags, True, False)
|
|
395
402
|
return FaceSelection([t for d,t in dimtags if t not in tags])
|
|
396
403
|
|
|
397
|
-
def face(self, name: FaceNames, tool: GeoObject = None) -> FaceSelection:
|
|
404
|
+
def face(self, name: FaceNames, tool: GeoObject | None = None) -> FaceSelection:
|
|
398
405
|
"""Returns the FaceSelection for a given face name.
|
|
399
406
|
|
|
400
407
|
The face name must be defined for the type of geometry.
|
|
@@ -422,7 +429,7 @@ class GeoObject:
|
|
|
422
429
|
return FaceSelection([t[1] for t in tags])
|
|
423
430
|
if self.dim == 2:
|
|
424
431
|
return FaceSelection(self.tags)
|
|
425
|
-
|
|
432
|
+
else:
|
|
426
433
|
raise ValueError('Can only generate faces for objects of dimension 2 or higher.')
|
|
427
434
|
|
|
428
435
|
@staticmethod
|
|
@@ -443,12 +450,13 @@ class GeoVolume(GeoObject):
|
|
|
443
450
|
'''GeoVolume is an interface to the GMSH CAD kernel. It does not represent EMerge
|
|
444
451
|
specific geometry data.'''
|
|
445
452
|
dim = 3
|
|
446
|
-
def __init__(self, tag: int |
|
|
453
|
+
def __init__(self, tag: int | Iterable[int]):
|
|
447
454
|
super().__init__()
|
|
448
|
-
|
|
449
|
-
|
|
455
|
+
self.tags: list[int] = []
|
|
456
|
+
if isinstance(tag, Iterable):
|
|
457
|
+
self.tags = list(tag)
|
|
450
458
|
else:
|
|
451
|
-
self.tags
|
|
459
|
+
self.tags = [tag,]
|
|
452
460
|
|
|
453
461
|
@property
|
|
454
462
|
def select(self) -> DomainSelection:
|
|
@@ -463,10 +471,12 @@ class GeoPoint(GeoObject):
|
|
|
463
471
|
|
|
464
472
|
def __init__(self, tag: int | list[int]):
|
|
465
473
|
super().__init__()
|
|
466
|
-
|
|
467
|
-
|
|
474
|
+
|
|
475
|
+
self.tags: list[int] = []
|
|
476
|
+
if isinstance(tag, Iterable):
|
|
477
|
+
self.tags = list(tag)
|
|
468
478
|
else:
|
|
469
|
-
self.tags
|
|
479
|
+
self.tags = [tag,]
|
|
470
480
|
|
|
471
481
|
class GeoEdge(GeoObject):
|
|
472
482
|
dim = 1
|
|
@@ -477,10 +487,11 @@ class GeoEdge(GeoObject):
|
|
|
477
487
|
|
|
478
488
|
def __init__(self, tag: int | list[int]):
|
|
479
489
|
super().__init__()
|
|
480
|
-
|
|
481
|
-
|
|
490
|
+
self.tags: list[int] = []
|
|
491
|
+
if isinstance(tag, Iterable):
|
|
492
|
+
self.tags = list(tag)
|
|
482
493
|
else:
|
|
483
|
-
self.tags
|
|
494
|
+
self.tags = [tag,]
|
|
484
495
|
|
|
485
496
|
|
|
486
497
|
class GeoSurface(GeoObject):
|
|
@@ -494,17 +505,20 @@ class GeoSurface(GeoObject):
|
|
|
494
505
|
|
|
495
506
|
def __init__(self, tag: int | list[int]):
|
|
496
507
|
super().__init__()
|
|
497
|
-
|
|
498
|
-
|
|
508
|
+
self.tags: list[int] = []
|
|
509
|
+
if isinstance(tag, Iterable):
|
|
510
|
+
self.tags = list(tag)
|
|
499
511
|
else:
|
|
500
|
-
self.tags
|
|
512
|
+
self.tags = [tag,]
|
|
501
513
|
|
|
502
514
|
class GeoPolygon(GeoSurface):
|
|
503
515
|
|
|
504
516
|
def __init__(self,
|
|
505
517
|
tags: list[int]):
|
|
506
518
|
super().__init__(tags)
|
|
507
|
-
self.points: list[int] =
|
|
508
|
-
self.lines: list[int] =
|
|
519
|
+
self.points: list[int] = []
|
|
520
|
+
self.lines: list[int] = []
|
|
509
521
|
|
|
510
522
|
|
|
523
|
+
T = TypeVar('T', GeoVolume, GeoEdge, GeoPoint, GeoSurface)
|
|
524
|
+
|
emerge/_emerge/logsettings.py
CHANGED
|
@@ -4,31 +4,32 @@ from typing import Literal
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import os
|
|
7
|
-
|
|
7
|
+
import gmsh
|
|
8
8
|
|
|
9
9
|
############################################################
|
|
10
10
|
# FORMATS #
|
|
11
11
|
############################################################
|
|
12
12
|
|
|
13
13
|
TRACE_FORMAT = (
|
|
14
|
-
"{time: YY/MM/DD
|
|
15
|
-
"
|
|
14
|
+
"{time:ddd YY/MM/DD HH:mm:ss.SSSS} {level:<7} {thread.id:<15} {line:>4}: "
|
|
15
|
+
"{message}"
|
|
16
16
|
)
|
|
17
|
+
|
|
17
18
|
DEBUG_FORMAT = (
|
|
18
|
-
"<green>{elapsed}</green>
|
|
19
|
-
"
|
|
19
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
20
|
+
"<level>{message}</level>"
|
|
20
21
|
)
|
|
21
22
|
INFO_FORMAT = (
|
|
22
|
-
"<green>{elapsed}</green>
|
|
23
|
-
"
|
|
23
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
24
|
+
"<level>{message}</level>"
|
|
24
25
|
)
|
|
25
26
|
WARNING_FORMAT = (
|
|
26
|
-
"<green>{elapsed}</green>
|
|
27
|
-
"
|
|
27
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
28
|
+
"<level>{message}</level>"
|
|
28
29
|
)
|
|
29
30
|
ERROR_FORMAT = (
|
|
30
|
-
"<green>{elapsed}</green>
|
|
31
|
-
"
|
|
31
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
32
|
+
"<level>{message}</level>"
|
|
32
33
|
)
|
|
33
34
|
FORMAT_DICT = {
|
|
34
35
|
'TRACE': TRACE_FORMAT,
|
|
@@ -51,8 +52,8 @@ class LogController:
|
|
|
51
52
|
logger.remove()
|
|
52
53
|
self.std_handlers: list[int] = []
|
|
53
54
|
self.file_handlers: list[int] = []
|
|
54
|
-
self.level:
|
|
55
|
-
self.file_level:
|
|
55
|
+
self.level: str = 'INFO'
|
|
56
|
+
self.file_level: str = 'INFO'
|
|
56
57
|
|
|
57
58
|
def set_default(self):
|
|
58
59
|
value = os.getenv("EMERGE_STD_LOGLEVEL", default="INFO")
|
|
@@ -68,13 +69,11 @@ class LogController:
|
|
|
68
69
|
handler = {"sink": sys.stdout,
|
|
69
70
|
"level": loglevel,
|
|
70
71
|
"format": FORMAT_DICT.get(loglevel, INFO_FORMAT)}
|
|
71
|
-
logger.configure(handlers=[handler])
|
|
72
|
+
logger.configure(handlers=[handler]) # type: ignore
|
|
72
73
|
self.level = loglevel
|
|
73
74
|
os.environ["EMERGE_STD_LOGLEVEL"] = loglevel
|
|
74
|
-
|
|
75
75
|
|
|
76
76
|
def set_write_file(self, path: Path, loglevel: str = 'TRACE'):
|
|
77
|
-
|
|
78
77
|
handler_id = logger.add(str(path / 'logging.log'), mode='w', level=loglevel, format=FORMAT_DICT.get(loglevel, INFO_FORMAT), colorize=False, backtrace=True, diagnose=True)
|
|
79
78
|
self.file_handlers.append(handler_id)
|
|
80
79
|
self.file_level = loglevel
|
emerge/_emerge/material.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
from dataclasses import dataclass
|
|
20
|
-
|
|
20
|
+
from typing import Callable
|
|
21
21
|
@dataclass
|
|
22
22
|
class Material:
|
|
23
23
|
"""The Material class generalizes a material in the EMerge FEM environment.
|
|
@@ -34,17 +34,21 @@ class Material:
|
|
|
34
34
|
ur: float = 1
|
|
35
35
|
tand: float = 0
|
|
36
36
|
cond: float = 0
|
|
37
|
-
_neff: float = None
|
|
38
|
-
_fer:
|
|
39
|
-
_fur:
|
|
37
|
+
_neff: float | None = None
|
|
38
|
+
_fer: Callable | None= None
|
|
39
|
+
_fur: Callable | None = None
|
|
40
40
|
color: str = "#BEBEBE"
|
|
41
|
-
_color_rgb: tuple[
|
|
41
|
+
_color_rgb: tuple[float,float,float] = (0.5, 0.5, 0.5)
|
|
42
42
|
opacity: float = 1.0
|
|
43
43
|
|
|
44
44
|
def __post_init__(self):
|
|
45
45
|
hex_str = self.color.lstrip('#')
|
|
46
46
|
self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
|
|
47
47
|
|
|
48
|
+
@property
|
|
49
|
+
def sigma(self) -> float:
|
|
50
|
+
return self.cond
|
|
51
|
+
|
|
48
52
|
@property
|
|
49
53
|
def color_rgb(self) -> tuple[float,float,float]:
|
|
50
54
|
return self._color_rgb
|
|
@@ -73,34 +77,34 @@ class Material:
|
|
|
73
77
|
return np.abs(np.sqrt(er*(1-1j*self.tand)*ur))
|
|
74
78
|
|
|
75
79
|
@property
|
|
76
|
-
def fer2d(self) ->
|
|
80
|
+
def fer2d(self) -> Callable:
|
|
77
81
|
if self._fer is None:
|
|
78
82
|
return lambda x,y: self.er*(1-1j*self.tand)*np.ones_like(x)
|
|
79
83
|
else:
|
|
80
84
|
return self._fer
|
|
81
85
|
|
|
82
86
|
@property
|
|
83
|
-
def fur2d(self) ->
|
|
87
|
+
def fur2d(self) -> Callable:
|
|
84
88
|
if self._fur is None:
|
|
85
89
|
|
|
86
90
|
return lambda x,y: self.ur*np.ones_like(x)
|
|
87
91
|
else:
|
|
88
92
|
return self._fur
|
|
89
93
|
@property
|
|
90
|
-
def fer3d(self) ->
|
|
94
|
+
def fer3d(self) -> Callable:
|
|
91
95
|
if self._fer is None:
|
|
92
96
|
return lambda x,y,z: self.er*(1-1j*self.tand)*np.ones_like(x)
|
|
93
97
|
else:
|
|
94
98
|
return self._fer
|
|
95
99
|
|
|
96
100
|
@property
|
|
97
|
-
def fur3d(self) ->
|
|
101
|
+
def fur3d(self) -> Callable:
|
|
98
102
|
if self._fur is None:
|
|
99
103
|
return lambda x,y,z: self.ur*np.ones_like(x)
|
|
100
104
|
else:
|
|
101
105
|
return self._fur
|
|
102
106
|
@property
|
|
103
|
-
def fer3d_mat(self) ->
|
|
107
|
+
def fer3d_mat(self) -> Callable:
|
|
104
108
|
if self._fer is None:
|
|
105
109
|
|
|
106
110
|
return lambda x,y,z: np.repeat(self.ermat[:, :, np.newaxis], x.shape[0], axis=2)
|
|
@@ -108,7 +112,7 @@ class Material:
|
|
|
108
112
|
return self._fer
|
|
109
113
|
|
|
110
114
|
@property
|
|
111
|
-
def fur3d_mat(self) ->
|
|
115
|
+
def fur3d_mat(self) -> Callable:
|
|
112
116
|
if self._fur is None:
|
|
113
117
|
return lambda x,y,z: np.repeat(self.urmat[:, :, np.newaxis], x.shape[0], axis=2)
|
|
114
118
|
else:
|
emerge/_emerge/mesh3d.py
CHANGED
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
|
-
import gmsh
|
|
19
|
+
import gmsh # type: ignore
|
|
20
20
|
import numpy as np
|
|
21
|
-
from numba import njit, f8
|
|
21
|
+
from numba import njit, f8 # type: ignore
|
|
22
22
|
from .mesher import Mesher
|
|
23
|
-
from typing import Union, List, Tuple, Callable
|
|
23
|
+
from typing import Union, List, Tuple, Callable, Any
|
|
24
24
|
from collections import defaultdict
|
|
25
25
|
from .geometry import GeoVolume
|
|
26
26
|
from .mth.optimized import outward_normal
|
|
@@ -75,8 +75,10 @@ def tri_ordering(i1: int, i2: int, i3: int) -> int:
|
|
|
75
75
|
'''
|
|
76
76
|
return np.sign(np.sign(i2-1) + np.sign(i3-i2) + np.sign(i1-i3))
|
|
77
77
|
|
|
78
|
+
class Mesh:
|
|
79
|
+
pass
|
|
78
80
|
|
|
79
|
-
class Mesh3D:
|
|
81
|
+
class Mesh3D(Mesh):
|
|
80
82
|
"""A Mesh managing all 3D mesh related properties.
|
|
81
83
|
|
|
82
84
|
Relevant mesh data such as mappings between nodes(vertices), edges, triangles and tetrahedra
|
|
@@ -89,54 +91,54 @@ class Mesh3D:
|
|
|
89
91
|
self.geometry: Mesher = mesher
|
|
90
92
|
|
|
91
93
|
# All spatial objects
|
|
92
|
-
self.nodes: np.ndarray =
|
|
93
|
-
self.n_i2t: dict =
|
|
94
|
-
self.n_t2i: dict =
|
|
94
|
+
self.nodes: np.ndarray = np.array([])
|
|
95
|
+
self.n_i2t: dict = dict()
|
|
96
|
+
self.n_t2i: dict = dict()
|
|
95
97
|
|
|
96
98
|
# tets colletions
|
|
97
|
-
self.tets: np.ndarray =
|
|
98
|
-
self.tet_i2t: dict =
|
|
99
|
-
self.tet_t2i: dict =
|
|
100
|
-
self.centers: np.ndarray =
|
|
99
|
+
self.tets: np.ndarray = np.array([])
|
|
100
|
+
self.tet_i2t: dict = dict()
|
|
101
|
+
self.tet_t2i: dict = dict()
|
|
102
|
+
self.centers: np.ndarray = np.array([])
|
|
101
103
|
|
|
102
104
|
# triangles
|
|
103
|
-
self.tris: np.ndarray =
|
|
104
|
-
self.tri_i2t: dict =
|
|
105
|
-
self.tri_t2i: dict =
|
|
106
|
-
self.areas: np.ndarray =
|
|
107
|
-
self.tri_centers: np.ndarray =
|
|
105
|
+
self.tris: np.ndarray = np.array([])
|
|
106
|
+
self.tri_i2t: dict = dict()
|
|
107
|
+
self.tri_t2i: dict = dict()
|
|
108
|
+
self.areas: np.ndarray = np.array([])
|
|
109
|
+
self.tri_centers: np.ndarray = np.array([])
|
|
108
110
|
|
|
109
111
|
# edges
|
|
110
|
-
self.edges: np.ndarray =
|
|
111
|
-
self.edge_i2t: dict =
|
|
112
|
-
self.edge_t2i: dict =
|
|
113
|
-
self.edge_centers: np.ndarray =
|
|
114
|
-
self.edge_lengths: np.ndarray =
|
|
112
|
+
self.edges: np.ndarray = np.array([])
|
|
113
|
+
self.edge_i2t: dict = dict()
|
|
114
|
+
self.edge_t2i: dict = dict()
|
|
115
|
+
self.edge_centers: np.ndarray = np.array([])
|
|
116
|
+
self.edge_lengths: np.ndarray = np.array([])
|
|
115
117
|
|
|
116
118
|
# Inverse mappings
|
|
117
|
-
self.inv_edges: dict =
|
|
118
|
-
self.inv_tris: dict =
|
|
119
|
-
self.inv_tets: dict =
|
|
119
|
+
self.inv_edges: dict = dict()
|
|
120
|
+
self.inv_tris: dict = dict()
|
|
121
|
+
self.inv_tets: dict = dict()
|
|
120
122
|
|
|
121
123
|
# Mappings
|
|
122
124
|
|
|
123
|
-
self.tet_to_edge: np.ndarray =
|
|
124
|
-
self.tet_to_edge_sign: np.ndarray =
|
|
125
|
-
self.tet_to_tri: np.ndarray =
|
|
126
|
-
self.tri_to_tet: np.ndarray =
|
|
127
|
-
self.tri_to_edge: np.ndarray =
|
|
128
|
-
self.tri_to_edge_sign: np.ndarray =
|
|
129
|
-
self.edge_to_tri: defaultdict =
|
|
130
|
-
self.node_to_edge: defaultdict =
|
|
125
|
+
self.tet_to_edge: np.ndarray = np.array([])
|
|
126
|
+
self.tet_to_edge_sign: np.ndarray = np.array([])
|
|
127
|
+
self.tet_to_tri: np.ndarray = np.array([])
|
|
128
|
+
self.tri_to_tet: np.ndarray = np.array([])
|
|
129
|
+
self.tri_to_edge: np.ndarray = np.array([])
|
|
130
|
+
self.tri_to_edge_sign: np.ndarray = np.array([])
|
|
131
|
+
self.edge_to_tri: defaultdict | dict = defaultdict()
|
|
132
|
+
self.node_to_edge: defaultdict | dict = defaultdict()
|
|
131
133
|
|
|
132
134
|
# Physics mappings
|
|
133
135
|
|
|
134
|
-
self.tet_to_field: np.ndarray =
|
|
135
|
-
self.edge_to_field: np.ndarray =
|
|
136
|
-
self.tri_to_field: np.ndarray =
|
|
136
|
+
self.tet_to_field: np.ndarray = np.array([])
|
|
137
|
+
self.edge_to_field: np.ndarray = np.array([])
|
|
138
|
+
self.tri_to_field: np.ndarray = np.array([])
|
|
137
139
|
|
|
138
140
|
## States
|
|
139
|
-
self.defined = False
|
|
141
|
+
self.defined: bool = False
|
|
140
142
|
|
|
141
143
|
|
|
142
144
|
## Memory
|
|
@@ -144,6 +146,8 @@ class Mesh3D:
|
|
|
144
146
|
self.ftag_to_node: dict[int, list[int]] = dict()
|
|
145
147
|
self.ftag_to_edge: dict[int, list[int]] = dict()
|
|
146
148
|
self.vtag_to_tet: dict[int, list[int]] = dict()
|
|
149
|
+
|
|
150
|
+
self.exterior_face_tags: list[int] = []
|
|
147
151
|
|
|
148
152
|
@property
|
|
149
153
|
def n_edges(self) -> int:
|
|
@@ -170,7 +174,9 @@ class Mesh3D:
|
|
|
170
174
|
if i1==i2:
|
|
171
175
|
raise ValueError("Edge cannot be formed by the same node.")
|
|
172
176
|
search = (min(int(i1),int(i2)), max(int(i1),int(i2)))
|
|
173
|
-
result = self.inv_edges.get(search,
|
|
177
|
+
result = self.inv_edges.get(search, -10)
|
|
178
|
+
if result == -10:
|
|
179
|
+
ValueError(f'There is no edge with indices {i1}, {i2}')
|
|
174
180
|
return result
|
|
175
181
|
|
|
176
182
|
def get_edge_sign(self, i1: int, i2: int) -> int:
|
|
@@ -183,13 +189,19 @@ class Mesh3D:
|
|
|
183
189
|
|
|
184
190
|
def get_tri(self, i1, i2, i3) -> int:
|
|
185
191
|
'''Return the triangle index given the three node indices'''
|
|
186
|
-
|
|
192
|
+
output = self.inv_tris.get(tuple(sorted((int(i1), int(i2), int(i3)))), None)
|
|
193
|
+
if output is None:
|
|
194
|
+
raise ValueError(f'There is no triangle with indices {i1}, {i2}, {i3}')
|
|
195
|
+
return output
|
|
187
196
|
|
|
188
197
|
def get_tet(self, i1, i2, i3, i4) -> int:
|
|
189
198
|
'''Return the tetrahedron index given the four node indices'''
|
|
190
|
-
|
|
199
|
+
output = self.inv_tets.get(tuple(sorted((int(i1), int(i2), int(i3), int(i4)))), None)
|
|
200
|
+
if output is None:
|
|
201
|
+
raise ValueError(f'There is no tetrahedron with indices {i1}, {i2}, {i3}, {i4}')
|
|
202
|
+
return output
|
|
191
203
|
|
|
192
|
-
def boundary_triangles(self, dimtags: list[tuple[int, int]] = None) -> np.ndarray:
|
|
204
|
+
def boundary_triangles(self, dimtags: list[tuple[int, int]] | None = None) -> np.ndarray:
|
|
193
205
|
if dimtags is None:
|
|
194
206
|
outputtags = []
|
|
195
207
|
for tags in self.ftag_to_tri.values():
|
|
@@ -230,7 +242,7 @@ class Mesh3D:
|
|
|
230
242
|
|
|
231
243
|
def get_face_tets(self, *taglist: list[int]) -> np.ndarray:
|
|
232
244
|
''' Return a list of a tetrahedrons that share a node with any of the nodes in the provided face.'''
|
|
233
|
-
nodes = set()
|
|
245
|
+
nodes: set = set()
|
|
234
246
|
for tags in taglist:
|
|
235
247
|
nodes.update(self.get_nodes(tags))
|
|
236
248
|
return np.array([i for i, tet in enumerate(self.tets.T) if not set(tet).isdisjoint(nodes)])
|
|
@@ -258,7 +270,7 @@ class Mesh3D:
|
|
|
258
270
|
return np.array(sorted(list(set(edges))))
|
|
259
271
|
|
|
260
272
|
|
|
261
|
-
def update(self, periodic_bcs: list[Periodic] = None):
|
|
273
|
+
def update(self, periodic_bcs: list[Periodic] | None = None):
|
|
262
274
|
if periodic_bcs is None:
|
|
263
275
|
periodic_bcs = []
|
|
264
276
|
|
|
@@ -406,8 +418,7 @@ class Mesh3D:
|
|
|
406
418
|
self.edge_centers = (self.nodes[:,self.edges[0,:]] + self.nodes[:,self.edges[1,:]]) / 2
|
|
407
419
|
self.edge_lengths = np.sqrt(np.sum((self.nodes[:,self.edges[0,:]] - self.nodes[:,self.edges[1,:]])**2, axis=0))
|
|
408
420
|
self.areas = np.array([area(self.nodes[:,self.tris[0,i]], self.nodes[:,self.tris[1,i]], self.nodes[:,self.tris[2,i]]) for i in range(self.tris.shape[1])])
|
|
409
|
-
|
|
410
|
-
|
|
421
|
+
|
|
411
422
|
## Tag bindings
|
|
412
423
|
face_dimtags = gmsh.model.get_entities(2)
|
|
413
424
|
for d,t in face_dimtags:
|
|
@@ -466,11 +477,11 @@ class Mesh3D:
|
|
|
466
477
|
all_node_ids = np.unique(np.array(node_ids_1 + node_ids_2))
|
|
467
478
|
dsmin = shortest_distance(self.nodes[:,all_node_ids])
|
|
468
479
|
|
|
469
|
-
|
|
470
|
-
|
|
480
|
+
node_ids_1_arry = np.sort(np.unique(np.array(node_ids_1)))
|
|
481
|
+
node_ids_2_arry = np.sort(np.unique(np.array(node_ids_2)))
|
|
471
482
|
dv = np.array(bc.dv)
|
|
472
483
|
|
|
473
|
-
nodemap = pair_coordinates(self.nodes,
|
|
484
|
+
nodemap = pair_coordinates(self.nodes, node_ids_1_arry, node_ids_2_arry, dv, dsmin/2)
|
|
474
485
|
node_ids_2_unsorted = [nodemap[i] for i in sorted(node_ids_1)]
|
|
475
486
|
node_ids_2_sorted = sorted(node_ids_2_unsorted)
|
|
476
487
|
conv_map = {i1: i2 for i1, i2 in zip(node_ids_2_unsorted, node_ids_2_sorted)}
|
|
@@ -499,7 +510,7 @@ class Mesh3D:
|
|
|
499
510
|
def plot_gmsh(self) -> None:
|
|
500
511
|
gmsh.fltk.run()
|
|
501
512
|
|
|
502
|
-
def find_edge_groups(self, edge_ids: np.ndarray) ->
|
|
513
|
+
def find_edge_groups(self, edge_ids: np.ndarray) -> list[tuple[Any,...]]:
|
|
503
514
|
"""
|
|
504
515
|
Find the groups of edges in the mesh.
|
|
505
516
|
|
|
@@ -568,7 +579,7 @@ class Mesh3D:
|
|
|
568
579
|
|
|
569
580
|
def boundary_surface(self,
|
|
570
581
|
face_tags: Union[int, list[int]],
|
|
571
|
-
origin: tuple[float, float, float] = None) -> SurfaceMesh:
|
|
582
|
+
origin: tuple[float, float, float] | None = None) -> SurfaceMesh:
|
|
572
583
|
"""Returns a SurfaceMesh class that is a 2D mesh isolated from the 3D mesh
|
|
573
584
|
|
|
574
585
|
The mesh will be based on the given set of face tags.
|
|
@@ -593,7 +604,7 @@ class Mesh3D:
|
|
|
593
604
|
|
|
594
605
|
return SurfaceMesh(self, tri_ids, origin)
|
|
595
606
|
|
|
596
|
-
class SurfaceMesh:
|
|
607
|
+
class SurfaceMesh(Mesh):
|
|
597
608
|
|
|
598
609
|
def __init__(self,
|
|
599
610
|
original: Mesh3D,
|
|
@@ -614,21 +625,22 @@ class SurfaceMesh:
|
|
|
614
625
|
|
|
615
626
|
self.original_tris: np.ndarray = original.tris
|
|
616
627
|
|
|
617
|
-
self.old_new_node_map: dict
|
|
628
|
+
self.old_new_node_map: dict = old_to_new_node_id_map
|
|
618
629
|
self.original: Mesh3D = original
|
|
619
630
|
self._alignment_origin: np.ndarray = np.array(origin).astype(np.float64)
|
|
620
631
|
self.nodes: np.ndarray = original.nodes[:, unique_nodes]
|
|
621
632
|
self.tris: np.ndarray = new_tris
|
|
622
633
|
|
|
623
634
|
## initialize derived
|
|
624
|
-
self.edge_centers: np.ndarray =
|
|
625
|
-
self.edge_tris: np.ndarray =
|
|
635
|
+
self.edge_centers: np.ndarray = np.array([])
|
|
636
|
+
self.edge_tris: np.ndarray = np.array([])
|
|
626
637
|
self.n_nodes = self.nodes.shape[1]
|
|
627
638
|
self.n_tris = self.tris.shape[1]
|
|
628
|
-
self.n_edges =
|
|
629
|
-
self.areas: np.ndarray =
|
|
630
|
-
self.normals: np.ndarray =
|
|
631
|
-
|
|
639
|
+
self.n_edges: float = -1
|
|
640
|
+
self.areas: np.ndarray = np.array([])
|
|
641
|
+
self.normals: np.ndarray = np.array([])
|
|
642
|
+
self.tri_to_edge: np.ndarray = np.array([])
|
|
643
|
+
self.edge_to_tri: dict | defaultdict = dict()
|
|
632
644
|
# Generate derived
|
|
633
645
|
self.update()
|
|
634
646
|
|
|
@@ -642,22 +654,26 @@ class SurfaceMesh:
|
|
|
642
654
|
self.flipY()
|
|
643
655
|
if ax.lower()=='z':
|
|
644
656
|
self.flipZ()
|
|
657
|
+
return self
|
|
645
658
|
#self.tris[(0,1),:] = self.tris[(1,0),:]
|
|
646
659
|
|
|
647
660
|
def flipX(self) -> SurfaceMesh:
|
|
648
661
|
self.nodes[0,:] = -self.nodes[0,:]
|
|
649
662
|
self.normals[0,:] = -self.normals[0,:]
|
|
650
663
|
self.edge_centers[0,:] = -self.edge_centers[0,:]
|
|
651
|
-
|
|
664
|
+
return self
|
|
665
|
+
|
|
652
666
|
def flipY(self) -> SurfaceMesh:
|
|
653
667
|
self.nodes[1,:] = -self.nodes[1,:]
|
|
654
668
|
self.normals[1,:] = -self.normals[1,:]
|
|
655
669
|
self.edge_centers[1,:] = -self.edge_centers[1,:]
|
|
670
|
+
return self
|
|
656
671
|
|
|
657
672
|
def flipZ(self) -> SurfaceMesh:
|
|
658
673
|
self.nodes[2,:] = -self.nodes[2,:]
|
|
659
674
|
self.normals[2,:] = -self.normals[2,:]
|
|
660
675
|
self.edge_centers[2,:] = -self.edge_centers[2,:]
|
|
676
|
+
return self
|
|
661
677
|
|
|
662
678
|
def from_source_tri(self, triid: int) -> int | None:
|
|
663
679
|
''' Returns a triangle index from the old mesh to the new mesh.'''
|
|
@@ -685,6 +701,8 @@ class SurfaceMesh:
|
|
|
685
701
|
raise ValueError("Edge cannot be formed by the same node.")
|
|
686
702
|
search = (min(int(i1),int(i2)), max(int(i1),int(i2)))
|
|
687
703
|
result = self.inv_edges.get(search, None)
|
|
704
|
+
if result is None:
|
|
705
|
+
raise ValueError(f'There is no edge with indices {i1}, {i2}')
|
|
688
706
|
return result
|
|
689
707
|
|
|
690
708
|
def get_edge_sign(self, i1: int, i2: int) -> int:
|
|
@@ -697,7 +715,11 @@ class SurfaceMesh:
|
|
|
697
715
|
|
|
698
716
|
def get_tri(self, i1, i2, i3) -> int:
|
|
699
717
|
'''Return the triangle index given the three node indices'''
|
|
700
|
-
|
|
718
|
+
result = self.inv_tris.get(tuple(sorted((int(i1), int(i2), int(i3)))), None)
|
|
719
|
+
if result is None:
|
|
720
|
+
raise ValueError(f'There is no triangle with indices {i1}, {i2}, {i3}')
|
|
721
|
+
return result
|
|
722
|
+
|
|
701
723
|
|
|
702
724
|
def update(self) -> None:
|
|
703
725
|
## First Edges
|