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.

Files changed (39) hide show
  1. emerge/__init__.py +7 -8
  2. emerge/_emerge/elements/femdata.py +4 -3
  3. emerge/_emerge/elements/nedelec2.py +8 -4
  4. emerge/_emerge/elements/nedleg2.py +6 -2
  5. emerge/_emerge/geo/__init__.py +1 -1
  6. emerge/_emerge/geo/pcb.py +149 -66
  7. emerge/_emerge/geo/pcb_tools/dxf.py +361 -0
  8. emerge/_emerge/geo/polybased.py +23 -74
  9. emerge/_emerge/geo/shapes.py +31 -16
  10. emerge/_emerge/geometry.py +120 -21
  11. emerge/_emerge/mesh3d.py +62 -43
  12. emerge/_emerge/{_cache_check.py → mth/_cache_check.py} +2 -2
  13. emerge/_emerge/mth/optimized.py +69 -3
  14. emerge/_emerge/periodic.py +19 -17
  15. emerge/_emerge/physics/microwave/__init__.py +0 -1
  16. emerge/_emerge/physics/microwave/assembly/assembler.py +27 -5
  17. emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +2 -3
  18. emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -1
  19. emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +375 -0
  20. emerge/_emerge/physics/microwave/assembly/robinbc.py +37 -38
  21. emerge/_emerge/physics/microwave/microwave_3d.py +11 -19
  22. emerge/_emerge/physics/microwave/microwave_bc.py +38 -21
  23. emerge/_emerge/physics/microwave/microwave_data.py +3 -26
  24. emerge/_emerge/physics/microwave/port_functions.py +4 -4
  25. emerge/_emerge/plot/pyvista/display.py +13 -2
  26. emerge/_emerge/plot/simple_plots.py +4 -1
  27. emerge/_emerge/selection.py +12 -9
  28. emerge/_emerge/simmodel.py +68 -34
  29. emerge/_emerge/solver.py +28 -16
  30. emerge/beta/dxf.py +1 -0
  31. emerge/lib.py +1 -0
  32. emerge/materials/__init__.py +1 -0
  33. emerge/materials/isola.py +294 -0
  34. emerge/materials/rogers.py +58 -0
  35. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/METADATA +18 -4
  36. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/RECORD +39 -33
  37. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/WHEEL +0 -0
  38. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/entry_points.txt +0 -0
  39. {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, types
18
+ from numba.core import event
19
19
  from numba import njit
20
20
 
21
21
  _COMPILE_MESSAGE = """
22
22
  [ EMERGE ]
23
- ⚠ Numba is compiling optimized code; this may take a few minutes.
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…"""
@@ -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
@@ -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(Selection([t1,]))
54
- f2s.append(Selection([t2,]))
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.excluded_faces: Selection | None = None
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.excluded_faces is not None:
109
- f1_new = f1 - self.excluded_faces # type: ignore
110
- f2_new = f2 - self.excluded_faces # type: ignore
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], *self.fleft[1])
195
- f2s = SELECTOR_OBJ.inplane(*self.fright[0], *self.fright[1])
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], *self.fbot[1])
203
- f2s = SELECTOR_OBJ.inplane(*self.ftop[0], *self.ftop[1])
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], *self.f11[1])\
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], *self.f12[1])\
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], *self.f21[1])\
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], *self.f22[1])\
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], *self.f31[1])\
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], *self.f32[1])\
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
- from .periodicbc import gen_periodic_matrix
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
- local_tri_map = local_tri_to_edgeid(itri, tris, edges, tri_to_edge)
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,local_tri_map, matinv(urt), ert, k0)
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):
@@ -20,7 +20,6 @@ import numpy as np
20
20
  from numba import njit, types, i8, c16
21
21
  from scipy.sparse import csr_matrix
22
22
 
23
-
24
23
  ############################################################
25
24
  # NUMBA COMPILED #
26
25
  ############################################################
@@ -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