emerge 1.0.1__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.
- emerge/__init__.py +6 -7
- emerge/_emerge/elements/femdata.py +4 -3
- emerge/_emerge/elements/nedelec2.py +8 -4
- emerge/_emerge/elements/nedleg2.py +6 -2
- emerge/_emerge/geo/pcb.py +6 -2
- emerge/_emerge/geo/pcb_tools/dxf.py +4 -3
- emerge/_emerge/geo/polybased.py +2 -59
- emerge/_emerge/mesh3d.py +23 -31
- emerge/_emerge/{_cache_check.py → mth/_cache_check.py} +2 -2
- emerge/_emerge/mth/optimized.py +69 -3
- emerge/_emerge/physics/microwave/__init__.py +0 -1
- emerge/_emerge/physics/microwave/assembly/assembler.py +27 -5
- emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +2 -3
- emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -1
- emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +375 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +37 -38
- emerge/_emerge/physics/microwave/microwave_3d.py +11 -19
- emerge/_emerge/physics/microwave/microwave_bc.py +21 -17
- emerge/_emerge/physics/microwave/microwave_data.py +0 -26
- emerge/_emerge/physics/microwave/port_functions.py +4 -4
- emerge/_emerge/plot/pyvista/display.py +4 -1
- emerge/_emerge/selection.py +2 -1
- emerge/_emerge/simmodel.py +1 -1
- emerge/_emerge/solver.py +19 -14
- emerge/lib.py +0 -2
- {emerge-1.0.1.dist-info → emerge-1.0.2.dist-info}/METADATA +16 -4
- {emerge-1.0.1.dist-info → emerge-1.0.2.dist-info}/RECORD +30 -29
- {emerge-1.0.1.dist-info → emerge-1.0.2.dist-info}/WHEEL +0 -0
- {emerge-1.0.1.dist-info → emerge-1.0.2.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.1.dist-info → emerge-1.0.2.dist-info}/licenses/LICENSE +0 -0
emerge/__init__.py
CHANGED
|
@@ -18,7 +18,7 @@ along with this program; if not, see
|
|
|
18
18
|
"""
|
|
19
19
|
import os
|
|
20
20
|
|
|
21
|
-
__version__ = "1.0.
|
|
21
|
+
__version__ = "1.0.2"
|
|
22
22
|
|
|
23
23
|
############################################################
|
|
24
24
|
# HANDLE ENVIRONMENT VARIABLES #
|
|
@@ -33,11 +33,10 @@ os.environ["OPENBLAS_NUM_THREADS"] = NTHREADS
|
|
|
33
33
|
os.environ["VECLIB_MAXIMUM_THREADS"] = NTHREADS
|
|
34
34
|
os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
|
|
35
35
|
|
|
36
|
-
|
|
37
36
|
############################################################
|
|
38
37
|
# IMPORT MODULES #
|
|
39
38
|
############################################################
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
from ._emerge.logsettings import LOG_CONTROLLER
|
|
42
41
|
from loguru import logger
|
|
43
42
|
|
|
@@ -54,8 +53,7 @@ from ._emerge.coord import Line
|
|
|
54
53
|
from ._emerge import geo
|
|
55
54
|
from ._emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
56
55
|
from ._emerge.geometry import select
|
|
57
|
-
from ._emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
58
|
-
from ._emerge.physics.microwave.sc import stratton_chu
|
|
56
|
+
#from ._emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
59
57
|
from ._emerge.periodic import RectCell, HexCell
|
|
60
58
|
from ._emerge.mesher import Algorithm2D, Algorithm3D
|
|
61
59
|
from . import lib
|
|
@@ -65,10 +63,11 @@ howto = _HowtoClass()
|
|
|
65
63
|
|
|
66
64
|
logger.debug('Importing complete!')
|
|
67
65
|
|
|
68
|
-
|
|
69
66
|
############################################################
|
|
70
67
|
# CONSTANTS #
|
|
71
68
|
############################################################
|
|
72
69
|
|
|
73
70
|
CENTER = geo.Alignment.CENTER
|
|
74
|
-
CORNER = geo.Alignment.CORNER
|
|
71
|
+
CORNER = geo.Alignment.CORNER
|
|
72
|
+
EISO = lib.EISO
|
|
73
|
+
EOMNI = lib.EOMNI
|
|
@@ -19,9 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
from ..mesh3d import Mesh3D
|
|
20
20
|
import numpy as np
|
|
21
21
|
from typing import Callable
|
|
22
|
-
from scipy.sparse import csr_matrix # type: ignore
|
|
23
22
|
|
|
24
|
-
from ..mth.optimized import matmul
|
|
25
23
|
|
|
26
24
|
class FEMBasis:
|
|
27
25
|
|
|
@@ -48,6 +46,8 @@ class FEMBasis:
|
|
|
48
46
|
|
|
49
47
|
def interpolate_Ef(self, field: np.ndarray, basis: np.ndarray | None = None, origin: np.ndarray | None = None, tetids: np.ndarray | None = None) -> Callable:
|
|
50
48
|
'''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
|
|
49
|
+
from ..mth.optimized import matmul
|
|
50
|
+
|
|
51
51
|
if basis is None:
|
|
52
52
|
basis = np.eye(3)
|
|
53
53
|
|
|
@@ -124,7 +124,8 @@ class FEMBasis:
|
|
|
124
124
|
N = self.n_tri_dofs**2
|
|
125
125
|
return slice(itri*N,(itri+1)*N)
|
|
126
126
|
|
|
127
|
-
def generate_csr(self, data: np.ndarray)
|
|
127
|
+
def generate_csr(self, data: np.ndarray):
|
|
128
|
+
from scipy.sparse import csr_matrix # type: ignore
|
|
128
129
|
ids = np.argwhere(data!=0)[:,0]
|
|
129
130
|
return csr_matrix((data[ids], (self._rows[ids], self._cols[ids])), shape=(self.n_field, self.n_field))
|
|
130
131
|
### QUANTITIES
|
|
@@ -19,9 +19,6 @@ from __future__ import annotations
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
from ..mesh3d import Mesh3D
|
|
21
21
|
from .femdata import FEMBasis
|
|
22
|
-
from .ned2_interp import ned2_tet_interp, ned2_tet_interp_curl
|
|
23
|
-
from ..mth.optimized import local_mapping
|
|
24
|
-
from .index_interp import index_interp
|
|
25
22
|
|
|
26
23
|
############### Nedelec2 Class
|
|
27
24
|
|
|
@@ -72,6 +69,7 @@ class Nedelec2(FEMBasis):
|
|
|
72
69
|
'''
|
|
73
70
|
Interpolate the provided field data array at the given xs, ys and zs coordinates
|
|
74
71
|
'''
|
|
72
|
+
from .ned2_interp import ned2_tet_interp
|
|
75
73
|
if tetids is None:
|
|
76
74
|
tetids = self._all_tet_ids
|
|
77
75
|
vals = ned2_tet_interp(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, tetids)
|
|
@@ -83,8 +81,11 @@ class Nedelec2(FEMBasis):
|
|
|
83
81
|
"""
|
|
84
82
|
Interpolates the curl of the field at the given points.
|
|
85
83
|
"""
|
|
84
|
+
from .ned2_interp import ned2_tet_interp_curl
|
|
85
|
+
|
|
86
86
|
if tetids is None:
|
|
87
87
|
tetids = self._all_tet_ids
|
|
88
|
+
|
|
88
89
|
vals = ned2_tet_interp_curl(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, c, tetids)
|
|
89
90
|
if not usenan:
|
|
90
91
|
vals = np.nan_to_num(vals)
|
|
@@ -97,7 +98,7 @@ class Nedelec2(FEMBasis):
|
|
|
97
98
|
usenan: bool = True) -> np.ndarray:
|
|
98
99
|
if tetids is None:
|
|
99
100
|
tetids = self._all_tet_ids
|
|
100
|
-
|
|
101
|
+
from .index_interp import index_interp
|
|
101
102
|
vals = index_interp(np.array([xs, ys, zs]), self.mesh.tets, self.mesh.nodes, tetids)
|
|
102
103
|
if not usenan:
|
|
103
104
|
vals[vals==-1]==0
|
|
@@ -106,15 +107,18 @@ class Nedelec2(FEMBasis):
|
|
|
106
107
|
###### INDEX MAPPINGS
|
|
107
108
|
|
|
108
109
|
def local_tet_to_triid(self, itet: int) -> np.ndarray:
|
|
110
|
+
from ..mth.optimized import local_mapping
|
|
109
111
|
tri_ids = self.tet_to_field[6:10, itet] - self.n_edges
|
|
110
112
|
global_tri_map = self.mesh.tris[:, tri_ids]
|
|
111
113
|
return local_mapping(self.mesh.tets[:, itet], global_tri_map)
|
|
112
114
|
|
|
113
115
|
def local_tet_to_edgeid(self, itet: int) -> np.ndarray:
|
|
116
|
+
from ..mth.optimized import local_mapping
|
|
114
117
|
global_edge_map = self.mesh.edges[:, self.tet_to_field[:6,itet]]
|
|
115
118
|
return local_mapping(self.mesh.tets[:, itet], global_edge_map)
|
|
116
119
|
|
|
117
120
|
def local_tri_to_edgeid(self, itri: int) -> np.ndarray:
|
|
121
|
+
from ..mth.optimized import local_mapping
|
|
118
122
|
global_edge_map = self.mesh.edges[:, self.tri_to_field[:3,itri]]
|
|
119
123
|
return local_mapping(self.mesh.tris[:, itri], global_edge_map)
|
|
120
124
|
|
|
@@ -19,8 +19,6 @@ from __future__ import annotations
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
from ..mesh3d import SurfaceMesh
|
|
21
21
|
from .femdata import FEMBasis
|
|
22
|
-
from .ned2_interp import ned2_tri_interp_full, ned2_tri_interp_curl
|
|
23
|
-
from ..mth.optimized import matinv
|
|
24
22
|
from ..cs import CoordinateSystem
|
|
25
23
|
from ..const import MU0, C0
|
|
26
24
|
|
|
@@ -74,6 +72,8 @@ class FieldFunctionClass:
|
|
|
74
72
|
return np.array([Fx, Fy, Fz])*self.constant
|
|
75
73
|
|
|
76
74
|
def calcE(self, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
75
|
+
from .ned2_interp import ned2_tri_interp_full
|
|
76
|
+
|
|
77
77
|
coordinates = np.array([xs, ys])
|
|
78
78
|
vals = ned2_tri_interp_full(coordinates,
|
|
79
79
|
self.field,
|
|
@@ -85,6 +85,7 @@ class FieldFunctionClass:
|
|
|
85
85
|
return vals
|
|
86
86
|
|
|
87
87
|
def calcH(self, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
88
|
+
from .ned2_interp import ned2_tri_interp_curl
|
|
88
89
|
coordinates = np.array([xs, ys])
|
|
89
90
|
|
|
90
91
|
vals = ned2_tri_interp_curl(coordinates,
|
|
@@ -176,6 +177,7 @@ class NedelecLegrange2(FEMBasis):
|
|
|
176
177
|
|
|
177
178
|
def interpolate_Hf(self, field: np.ndarray, k0: float, ur: np.ndarray, beta: float) -> FieldFunctionClass:
|
|
178
179
|
'''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
|
|
180
|
+
from ..mth.optimized import matinv
|
|
179
181
|
constant = 1j / ((k0*C0)*MU0)
|
|
180
182
|
urinv = np.zeros_like(ur)
|
|
181
183
|
|
|
@@ -185,6 +187,7 @@ class NedelecLegrange2(FEMBasis):
|
|
|
185
187
|
return FieldFunctionClass(field, self.cs, self.local_nodes, self.mesh.tris, self.tri_to_field, 'H', urinv, beta, constant)
|
|
186
188
|
|
|
187
189
|
def tri_interpolate(self, field, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
190
|
+
from .ned2_interp import ned2_tri_interp_full
|
|
188
191
|
coordinates = np.array([xs, ys])
|
|
189
192
|
vals = ned2_tri_interp_full(coordinates,
|
|
190
193
|
field,
|
|
@@ -196,6 +199,7 @@ class NedelecLegrange2(FEMBasis):
|
|
|
196
199
|
return vals
|
|
197
200
|
|
|
198
201
|
def tri_interpolate_curl(self, field, xs: np.ndarray, ys: np.ndarray, diadic: np.ndarray | None = None, beta: float = 0.0, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
202
|
+
from .ned2_interp import ned2_tri_interp_curl
|
|
199
203
|
coordinates = np.array([xs, ys])
|
|
200
204
|
if diadic is None:
|
|
201
205
|
diadic = np.eye(3)[:,:,np.newaxis()] * np.ones((self.mesh.n_tris)) # type: ignore
|
emerge/_emerge/geo/pcb.py
CHANGED
|
@@ -958,6 +958,7 @@ class PCB:
|
|
|
958
958
|
stack: list[PCBLayer] = None,
|
|
959
959
|
name: str | None = None,
|
|
960
960
|
trace_thickness: float | None = None,
|
|
961
|
+
zs: np.ndarray | None = None
|
|
961
962
|
):
|
|
962
963
|
"""Creates a new PCB layout class instance
|
|
963
964
|
|
|
@@ -975,8 +976,11 @@ class PCB:
|
|
|
975
976
|
|
|
976
977
|
self.thickness: float = thickness
|
|
977
978
|
self._stack: list[PCBLayer] = []
|
|
978
|
-
|
|
979
|
-
|
|
979
|
+
if zs is not None:
|
|
980
|
+
self._zs = zs
|
|
981
|
+
self.thickness = np.max(zs)-np.min(zs)
|
|
982
|
+
self._stack = [PCBLayer(th, material) for th in np.diff(self._zs)]
|
|
983
|
+
elif stack is not None:
|
|
980
984
|
self._stack = stack
|
|
981
985
|
ths = [ly.th for ly in stack]
|
|
982
986
|
zbot = -sum(ths)
|
|
@@ -325,8 +325,9 @@ def import_dxf(filename: str,
|
|
|
325
325
|
unit: float | None = None,
|
|
326
326
|
cs: CoordinateSystem | None = GCS,
|
|
327
327
|
trace_material: Material = PEC) -> PCB:
|
|
328
|
+
|
|
328
329
|
polies = extract_polygons_with_meta(filename)
|
|
329
|
-
prop = inspect_pcb_from_dxf(
|
|
330
|
+
prop = inspect_pcb_from_dxf(filename)
|
|
330
331
|
|
|
331
332
|
if prop['units']['name'] == 'unitless':
|
|
332
333
|
if unit is None:
|
|
@@ -335,7 +336,7 @@ def import_dxf(filename: str,
|
|
|
335
336
|
else:
|
|
336
337
|
pcb_unit = 0.001 * prop['units']['to_mm']
|
|
337
338
|
|
|
338
|
-
if prop['thickness']==0.0:
|
|
339
|
+
if prop['thickness'] == 0.0:
|
|
339
340
|
if thickness is None:
|
|
340
341
|
raise RouteException(f'Cannot generate PCB because no thickness is found int he DXF file and none is provided in the import_dxf function.')
|
|
341
342
|
pcb_thickness = thickness
|
|
@@ -346,7 +347,7 @@ def import_dxf(filename: str,
|
|
|
346
347
|
cs = GCS
|
|
347
348
|
|
|
348
349
|
zs = sorted(list(set([pol['z'] for pol in polies])))
|
|
349
|
-
pcb = PCB(pcb_thickness, pcb_unit, cs, material=material, trace_material=trace_material
|
|
350
|
+
pcb = PCB(pcb_thickness, pcb_unit, cs, material=material, trace_material=trace_material)
|
|
350
351
|
|
|
351
352
|
for poly in polies:
|
|
352
353
|
xs, ys = zip(*poly['ring'])
|
emerge/_emerge/geo/polybased.py
CHANGED
|
@@ -24,66 +24,7 @@ from typing import Generator, Callable
|
|
|
24
24
|
from ..selection import FaceSelection
|
|
25
25
|
from typing import Literal
|
|
26
26
|
from functools import reduce
|
|
27
|
-
from numba import njit
|
|
28
27
|
|
|
29
|
-
@njit(cache=True)
|
|
30
|
-
def _subsample_coordinates(xs: np.ndarray, ys: np.ndarray, tolerance: float, xmin: float) -> tuple[np.ndarray, np.ndarray]:
|
|
31
|
-
"""This function takes a set of x and y coordinates in a finely sampled set and returns a reduced
|
|
32
|
-
set of numbers that traces the input curve within a provided tolerance.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
xs (np.ndarray): The set of X-coordinates
|
|
36
|
-
ys (np.ndarray): The set of Y-coordinates
|
|
37
|
-
tolerance (float): The maximum deviation of the curve in meters
|
|
38
|
-
xmin (float): The minimal distance to the next point.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
np.ndarray: The output X-coordinates
|
|
42
|
-
np.ndarray: The output Y-coordinates
|
|
43
|
-
"""
|
|
44
|
-
N = xs.shape[0]
|
|
45
|
-
ids = np.zeros((N,), dtype=np.int32)
|
|
46
|
-
store_index = 1
|
|
47
|
-
start_index = 0
|
|
48
|
-
final_index = 0
|
|
49
|
-
for iteration in range(N):
|
|
50
|
-
i1 = start_index
|
|
51
|
-
done = 0
|
|
52
|
-
for i2 in range(i1+1,N):
|
|
53
|
-
x_true = xs[i1:i2+1]
|
|
54
|
-
y_true = ys[i1:i2+1]
|
|
55
|
-
|
|
56
|
-
x_f = np.linspace(xs[i1],xs[i2], i2-i1+1)
|
|
57
|
-
y_f = np.linspace(ys[i1],ys[i2], i2-i1+1)
|
|
58
|
-
error = np.max(np.sqrt((x_f-x_true)**2 + (y_f-y_true)**2))
|
|
59
|
-
ds = np.sqrt((xs[i2]-xs[i1])**2 + (ys[i2]-ys[i1])**2)
|
|
60
|
-
# If at the end
|
|
61
|
-
if i2==N-1:
|
|
62
|
-
ids[store_index] = i2-1
|
|
63
|
-
final_index = store_index + 1
|
|
64
|
-
done = 1
|
|
65
|
-
break
|
|
66
|
-
# If not yet past the minimum distance, accumulate more
|
|
67
|
-
if ds < xmin:
|
|
68
|
-
continue
|
|
69
|
-
# If the end is less than a minimum distance
|
|
70
|
-
if np.sqrt((ys[-1]-ys[i2])**2 + (xs[-1]-xs[i2])**2) < xmin:
|
|
71
|
-
imid = i1 + (N-1-i1)//2
|
|
72
|
-
ids[store_index] = imid
|
|
73
|
-
ids[store_index+1] = N-1
|
|
74
|
-
final_index = store_index + 2
|
|
75
|
-
done = 1
|
|
76
|
-
break
|
|
77
|
-
if error < tolerance:
|
|
78
|
-
continue
|
|
79
|
-
else:
|
|
80
|
-
ids[store_index] = i2-1
|
|
81
|
-
start_index = i2
|
|
82
|
-
store_index = store_index + 1
|
|
83
|
-
break
|
|
84
|
-
if done==1:
|
|
85
|
-
break
|
|
86
|
-
return xs[ids[0:final_index]], ys[ids[0:final_index]]
|
|
87
28
|
|
|
88
29
|
def _discretize_curve(xfunc: Callable, yfunc: Callable,
|
|
89
30
|
t0: float, t1: float, xmin: float, tol: float=1e-4) -> tuple[np.ndarray, np.ndarray]:
|
|
@@ -100,6 +41,8 @@ def _discretize_curve(xfunc: Callable, yfunc: Callable,
|
|
|
100
41
|
Returns:
|
|
101
42
|
tuple[np.ndarray, np.ndarray]: _description_
|
|
102
43
|
"""
|
|
44
|
+
from ..mth.optimized import _subsample_coordinates
|
|
45
|
+
|
|
103
46
|
td = np.linspace(t0, t1, 10001)
|
|
104
47
|
xs = xfunc(td)
|
|
105
48
|
ys = yfunc(td)
|
emerge/_emerge/mesh3d.py
CHANGED
|
@@ -18,25 +18,14 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
import gmsh # type: ignore
|
|
20
20
|
import numpy as np
|
|
21
|
-
from numba import njit, f8 # type: ignore
|
|
22
21
|
from .mesher import Mesher
|
|
23
22
|
from typing import Union, List, Tuple, Callable, Any
|
|
24
23
|
from collections import defaultdict
|
|
25
24
|
from .geometry import GeoVolume
|
|
26
|
-
from .mth.optimized import outward_normal
|
|
27
25
|
from loguru import logger
|
|
28
|
-
from functools import cache
|
|
29
26
|
from .bc import Periodic
|
|
30
|
-
from .mth.pairing import pair_coordinates
|
|
31
27
|
from .material import Material
|
|
32
28
|
|
|
33
|
-
@njit(f8(f8[:], f8[:], f8[:]), cache=True, nogil=True)
|
|
34
|
-
def area(x1: np.ndarray, x2: np.ndarray, x3: np.ndarray):
|
|
35
|
-
e1 = x2 - x1
|
|
36
|
-
e2 = x3 - x1
|
|
37
|
-
av = np.array([e1[1]*e2[2] - e1[2]*e2[1], e1[2]*e2[0] - e1[0]*e2[2], e1[0]*e2[1] - e1[1]*e2[0]])
|
|
38
|
-
return np.sqrt(av[0]**2 + av[1]**2 + av[2]**2)/2
|
|
39
|
-
|
|
40
29
|
def shortest_distance(point_cloud):
|
|
41
30
|
"""
|
|
42
31
|
Compute the shortest distance between any two points in a 3D point cloud.
|
|
@@ -172,7 +161,7 @@ class Mesh3D(Mesh):
|
|
|
172
161
|
def n_nodes(self) -> int:
|
|
173
162
|
'''Return the number of nodes'''
|
|
174
163
|
return self.nodes.shape[1]
|
|
175
|
-
|
|
164
|
+
|
|
176
165
|
def get_edge(self, i1: int, i2: int, skip: bool = False) -> int:
|
|
177
166
|
'''Return the edge index given the two node indices'''
|
|
178
167
|
if i1==i2:
|
|
@@ -204,21 +193,6 @@ class Mesh3D(Mesh):
|
|
|
204
193
|
if output is None:
|
|
205
194
|
raise ValueError(f'There is no tetrahedron with indices {i1}, {i2}, {i3}, {i4}')
|
|
206
195
|
return output
|
|
207
|
-
|
|
208
|
-
def boundary_triangles(self, dimtags: list[tuple[int, int]] | None = None) -> np.ndarray:
|
|
209
|
-
if dimtags is None:
|
|
210
|
-
outputtags = []
|
|
211
|
-
for tags in self.ftag_to_tri.values():
|
|
212
|
-
outputtags.extend(tags)
|
|
213
|
-
return np.array(outputtags)
|
|
214
|
-
else:
|
|
215
|
-
dts = []
|
|
216
|
-
for dimtag in dimtags:
|
|
217
|
-
if dimtag[0]==2:
|
|
218
|
-
dts.append(dimtag)
|
|
219
|
-
elif dimtag[0]==3:
|
|
220
|
-
dts.extend(gmsh.model.get_boundary(dimtags))
|
|
221
|
-
return self.get_triangles([tag[1] for tag in dts])
|
|
222
196
|
|
|
223
197
|
|
|
224
198
|
def get_tetrahedra(self, vol_tags: Union[int, list[int]]) -> np.ndarray:
|
|
@@ -317,7 +291,16 @@ class Mesh3D(Mesh):
|
|
|
317
291
|
return np.array(sorted(list(set(edges))))
|
|
318
292
|
|
|
319
293
|
|
|
320
|
-
def
|
|
294
|
+
def _pre_update(self, periodic_bcs: list[Periodic] | None = None):
|
|
295
|
+
"""Builds the mesh data properties
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
periodic_bcs (list[Periodic] | None, optional): A list of periodic boundary conditions. Defaults to None.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
None: None
|
|
302
|
+
"""
|
|
303
|
+
from .mth.optimized import area
|
|
321
304
|
|
|
322
305
|
logger.trace('Generating mesh data.')
|
|
323
306
|
if periodic_bcs is None:
|
|
@@ -357,7 +340,7 @@ class Mesh3D(Mesh):
|
|
|
357
340
|
|
|
358
341
|
for bc in periodic_bcs:
|
|
359
342
|
logger.trace(f'reassigning ordered node numbers for periodic boundary {bc}')
|
|
360
|
-
nodemap, ids1, ids2 = self.
|
|
343
|
+
nodemap, ids1, ids2 = self._pre_derive_node_map(bc)
|
|
361
344
|
nodemap = {int(a): int(b) for a,b in nodemap.items()}
|
|
362
345
|
self.nodes[:,ids2] = self.nodes[:,ids1]
|
|
363
346
|
for itet in range(self.tets.shape[1]):
|
|
@@ -508,6 +491,11 @@ class Mesh3D(Mesh):
|
|
|
508
491
|
|
|
509
492
|
self.defined = True
|
|
510
493
|
|
|
494
|
+
|
|
495
|
+
############################################################
|
|
496
|
+
# GMSH CACHE #
|
|
497
|
+
############################################################
|
|
498
|
+
|
|
511
499
|
for dim in (0,1,2,3):
|
|
512
500
|
dts= gmsh.model.get_entities(dim)
|
|
513
501
|
for dt in dts:
|
|
@@ -519,7 +507,7 @@ class Mesh3D(Mesh):
|
|
|
519
507
|
|
|
520
508
|
## Higher order functions
|
|
521
509
|
|
|
522
|
-
def
|
|
510
|
+
def _pre_derive_node_map(self, bc: Periodic) -> tuple[dict[int, int], np.ndarray, np.ndarray]:
|
|
523
511
|
"""Computes an old to new node index mapping that preserves global sorting
|
|
524
512
|
|
|
525
513
|
Since basis function field direction is based on the order of indices in tetrahedron
|
|
@@ -534,6 +522,8 @@ class Mesh3D(Mesh):
|
|
|
534
522
|
tuple[dict[int, int], np.ndarray, np.ndarray]: The node index mapping and the node index arrays
|
|
535
523
|
"""
|
|
536
524
|
|
|
525
|
+
from .mth.pairing import pair_coordinates
|
|
526
|
+
|
|
537
527
|
node_ids_1 = []
|
|
538
528
|
node_ids_2 = []
|
|
539
529
|
|
|
@@ -566,7 +556,7 @@ class Mesh3D(Mesh):
|
|
|
566
556
|
return conv_map, np.array(node_ids_2_unsorted), np.array(node_ids_2_sorted)
|
|
567
557
|
|
|
568
558
|
|
|
569
|
-
def
|
|
559
|
+
def _get_material_assignment(self, volumes: list[GeoVolume]) -> list[Material]:
|
|
570
560
|
'''Retrieve the material properties of the geometry'''
|
|
571
561
|
#arry = np.zeros((3,3,self.n_tets,), dtype=np.complex128)
|
|
572
562
|
for vol in volumes:
|
|
@@ -818,6 +808,8 @@ class SurfaceMesh(Mesh):
|
|
|
818
808
|
def update(self) -> None:
|
|
819
809
|
## First Edges
|
|
820
810
|
|
|
811
|
+
from .mth.optimized import outward_normal, area
|
|
812
|
+
|
|
821
813
|
edges = set()
|
|
822
814
|
for i in range(self.n_tris):
|
|
823
815
|
i1, i2, i3 = self.tris[:,i]
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
# along with this program; if not, see
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
-
from numba.core import event
|
|
18
|
+
from numba.core import event
|
|
19
19
|
from numba import njit
|
|
20
20
|
|
|
21
21
|
_COMPILE_MESSAGE = """
|
|
22
22
|
[ EMERGE ]
|
|
23
|
-
⚠ Numba
|
|
23
|
+
⚠ Numba will be compiling optimized code in this first run; this may take a few minutes.
|
|
24
24
|
• Additional functions may be compiled on-the-fly.
|
|
25
25
|
• Compilation happens only once-subsequent runs load from cache.
|
|
26
26
|
Please wait…"""
|
emerge/_emerge/mth/optimized.py
CHANGED
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
from numba import njit, f8, i8, types, c16
|
|
19
19
|
import numpy as np
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
from . import _cache_check
|
|
22
21
|
|
|
23
22
|
############################################################
|
|
24
23
|
# TRIANGLE GAUSS QUADRATURE POINTS #
|
|
@@ -446,4 +445,71 @@ def matmul(M: np.ndarray, vecs: np.ndarray):
|
|
|
446
445
|
out[0,:] = M[0,0]*vecs[0,:] + M[0,1]*vecs[1,:] + M[0,2]*vecs[2,:]
|
|
447
446
|
out[1,:] = M[1,0]*vecs[0,:] + M[1,1]*vecs[1,:] + M[1,2]*vecs[2,:]
|
|
448
447
|
out[2,:] = M[2,0]*vecs[0,:] + M[2,1]*vecs[1,:] + M[2,2]*vecs[2,:]
|
|
449
|
-
return out
|
|
448
|
+
return out
|
|
449
|
+
|
|
450
|
+
@njit(cache=True)
|
|
451
|
+
def _subsample_coordinates(xs: np.ndarray, ys: np.ndarray, tolerance: float, xmin: float) -> tuple[np.ndarray, np.ndarray]:
|
|
452
|
+
"""This function takes a set of x and y coordinates in a finely sampled set and returns a reduced
|
|
453
|
+
set of numbers that traces the input curve within a provided tolerance.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
xs (np.ndarray): The set of X-coordinates
|
|
457
|
+
ys (np.ndarray): The set of Y-coordinates
|
|
458
|
+
tolerance (float): The maximum deviation of the curve in meters
|
|
459
|
+
xmin (float): The minimal distance to the next point.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
np.ndarray: The output X-coordinates
|
|
463
|
+
np.ndarray: The output Y-coordinates
|
|
464
|
+
"""
|
|
465
|
+
N = xs.shape[0]
|
|
466
|
+
ids = np.zeros((N,), dtype=np.int32)
|
|
467
|
+
store_index = 1
|
|
468
|
+
start_index = 0
|
|
469
|
+
final_index = 0
|
|
470
|
+
for iteration in range(N):
|
|
471
|
+
i1 = start_index
|
|
472
|
+
done = 0
|
|
473
|
+
for i2 in range(i1+1,N):
|
|
474
|
+
x_true = xs[i1:i2+1]
|
|
475
|
+
y_true = ys[i1:i2+1]
|
|
476
|
+
|
|
477
|
+
x_f = np.linspace(xs[i1],xs[i2], i2-i1+1)
|
|
478
|
+
y_f = np.linspace(ys[i1],ys[i2], i2-i1+1)
|
|
479
|
+
error = np.max(np.sqrt((x_f-x_true)**2 + (y_f-y_true)**2))
|
|
480
|
+
ds = np.sqrt((xs[i2]-xs[i1])**2 + (ys[i2]-ys[i1])**2)
|
|
481
|
+
# If at the end
|
|
482
|
+
if i2==N-1:
|
|
483
|
+
ids[store_index] = i2-1
|
|
484
|
+
final_index = store_index + 1
|
|
485
|
+
done = 1
|
|
486
|
+
break
|
|
487
|
+
# If not yet past the minimum distance, accumulate more
|
|
488
|
+
if ds < xmin:
|
|
489
|
+
continue
|
|
490
|
+
# If the end is less than a minimum distance
|
|
491
|
+
if np.sqrt((ys[-1]-ys[i2])**2 + (xs[-1]-xs[i2])**2) < xmin:
|
|
492
|
+
imid = i1 + (N-1-i1)//2
|
|
493
|
+
ids[store_index] = imid
|
|
494
|
+
ids[store_index+1] = N-1
|
|
495
|
+
final_index = store_index + 2
|
|
496
|
+
done = 1
|
|
497
|
+
break
|
|
498
|
+
if error < tolerance:
|
|
499
|
+
continue
|
|
500
|
+
else:
|
|
501
|
+
ids[store_index] = i2-1
|
|
502
|
+
start_index = i2
|
|
503
|
+
store_index = store_index + 1
|
|
504
|
+
break
|
|
505
|
+
if done==1:
|
|
506
|
+
break
|
|
507
|
+
return xs[ids[0:final_index]], ys[ids[0:final_index]]
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@njit(f8(f8[:], f8[:], f8[:]), cache=True, nogil=True)
|
|
511
|
+
def area(x1: np.ndarray, x2: np.ndarray, x3: np.ndarray):
|
|
512
|
+
e1 = x2 - x1
|
|
513
|
+
e2 = x3 - x1
|
|
514
|
+
av = np.array([e1[1]*e2[2] - e1[2]*e2[1], e1[2]*e2[0] - e1[0]*e2[2], e1[0]*e2[1] - e1[1]*e2[0]])
|
|
515
|
+
return np.sqrt(av[0]**2 + av[1]**2 + av[2]**2)/2
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .sc import stratton_chu
|
|
@@ -19,14 +19,12 @@ import numpy as np
|
|
|
19
19
|
from ..microwave_bc import PEC, BoundaryCondition, RectangularWaveguide, RobinBC, PortBC, Periodic, MWBoundaryConditionSet
|
|
20
20
|
from ....elements.nedelec2 import Nedelec2
|
|
21
21
|
from ....elements.nedleg2 import NedelecLegrange2
|
|
22
|
-
from ....mth.optimized import gaus_quad_tri
|
|
23
|
-
from ....mth.pairing import pair_coordinates
|
|
24
22
|
from ....material import Material
|
|
25
23
|
from ....settings import Settings
|
|
26
24
|
from scipy.sparse import csr_matrix
|
|
27
25
|
from loguru import logger
|
|
28
26
|
from ..simjob import SimJob
|
|
29
|
-
|
|
27
|
+
|
|
30
28
|
from ....const import MU0, EPS0, C0
|
|
31
29
|
|
|
32
30
|
|
|
@@ -209,7 +207,11 @@ class Assembler:
|
|
|
209
207
|
|
|
210
208
|
from .curlcurl import tet_mass_stiffness_matrices
|
|
211
209
|
from .robinbc import assemble_robin_bc, assemble_robin_bc_excited
|
|
212
|
-
|
|
210
|
+
from ....mth.optimized import gaus_quad_tri
|
|
211
|
+
from ....mth.pairing import pair_coordinates
|
|
212
|
+
from .periodicbc import gen_periodic_matrix
|
|
213
|
+
from .robin_abc_order2 import abc_order_2_matrix
|
|
214
|
+
|
|
213
215
|
# PREDEFINE CONSTANTS
|
|
214
216
|
W0 = 2*np.pi*frequency
|
|
215
217
|
K0 = W0/C0
|
|
@@ -326,6 +328,15 @@ class Assembler:
|
|
|
326
328
|
logger.trace(f'..included force vector term with norm {np.linalg.norm(b_p):.3f}')
|
|
327
329
|
else:
|
|
328
330
|
Bempty = assemble_robin_bc(field, Bempty, tri_ids, gamma) # type: ignore
|
|
331
|
+
|
|
332
|
+
## Second order absorbing boundary correction
|
|
333
|
+
if bc._isabc:
|
|
334
|
+
if bc.order==2:
|
|
335
|
+
c2 = bc.o2coeffs[bc.abctype][1]
|
|
336
|
+
logger.debug('Implementing second order ABC correction.')
|
|
337
|
+
mat = abc_order_2_matrix(field, tri_ids, 1j*c2/(K0))
|
|
338
|
+
Bempty += mat
|
|
339
|
+
|
|
329
340
|
B_p = field.generate_csr(Bempty)
|
|
330
341
|
K = K + B_p
|
|
331
342
|
|
|
@@ -430,7 +441,10 @@ class Assembler:
|
|
|
430
441
|
"""
|
|
431
442
|
from .curlcurl import tet_mass_stiffness_matrices
|
|
432
443
|
from .robinbc import assemble_robin_bc
|
|
433
|
-
|
|
444
|
+
from ....mth.pairing import pair_coordinates
|
|
445
|
+
from .periodicbc import gen_periodic_matrix
|
|
446
|
+
from .robin_abc_order2 import abc_order_2_matrix
|
|
447
|
+
|
|
434
448
|
mesh = field.mesh
|
|
435
449
|
w0 = 2*np.pi*frequency
|
|
436
450
|
k0 = w0/C0
|
|
@@ -507,6 +521,14 @@ class Assembler:
|
|
|
507
521
|
ibasis = np.linalg.pinv(basis)
|
|
508
522
|
|
|
509
523
|
Bempty = assemble_robin_bc(field, Bempty, tri_ids, gamma) # type: ignore
|
|
524
|
+
|
|
525
|
+
## Second order absorbing boundary correction
|
|
526
|
+
if bc._isabc:
|
|
527
|
+
if bc.order==2:
|
|
528
|
+
c2 = bc.o2coeffs[bc.abctype][1]
|
|
529
|
+
logger.debug('Implementing second order ABC correction.')
|
|
530
|
+
mat = abc_order_2_matrix(field, tri_ids, 1j*c2/k0)
|
|
531
|
+
Bempty += mat
|
|
510
532
|
B_p = field.generate_csr(Bempty)
|
|
511
533
|
B = B + B_p
|
|
512
534
|
|
|
@@ -226,7 +226,6 @@ def tri_coefficients(vxs, vys):
|
|
|
226
226
|
c2 = x1-x3
|
|
227
227
|
c3 = x2-x1
|
|
228
228
|
|
|
229
|
-
#A = 0.5*(b1*c2 - b2*c1)
|
|
230
229
|
sA = 0.5*(((x1-x3)*(y2-y1) - (x1-x2)*(y3-y1)))
|
|
231
230
|
sign = np.sign(sA)
|
|
232
231
|
A = np.abs(sA)
|
|
@@ -444,11 +443,11 @@ def _matrix_builder(nodes, tris, edges, tri_to_field, ur, er, k0):
|
|
|
444
443
|
ert = er[:,:,itri]
|
|
445
444
|
|
|
446
445
|
# Construct a local mapping to global triangle orientations
|
|
447
|
-
|
|
446
|
+
local_edge_map = local_tri_to_edgeid(itri, tris, edges, tri_to_edge)
|
|
448
447
|
|
|
449
448
|
# Construct the local edge map
|
|
450
449
|
tri_nodes = nodes[:, tris[:,itri]]
|
|
451
|
-
Esub, Bsub = generalized_matrix_GQ(tri_nodes,
|
|
450
|
+
Esub, Bsub = generalized_matrix_GQ(tri_nodes,local_edge_map, matinv(urt), ert, k0)
|
|
452
451
|
|
|
453
452
|
indices = tri_to_field[:, itri]
|
|
454
453
|
for ii in range(14):
|