emerge 1.0.0__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 +7 -8
- emerge/_emerge/elements/femdata.py +4 -3
- emerge/_emerge/elements/nedelec2.py +8 -4
- emerge/_emerge/elements/nedleg2.py +6 -2
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/pcb.py +149 -66
- emerge/_emerge/geo/pcb_tools/dxf.py +361 -0
- emerge/_emerge/geo/polybased.py +23 -74
- emerge/_emerge/geo/shapes.py +31 -16
- emerge/_emerge/geometry.py +120 -21
- emerge/_emerge/mesh3d.py +62 -43
- emerge/_emerge/{_cache_check.py → mth/_cache_check.py} +2 -2
- emerge/_emerge/mth/optimized.py +69 -3
- emerge/_emerge/periodic.py +19 -17
- 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 +38 -21
- emerge/_emerge/physics/microwave/microwave_data.py +3 -26
- emerge/_emerge/physics/microwave/port_functions.py +4 -4
- emerge/_emerge/plot/pyvista/display.py +13 -2
- emerge/_emerge/plot/simple_plots.py +4 -1
- emerge/_emerge/selection.py +12 -9
- emerge/_emerge/simmodel.py +68 -34
- emerge/_emerge/solver.py +28 -16
- emerge/beta/dxf.py +1 -0
- emerge/lib.py +1 -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.2.dist-info}/METADATA +18 -4
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/RECORD +39 -33
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/WHEEL +0 -0
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
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
|
|
@@ -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):
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# EMerge is an open source Python based FEM EM simulation module.
|
|
2
|
+
# Copyright (C) 2025 Robert Fennis.
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU General Public License
|
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
|
7
|
+
# of the License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, see
|
|
16
|
+
# <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from ....elements import Nedelec2
|
|
21
|
+
from scipy.sparse import csr_matrix
|
|
22
|
+
from ....mth.optimized import local_mapping, compute_distances
|
|
23
|
+
from numba import c16, types, f8, i8, njit, prange
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
############################################################
|
|
27
|
+
# FIELD MAPPING #
|
|
28
|
+
############################################################
|
|
29
|
+
|
|
30
|
+
@njit(i8[:,:](i8, i8[:,:], i8[:,:], i8[:,:]), cache=True, nogil=True)
|
|
31
|
+
def local_tri_to_edgeid(itri: int, tris, edges, tri_to_edge) -> np.ndarray:
|
|
32
|
+
global_edge_map = edges[:, tri_to_edge[:,itri]]
|
|
33
|
+
return local_mapping(tris[:, itri], global_edge_map)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@njit(cache=True, fastmath=True, nogil=True)
|
|
37
|
+
def optim_matmul(B: np.ndarray, data: np.ndarray):
|
|
38
|
+
dnew = np.zeros_like(data)
|
|
39
|
+
dnew[0,:] = B[0,0]*data[0,:] + B[0,1]*data[1,:] + B[0,2]*data[2,:]
|
|
40
|
+
dnew[1,:] = B[1,0]*data[0,:] + B[1,1]*data[1,:] + B[1,2]*data[2,:]
|
|
41
|
+
dnew[2,:] = B[2,0]*data[0,:] + B[2,1]*data[1,:] + B[2,2]*data[2,:]
|
|
42
|
+
return dnew
|
|
43
|
+
|
|
44
|
+
@njit(f8[:](f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
|
|
45
|
+
def cross(a: np.ndarray, b: np.ndarray):
|
|
46
|
+
crossv = np.empty((3,), dtype=np.float64)
|
|
47
|
+
crossv[0] = a[1]*b[2] - a[2]*b[1]
|
|
48
|
+
crossv[1] = a[2]*b[0] - a[0]*b[2]
|
|
49
|
+
crossv[2] = a[0]*b[1] - a[1]*b[0]
|
|
50
|
+
return crossv
|
|
51
|
+
|
|
52
|
+
@njit(cache=True, nogil=True)
|
|
53
|
+
def normalize(a: np.ndarray):
|
|
54
|
+
return a/((a[0]**2 + a[1]**2 + a[2]**2)**0.5)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
############################################################
|
|
58
|
+
# GAUSS QUADRATURE IMPLEMENTATION #
|
|
59
|
+
############################################################
|
|
60
|
+
|
|
61
|
+
@njit(c16(c16[:], c16[:], types.Array(types.float64, 1, 'A', readonly=True)), cache=True, nogil=True)
|
|
62
|
+
def _gqi(v1, v2, W):
|
|
63
|
+
return np.sum(v1*v2*W)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
############################################################
|
|
67
|
+
# BASIS FUNCTION DERIVATIVES #
|
|
68
|
+
############################################################
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
72
|
+
def _curl_edge_1(coeff, coords):
|
|
73
|
+
a1, b1, c1 = coeff[:,0]
|
|
74
|
+
a2, b2, c2 = coeff[:,1]
|
|
75
|
+
xs = coords[0,:]
|
|
76
|
+
ys = coords[1,:]
|
|
77
|
+
out = -3*a1*b1*c2 + 3*a1*b2*c1 - 3*b1**2*c2*xs + 3*b1*b2*c1*xs - 3*b1*c1*c2*ys + 3*b2*c1**2*ys + 0j
|
|
78
|
+
return out
|
|
79
|
+
|
|
80
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
81
|
+
def _curl_edge_2(coeff, coords):
|
|
82
|
+
a1, b1, c1 = coeff[:,0]
|
|
83
|
+
a2, b2, c2 = coeff[:,1]
|
|
84
|
+
xs = coords[0,:]
|
|
85
|
+
ys = coords[1,:]
|
|
86
|
+
out = -3*a2*b1*c2 + 3*a2*b2*c1 - 3*b1*b2*c2*xs - 3*b1*c2**2*ys + 3*b2**2*c1*xs + 3*b2*c1*c2*ys + 0j
|
|
87
|
+
return out
|
|
88
|
+
|
|
89
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
90
|
+
def _curl_face_1(coeff, coords):
|
|
91
|
+
a1, b1, c1 = coeff[:,0]
|
|
92
|
+
a2, b2, c2 = coeff[:,1]
|
|
93
|
+
a3, b3, c3 = coeff[:,2]
|
|
94
|
+
xs = coords[0,:]
|
|
95
|
+
ys = coords[1,:]
|
|
96
|
+
out = -b2*(c1*(a3 + b3*xs + c3*ys) - c3*(a1 + b1*xs + c1*ys)) + c2*(b1*(a3 + b3*xs + c3*ys) - b3*(a1 + b1*xs + c1*ys)) + 2*(b1*c3 - b3*c1)*(a2 + b2*xs + c2*ys) + 0j
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
100
|
+
def _curl_face_2(coeff, coords):
|
|
101
|
+
a1, b1, c1 = coeff[:,0]
|
|
102
|
+
a2, b2, c2 = coeff[:,1]
|
|
103
|
+
a3, b3, c3 = coeff[:,2]
|
|
104
|
+
xs = coords[0,:]
|
|
105
|
+
ys = coords[1,:]
|
|
106
|
+
out = b3*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) - c3*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) - 2*(b1*c2 - b2*c1)*(a3 + b3*xs + c3*ys) + 0j
|
|
107
|
+
return out
|
|
108
|
+
|
|
109
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
110
|
+
def _divergence_edge_1(coeff, coords):
|
|
111
|
+
a1, b1, c1 = coeff[:,0]
|
|
112
|
+
a2, b2, c2 = coeff[:,1]
|
|
113
|
+
xs = coords[0,:]
|
|
114
|
+
ys = coords[1,:]
|
|
115
|
+
out = b1*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) + c1*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) + 0j
|
|
116
|
+
return out
|
|
117
|
+
|
|
118
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
119
|
+
def _divergence_edge_2(coeff, coords):
|
|
120
|
+
a1, b1, c1 = coeff[:,0]
|
|
121
|
+
a2, b2, c2 = coeff[:,1]
|
|
122
|
+
xs = coords[0,:]
|
|
123
|
+
ys = coords[1,:]
|
|
124
|
+
out = b2*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) + c2*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) + 0j
|
|
125
|
+
return out
|
|
126
|
+
|
|
127
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
128
|
+
def _divergence_face_1(coeff, coords):
|
|
129
|
+
a1, b1, c1 = coeff[:,0]
|
|
130
|
+
a2, b2, c2 = coeff[:,1]
|
|
131
|
+
a3, b3, c3 = coeff[:,2]
|
|
132
|
+
xs = coords[0,:]
|
|
133
|
+
ys = coords[1,:]
|
|
134
|
+
out = -b2*(b1*(a3 + b3*xs + c3*ys) - b3*(a1 + b1*xs + c1*ys)) - c2*(c1*(a3 + b3*xs + c3*ys) - c3*(a1 + b1*xs + c1*ys)) + 0j
|
|
135
|
+
return out
|
|
136
|
+
|
|
137
|
+
@njit(c16[:](f8[:,:], f8[:,:]), cache=True)
|
|
138
|
+
def _divergence_face_2(coeff, coords):
|
|
139
|
+
a1, b1, c1 = coeff[:,0]
|
|
140
|
+
a2, b2, c2 = coeff[:,1]
|
|
141
|
+
a3, b3, c3 = coeff[:,2]
|
|
142
|
+
xs = coords[0,:]
|
|
143
|
+
ys = coords[1,:]
|
|
144
|
+
out = b3*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) + c3*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) + 0j
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
############################################################
|
|
148
|
+
# TRIANGLE BARYCENTRIC COORDINATE LIN. COEFFICIENTS #
|
|
149
|
+
############################################################
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@njit(types.Tuple((f8[:], f8[:], f8[:], f8))(f8[:], f8[:]), cache = True, nogil=True)
|
|
153
|
+
def tri_coefficients(vxs, vys):
|
|
154
|
+
|
|
155
|
+
x1, x2, x3 = vxs
|
|
156
|
+
y1, y2, y3 = vys
|
|
157
|
+
|
|
158
|
+
a1 = x2*y3-y2*x3
|
|
159
|
+
a2 = x3*y1-y3*x1
|
|
160
|
+
a3 = x1*y2-y1*x2
|
|
161
|
+
b1 = y2-y3
|
|
162
|
+
b2 = y3-y1
|
|
163
|
+
b3 = y1-y2
|
|
164
|
+
c1 = x3-x2
|
|
165
|
+
c2 = x1-x3
|
|
166
|
+
c3 = x2-x1
|
|
167
|
+
|
|
168
|
+
sA = 0.5*(((x1-x3)*(y2-y1) - (x1-x2)*(y3-y1)))
|
|
169
|
+
sign = np.sign(sA)
|
|
170
|
+
A = np.abs(sA)
|
|
171
|
+
As = np.array([a1, a2, a3])*sign
|
|
172
|
+
Bs = np.array([b1, b2, b3])*sign
|
|
173
|
+
Cs = np.array([c1, c2, c3])*sign
|
|
174
|
+
return As, Bs, Cs, A
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
############################################################
|
|
178
|
+
# GAUSS QUADRATURE POINTS #
|
|
179
|
+
############################################################
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
DPTS = np.array([[0.22338159, 0.22338159, 0.22338159, 0.10995174, 0.10995174, 0.10995174],
|
|
183
|
+
[0.10810302, 0.44594849, 0.44594849, 0.81684757, 0.09157621, 0.09157621],
|
|
184
|
+
[0.44594849, 0.44594849, 0.10810302, 0.09157621, 0.09157621, 0.81684757],
|
|
185
|
+
[0.44594849, 0.10810302, 0.44594849, 0.09157621, 0.81684757, 0.09157621]], dtype=np.float64)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
############################################################
|
|
189
|
+
# NUMBA OPTIMIZED ASSEMBLER #
|
|
190
|
+
############################################################
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@njit(c16[:,:](f8[:,:], i8[:,:], c16), cache=True, nogil=True)
|
|
194
|
+
def _abc_order_2_terms(tri_vertices, local_edge_map, cf):
|
|
195
|
+
'''ABC order 2 tangent gradient term'''
|
|
196
|
+
|
|
197
|
+
origin = tri_vertices[:,0]
|
|
198
|
+
vertex_2 = tri_vertices[:,1]
|
|
199
|
+
vertex_3 = tri_vertices[:,2]
|
|
200
|
+
|
|
201
|
+
edge_1 = vertex_2-origin
|
|
202
|
+
edge_2 = vertex_3-origin
|
|
203
|
+
|
|
204
|
+
zhat = normalize(cross(edge_1, edge_2))
|
|
205
|
+
xhat = normalize(edge_1)
|
|
206
|
+
yhat = normalize(cross(zhat, xhat))
|
|
207
|
+
|
|
208
|
+
basis = np.zeros((3,3), dtype=np.float64)
|
|
209
|
+
basis[0,:] = xhat
|
|
210
|
+
basis[1,:] = yhat
|
|
211
|
+
basis[2,:] = zhat
|
|
212
|
+
|
|
213
|
+
local_vertices = optim_matmul(basis, tri_vertices - origin[:,np.newaxis])
|
|
214
|
+
|
|
215
|
+
CurlMatrix = np.zeros((8,8), dtype=np.complex128)
|
|
216
|
+
DivMatrix = np.zeros((8,8), dtype=np.complex128)
|
|
217
|
+
|
|
218
|
+
Lengths = np.ones((8,8), dtype=np.float64)
|
|
219
|
+
|
|
220
|
+
WEIGHTS = DPTS[0,:]
|
|
221
|
+
DPTS1 = DPTS[1,:]
|
|
222
|
+
DPTS2 = DPTS[2,:]
|
|
223
|
+
DPTS3 = DPTS[3,:]
|
|
224
|
+
|
|
225
|
+
xpts = local_vertices[0,:]
|
|
226
|
+
ypts = local_vertices[1,:]
|
|
227
|
+
|
|
228
|
+
distances = compute_distances(xpts, ypts, 0*xpts)
|
|
229
|
+
|
|
230
|
+
xs = xpts[0]*DPTS1 + xpts[1]*DPTS2 + xpts[2]*DPTS3
|
|
231
|
+
ys = ypts[0]*DPTS1 + ypts[1]*DPTS2 + ypts[2]*DPTS3
|
|
232
|
+
|
|
233
|
+
int_coords = np.empty((2,xs.shape[0]), dtype=np.float64)
|
|
234
|
+
int_coords[0,:] = xs
|
|
235
|
+
int_coords[1,:] = ys
|
|
236
|
+
|
|
237
|
+
aas, bbs, ccs, Area = tri_coefficients(xpts, ypts)
|
|
238
|
+
|
|
239
|
+
bary_coeff = np.empty((3,3), dtype=np.float64)
|
|
240
|
+
bary_coeff[0,:] = aas/(2*Area)
|
|
241
|
+
bary_coeff[1,:] = bbs/(2*Area)
|
|
242
|
+
bary_coeff[2,:] = ccs/(2*Area)
|
|
243
|
+
|
|
244
|
+
Lengths[3,:] *= distances[0,2]
|
|
245
|
+
Lengths[7,:] *= distances[0,1]
|
|
246
|
+
Lengths[:,3] *= distances[0,2]
|
|
247
|
+
Lengths[:,7] *= distances[0,1]
|
|
248
|
+
|
|
249
|
+
FF1C = _curl_face_1(bary_coeff, int_coords)
|
|
250
|
+
FF2C = _curl_face_2(bary_coeff, int_coords)
|
|
251
|
+
FF1D = _divergence_face_1(bary_coeff, int_coords)
|
|
252
|
+
FF2D = _divergence_face_2(bary_coeff, int_coords)
|
|
253
|
+
|
|
254
|
+
for iv1 in range(3):
|
|
255
|
+
ie1 = local_edge_map[:, iv1]
|
|
256
|
+
|
|
257
|
+
Le = distances[ie1[0], ie1[1]]
|
|
258
|
+
Lengths[iv1,:] *= Le
|
|
259
|
+
Lengths[iv1+4,:] *= Le
|
|
260
|
+
Lengths[:,iv1] *= Le
|
|
261
|
+
Lengths[:,iv1+4] *= Le
|
|
262
|
+
|
|
263
|
+
FE1C_1 = _curl_edge_1(bary_coeff[:,ie1], int_coords)
|
|
264
|
+
FE2C_1 = _curl_edge_2(bary_coeff[:,ie1], int_coords)
|
|
265
|
+
FE1D_1 = _divergence_edge_1(bary_coeff[:,ie1], int_coords)
|
|
266
|
+
FE2D_1 = _divergence_edge_2(bary_coeff[:,ie1], int_coords)
|
|
267
|
+
|
|
268
|
+
for iv2 in range(3):
|
|
269
|
+
ie2 = local_edge_map[:, iv2]
|
|
270
|
+
|
|
271
|
+
FE1C_2 = _curl_edge_1(bary_coeff[:,ie2], int_coords)
|
|
272
|
+
FE2C_2 = _curl_edge_2(bary_coeff[:,ie2], int_coords)
|
|
273
|
+
FE1D_2 = _divergence_edge_1(bary_coeff[:,ie2], int_coords)
|
|
274
|
+
FE2D_2 = _divergence_edge_2(bary_coeff[:,ie2], int_coords)
|
|
275
|
+
|
|
276
|
+
CurlMatrix[iv1, iv2] = _gqi(FE1C_1, FE1C_2, WEIGHTS)
|
|
277
|
+
CurlMatrix[iv1, iv2+4] = _gqi(FE1C_1, FE2C_2, WEIGHTS)
|
|
278
|
+
CurlMatrix[iv1+4, iv2] = _gqi(FE2C_1, FE1C_2, WEIGHTS)
|
|
279
|
+
CurlMatrix[iv1+4, iv2+4] = _gqi(FE2C_1, FE2C_2, WEIGHTS)
|
|
280
|
+
|
|
281
|
+
DivMatrix[iv1, iv2] = _gqi(FE1D_1, FE1D_2, WEIGHTS)
|
|
282
|
+
DivMatrix[iv1, iv2+4] = _gqi(FE1D_1, FE2D_2, WEIGHTS)
|
|
283
|
+
DivMatrix[iv1+4, iv2] = _gqi(FE2D_1, FE1D_2, WEIGHTS)
|
|
284
|
+
DivMatrix[iv1+4, iv2+4] = _gqi(FE2D_1, FE2D_2, WEIGHTS)
|
|
285
|
+
|
|
286
|
+
CurlMatrix[iv1, 3] = _gqi(FE1C_1, FF1C, WEIGHTS)
|
|
287
|
+
CurlMatrix[iv1+4,3] = _gqi(FE2C_1, FF1C, WEIGHTS)
|
|
288
|
+
CurlMatrix[iv1, 7] = _gqi(FE1C_1, FF2C, WEIGHTS)
|
|
289
|
+
CurlMatrix[iv1+4,7] = _gqi(FE2C_1, FF2C, WEIGHTS)
|
|
290
|
+
|
|
291
|
+
CurlMatrix[3, iv1] = CurlMatrix[iv1, 3]
|
|
292
|
+
CurlMatrix[3, iv1+4] = CurlMatrix[iv1+4, 3]
|
|
293
|
+
CurlMatrix[7, iv1] = CurlMatrix[iv1, 7]
|
|
294
|
+
CurlMatrix[7, iv1+4] = CurlMatrix[iv1+4, 7]
|
|
295
|
+
|
|
296
|
+
DivMatrix[iv1, 3] = _gqi(FE1D_1, FF1D, WEIGHTS)
|
|
297
|
+
DivMatrix[iv1+4,3] = _gqi(FE2D_1, FF1D, WEIGHTS)
|
|
298
|
+
DivMatrix[iv1, 7] = _gqi(FE1D_1, FF2D, WEIGHTS)
|
|
299
|
+
DivMatrix[iv1+4,7] = _gqi(FE2D_1, FF2D, WEIGHTS)
|
|
300
|
+
|
|
301
|
+
DivMatrix[3, iv1] = DivMatrix[iv1, 3]
|
|
302
|
+
DivMatrix[3, iv1+4] = DivMatrix[iv1+4, 3]
|
|
303
|
+
DivMatrix[7, iv1] = DivMatrix[iv1, 7]
|
|
304
|
+
DivMatrix[7, iv1+4] = DivMatrix[iv1+4, 7]
|
|
305
|
+
|
|
306
|
+
CurlMatrix[3, 3] = _gqi(FF1C, FF1C, WEIGHTS)
|
|
307
|
+
CurlMatrix[3, 7] = _gqi(FF1C, FF2C, WEIGHTS)
|
|
308
|
+
CurlMatrix[7, 3] = _gqi(FF2C, FF1C, WEIGHTS)
|
|
309
|
+
CurlMatrix[7, 7] = _gqi(FF2C, FF2C, WEIGHTS)
|
|
310
|
+
|
|
311
|
+
DivMatrix[3, 3] = _gqi(FF1D, FF1D, WEIGHTS)
|
|
312
|
+
DivMatrix[3, 7] = _gqi(FF1D, FF2D, WEIGHTS)
|
|
313
|
+
DivMatrix[7, 3] = _gqi(FF2D, FF1D, WEIGHTS)
|
|
314
|
+
DivMatrix[7, 7] = _gqi(FF2D, FF2D, WEIGHTS)
|
|
315
|
+
|
|
316
|
+
Mat = cf*Lengths*(CurlMatrix-DivMatrix)*np.abs(Area)
|
|
317
|
+
return Mat
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
############################################################
|
|
321
|
+
# NUMBA OPTIMIZED INTEGRAL OVER TETS #
|
|
322
|
+
############################################################
|
|
323
|
+
|
|
324
|
+
@njit((c16[:])(f8[:,:],
|
|
325
|
+
i8[:,:],
|
|
326
|
+
i8[:,:],
|
|
327
|
+
i8[:,:],
|
|
328
|
+
i8[:],
|
|
329
|
+
c16), cache=True, nogil=True, parallel=True)
|
|
330
|
+
def _matrix_builder(nodes, tris, edges, tri_to_field, tri_ids, coeff):
|
|
331
|
+
""" Numba optimized loop over each face triangle."""
|
|
332
|
+
ntritot = tris.shape[1]
|
|
333
|
+
nnz = ntritot*64
|
|
334
|
+
|
|
335
|
+
Mat = np.zeros(nnz, dtype=np.complex128)
|
|
336
|
+
|
|
337
|
+
tri_to_edge = tri_to_field[:3,:]
|
|
338
|
+
|
|
339
|
+
Ntris = tri_ids.shape[0]
|
|
340
|
+
for itri_sub in prange(Ntris): # type: ignore
|
|
341
|
+
|
|
342
|
+
itri = tri_ids[itri_sub]
|
|
343
|
+
p = itri*64
|
|
344
|
+
|
|
345
|
+
# Construct a local mapping to global triangle orientations
|
|
346
|
+
local_tri_map = local_tri_to_edgeid(itri, tris, edges, tri_to_edge)
|
|
347
|
+
|
|
348
|
+
# Construct the local edge map
|
|
349
|
+
tri_nodes = nodes[:, tris[:,itri]]
|
|
350
|
+
subMat = _abc_order_2_terms(tri_nodes, local_tri_map, coeff)
|
|
351
|
+
|
|
352
|
+
Mat[p:p+64] += subMat.ravel()
|
|
353
|
+
|
|
354
|
+
return Mat
|
|
355
|
+
|
|
356
|
+
############################################################
|
|
357
|
+
# PYTHON INTERFACE #
|
|
358
|
+
############################################################
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def abc_order_2_matrix(field: Nedelec2,
|
|
362
|
+
surf_triangle_indices: np.ndarray,
|
|
363
|
+
coeff: complex) -> np.ndarray:
|
|
364
|
+
"""Computes the second order absorbing boundary condition correction terms.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
field (Nedelec2): The Basis function object
|
|
368
|
+
surf_triangle_indices (np.ndarray): The surface triangle indices to add
|
|
369
|
+
coeff (complex): The integral coefficient jp2/k0
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
np.ndarray: The resultant matrix items
|
|
373
|
+
"""
|
|
374
|
+
Mat = _matrix_builder(field.mesh.nodes, field.mesh.tris, field.mesh.edges, field.tri_to_field, surf_triangle_indices, coeff)
|
|
375
|
+
return Mat
|