emerge 1.0.7__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of emerge might be problematic. Click here for more details.

Files changed (33) hide show
  1. emerge/__init__.py +15 -3
  2. emerge/_emerge/const.py +2 -1
  3. emerge/_emerge/elements/ned2_interp.py +122 -42
  4. emerge/_emerge/geo/__init__.py +1 -1
  5. emerge/_emerge/geo/operations.py +20 -0
  6. emerge/_emerge/geo/pcb.py +162 -71
  7. emerge/_emerge/geo/shapes.py +12 -7
  8. emerge/_emerge/geo/step.py +177 -41
  9. emerge/_emerge/geometry.py +189 -27
  10. emerge/_emerge/logsettings.py +26 -2
  11. emerge/_emerge/material.py +2 -0
  12. emerge/_emerge/mesh3d.py +6 -8
  13. emerge/_emerge/mesher.py +67 -11
  14. emerge/_emerge/mth/common_functions.py +1 -1
  15. emerge/_emerge/mth/optimized.py +2 -2
  16. emerge/_emerge/physics/microwave/adaptive_mesh.py +549 -116
  17. emerge/_emerge/physics/microwave/assembly/assembler.py +9 -1
  18. emerge/_emerge/physics/microwave/microwave_3d.py +133 -83
  19. emerge/_emerge/physics/microwave/microwave_bc.py +158 -8
  20. emerge/_emerge/physics/microwave/microwave_data.py +94 -5
  21. emerge/_emerge/plot/pyvista/display.py +36 -23
  22. emerge/_emerge/selection.py +17 -2
  23. emerge/_emerge/settings.py +124 -6
  24. emerge/_emerge/simmodel.py +273 -150
  25. emerge/_emerge/simstate.py +106 -0
  26. emerge/_emerge/simulation_data.py +11 -23
  27. emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
  28. emerge/_emerge/solver.py +4 -4
  29. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/METADATA +7 -3
  30. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/RECORD +33 -32
  31. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/WHEEL +0 -0
  32. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/entry_points.txt +0 -0
  33. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -18,75 +18,240 @@
18
18
  from .microwave_data import MWField
19
19
  import numpy as np
20
20
  from ...mth.optimized import matmul, outward_normal, cross_c
21
- from numba import njit, f8, c16, i8, types # type: ignore
21
+ from numba import njit, f8, c16, i8, types, prange # type: ignore, p
22
+ from loguru import logger
23
+ from ...const import C0, MU0, EPS0
24
+
25
+ @njit(cache=True, nogil=True)
26
+ def diam_circum_circle(v1: np.ndarray, v2: np.ndarray, v3: np.ndarray, eps: float = 1e-14) -> float:
27
+ """
28
+ Diameter of the circumcircle of triangle (v1,v2,v3) in 3D.
29
+ Uses D = (|AB|*|AC|*|BC|) / ||AB x AC||.
30
+ Returns np.inf if points are (near) collinear.
31
+ """
32
+ AB = v2 - v1
33
+ AC = v3 - v1
34
+ BC = v3 - v2
35
+
36
+ a = np.linalg.norm(BC)
37
+ b = np.linalg.norm(AC)
38
+ c = np.linalg.norm(AB)
39
+
40
+ cross = np.cross(AB, AC)
41
+ denom = np.linalg.norm(cross) # 2 * area of triangle
42
+
43
+ if denom < eps:
44
+ return np.inf # degenerate/collinear
45
+
46
+ return (a * b * c) / denom # diameter = 2R
47
+
48
+
49
+ @njit(cache=True, nogil=True)
50
+ def circum_sphere_diam(v1: np.ndarray, v2: np.ndarray, v3: np.ndarray, v4: np.ndarray, eps: float = 1e-14) -> float:
51
+ """
52
+ Diameter of the circumsphere of tetrahedron (v1,v2,v3,v4) in 3D.
53
+ Solves for center c from 2*(pi - p4) · c = |pi|^2 - |p4|^2, i=1..3.
54
+ Returns np.inf if points are (near) coplanar/degenerate.
55
+ """
56
+ p1, p2, p3, p4 = v1, v2, v3, v4
57
+
58
+ M = np.empty((3, 3), dtype=np.float64)
59
+ M[0, :] = 2.0 * (p1 - p4)
60
+ M[1, :] = 2.0 * (p2 - p4)
61
+ M[2, :] = 2.0 * (p3 - p4)
62
+
63
+ # manual 3x3 determinant (Numba-friendly)
64
+ det = (
65
+ M[0,0] * (M[1,1]*M[2,2] - M[1,2]*M[2,1])
66
+ - M[0,1] * (M[1,0]*M[2,2] - M[1,2]*M[2,0])
67
+ + M[0,2] * (M[1,0]*M[2,1] - M[1,1]*M[2,0])
68
+ )
69
+ if np.abs(det) < eps:
70
+ return np.inf # coplanar/degenerate
71
+
72
+ rhs = np.empty(3, dtype=np.float64)
73
+ rhs[0] = np.dot(p1, p1) - np.dot(p4, p4)
74
+ rhs[1] = np.dot(p2, p2) - np.dot(p4, p4)
75
+ rhs[2] = np.dot(p3, p3) - np.dot(p4, p4)
76
+
77
+ # Solve for circumcenter
78
+ c = np.linalg.solve(M, rhs)
79
+
80
+ # Radius = distance to any vertex
81
+ R = np.linalg.norm(c - p1)
82
+ return 2.0 * R # diameter
83
+
84
+ def print_sparam_matrix(pre: str, S: np.ndarray):
85
+ """
86
+ Print an N x N complex S-parameter matrix in dB∠deg format.
87
+ Magnitude in dB rounded to 2 decimals, phase in degrees with 1 decimal.
88
+ """
89
+ S = np.asarray(S)
90
+ if S.ndim != 2 or S.shape[0] != S.shape[1]:
91
+ raise ValueError("S must be a square (N x N) complex matrix")
92
+
93
+ N = S.shape[0]
94
+ logger.debug(pre+"S-parameter matrix (dB ∠ deg):")
95
+
96
+ for i in range(N):
97
+ row_str = []
98
+ for j in range(N):
99
+ mag_db = 20 * np.log10(np.abs(S[i, j]) + np.finfo(float).eps)
100
+ phase_deg = np.degrees(np.angle(S[i, j]))
101
+ row_str.append(f"{mag_db:6.2f} dB ∠ {phase_deg:6.1f}°")
102
+ logger.debug(" | ".join(row_str))
22
103
 
23
104
  def compute_convergence(Sold: np.ndarray, Snew: np.ndarray) -> float:
24
105
  """
25
106
  Return a single scalar: max |Snew - Sold|.
26
107
  Works for shapes (N,N) or (..., N, N); reduces over all axes.
27
108
  """
109
+
28
110
  Sold = np.asarray(Sold)
29
111
  Snew = np.asarray(Snew)
112
+ print_sparam_matrix('Old:',Sold)
113
+ print_sparam_matrix('New',Snew)
30
114
  if Sold.shape != Snew.shape:
31
115
  raise ValueError("Sold and Snew must have identical shapes")
32
- return float(np.abs(Snew - Sold).max())
116
+ #amp_conv = float(np.abs(np.abs(Snew) - np.abs(Sold)).max())
117
+ mag_conv = float(np.abs(np.abs(Snew)-np.abs(Sold)).max())
118
+ amp_conv = float(np.abs(Snew - Sold).max())
119
+ phase_conv = float(np.abs(np.angle(np.diag(Sold)/np.diag(Snew))).max()) * 180/np.pi
120
+ return amp_conv, mag_conv, phase_conv
33
121
 
34
122
  def select_refinement_indices(errors: np.ndarray, refine: float) -> np.ndarray:
35
123
  """
36
- Pick indices to refine based on two rules, then take the smaller set:
37
- (A) All indices with |error| >= (1 - refine) * max(|error|)
38
- (B) The top ceil(refine * N) indices by |error|
39
- Returns indices sorted from largest to smallest |error|.
124
+ Dörfler marking:
125
+ Choose the minimal number of elements whose squared error sum
126
+ reaches at least 'refine' (theta in [0,1]) of the global squared error.
127
+
128
+ Args:
129
+ errors: 1D or ND array of local error indicators (nonnegative).
130
+ refine: theta in [0,1]; fraction of total error energy to target.
131
+
132
+ Returns:
133
+ np.ndarray of indices (ints) sorted by decreasing error.
40
134
  """
41
- errs = np.abs(np.ravel(errors))
42
- N = errs.size
43
- if N == 0:
44
- return np.array([], dtype=int)
45
- refine = float(np.clip(refine, 0.0, 1.0))
46
- if refine == 0.0:
47
- return np.array([], dtype=int)
48
-
49
- # Sorted indices by decreasing amplitude
50
- sorted_desc = np.argsort(errs)[::-1]
51
-
52
- # Rule A: threshold by amplitude
53
- thresh = (1.0 - refine) * errs[sorted_desc[0]]
54
- A = np.flatnonzero(errs >= thresh)
55
-
56
- # Rule B: top-k by count
57
- k = int(np.ceil(refine * N))
58
- B = sorted_desc[:k]
59
-
60
- # Choose the smaller set (tie-breaker: use B)
61
- chosen = A if A.size < B.size else B
62
- chosen = B
63
- # Return chosen indices sorted from largest to smallest amplitude
64
- mask = np.zeros(N, dtype=bool)
65
- mask[chosen] = True
66
- return sorted_desc[mask[sorted_desc]]
67
-
68
-
69
- def compute_size(id: int, coords: np.ndarray, q: float, dss: np.ndarray) -> float:
70
- N = len(dss)
71
- sizes = []
135
+ # Flatten and sanitize
136
+ errs = np.abs(np.ravel(errors).astype(float))
137
+ n = errs.size
138
+ if n == 0:
139
+ return np.empty(0, dtype=int)
140
+
141
+ errs[~np.isfinite(errs)] = 0.0 # replace NaN/inf by 0
142
+ theta = float(np.clip(refine, 0.0, 1.0))
143
+ if theta <= 0.0:
144
+ return np.empty(0, dtype=int)
145
+
146
+ # Dörfler uses squared indicators
147
+ ind = errs * errs
148
+ total = ind.sum()
149
+ if total <= 0.0:
150
+ return np.empty(0, dtype=int)
151
+
152
+ # Sort by decreasing indicator
153
+ order = np.argsort(ind)[::-1]
154
+ sum_error = 0
155
+ indices = []
156
+ for index in order:
157
+ sum_error += ind[index]
158
+ indices.append(index)
159
+ if sum_error >= refine*total:
160
+ break
161
+
162
+ #cum = np.cumsum(ind[order])
163
+
164
+ # Smallest m with cumulative ≥ theta * total
165
+ #m = int(np.searchsorted(cum, theta * total, side="left")) + 1
166
+
167
+ #chosen = order[:m] # already from largest to smallest
168
+ return np.array(indices)#chosen.astype(int)
169
+
170
+ @njit(f8[:](i8, f8[:,:], f8, f8, f8[:]), cache=True, nogil=True, parallel=False)
171
+ def compute_size(id: int, coords: np.ndarray, q: float, scaler: float, dss: np.ndarray) -> float:
172
+ """Optimized function to compute the size impressed by size constraint points on each other size constraint point.
173
+
174
+ Args:
175
+ id (int): _description_
176
+ coords (np.ndarray): _description_
177
+ q (float): _description_
178
+ scaler (float): _description_
179
+ dss (np.ndarray): _description_
180
+
181
+ Returns:
182
+ float: _description_
183
+ """
184
+ N = dss.shape[0]
185
+ sizes = np.zeros((N,), dtype=np.float64)-1.0
72
186
  x, y, z = coords[:,id]
73
- for n in range(N):
187
+ for n in prange(N):
74
188
  if n == id:
189
+ sizes[n] = dss[n]*scaler
75
190
  continue
76
- nsize = dss[n]/q - (1-q)/q * ((x-coords[0,n])**2 + (y-coords[1,n])**2 + (z-coords[2,n])**2)**0.5
77
- sizes.append(nsize)
78
- return np.min(sizes)
79
-
80
- def reduce_point_set(coords: np.ndarray, q: float, dss: np.ndarray, scaler: float) -> list[int]:
81
- N = len(dss)
82
- candidates = []
191
+ nsize = scaler*dss[n]/q - (1-q)/q * max(0, (((x-coords[0,n])**2 + (y-coords[1,n])**2 + (z-coords[2,n])**2)**0.5-dss[n]/3))
192
+ sizes[n] = nsize
193
+ return sizes
194
+
195
+ @njit(f8[:](f8[:,:], i8, i8[:]), cache=True, nogil=True, parallel=True)
196
+ def nbmin(matrix, axis, include):
197
+
198
+ if axis==0:
199
+ N = matrix.shape[1]
200
+ out = np.empty((N,), dtype=np.float64)
201
+ for n in prange(N):
202
+ out[n] = np.min(matrix[include==1,n])
203
+ return out
204
+ if axis==1:
205
+ N = matrix.shape[0]
206
+ out = np.empty((N,), dtype=np.float64)
207
+ for n in prange(N):
208
+ out[n] = np.min(matrix[n,include==1])
209
+ return out
210
+ else:
211
+ out = np.empty((N,), dtype=np.float64)
212
+ return out
213
+
214
+ @njit(i8(i8, f8[:,:], f8, i8[:]), cache=True, nogil=True, parallel=False)
215
+ def can_remove(index: int, M: np.ndarray, scaling: float, include: np.ndarray) -> int:
216
+
217
+ ratio = np.min(M[index,:] / nbmin(M, 1, include))
218
+
219
+ if ratio > scaling:
220
+ return 1
221
+ return 0
222
+
223
+ @njit(i8[:](f8[:,:], f8, f8[:], f8, f8), cache=True, nogil=True, parallel=False)
224
+ def reduce_point_set(coords: np.ndarray, q: float, dss: np.ndarray, scaler: float, keep_percentage: float) -> list[int]:
225
+ N = dss.shape[0]
226
+ impressed_size = np.zeros((N,N), np.float64)
227
+
228
+ include = np.ones((N,), dtype=np.int64)
229
+
83
230
  for n in range(N):
84
- nsize = compute_size(n, coords, q, dss)
85
- if dss[n]*scaler*q**(0.4)< nsize:
86
- candidates.append(0)
87
- else:
88
- candidates.append(1)
89
- return [i for i in range(N) if candidates[i]==1]
231
+ impressed_size[:,n] = compute_size(n, coords, q, scaler, dss)
232
+
233
+ current_min = nbmin(impressed_size, 1, include)
234
+
235
+ counter = 0
236
+ for i in range(N):
237
+
238
+ if include[i]==0:
239
+ continue
240
+
241
+ if (N-counter)/N < keep_percentage:
242
+ break
243
+
244
+ n_imposed = np.sum(impressed_size[:,i] <= (current_min*1.3))
245
+
246
+ if n_imposed == 0:
247
+ include[i] = 0
248
+ counter = counter + 1
249
+
250
+
251
+
252
+ ids = np.arange(N)
253
+ output = ids[include==1]
254
+ return output
90
255
 
91
256
 
92
257
  @njit(i8[:, :](i8[:], i8[:, :]), cache=True, nogil=True)
@@ -166,16 +331,42 @@ def tet_coefficients(xs, ys, zs):
166
331
 
167
332
  return aas, bbs, ccs, dds, V
168
333
 
169
- @njit(c16[:](f8[:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
334
+ DPTS_2D = np.array([[0.22338159, 0.22338159, 0.22338159, 0.10995174, 0.10995174,
335
+ 0.10995174],
336
+ [0.10810302, 0.44594849, 0.44594849, 0.81684757, 0.09157621,
337
+ 0.09157621],
338
+ [0.44594849, 0.44594849, 0.10810302, 0.09157621, 0.09157621,
339
+ 0.81684757],
340
+ [0.44594849, 0.10810302, 0.44594849, 0.09157621, 0.81684757,
341
+ 0.09157621]], dtype=np.float64)
342
+
343
+ DPTS_3D = np.array([[-0.078933 , 0.04573333, 0.04573333, 0.04573333, 0.04573333,
344
+ 0.14933333, 0.14933333, 0.14933333, 0.14933333, 0.14933333,
345
+ 0.14933333],
346
+ [ 0.25 , 0.78571429, 0.07142857, 0.07142857, 0.07142857,
347
+ 0.39940358, 0.39940358, 0.39940358, 0.10059642, 0.10059642,
348
+ 0.10059642],
349
+ [ 0.25 , 0.07142857, 0.07142857, 0.07142857, 0.78571429,
350
+ 0.10059642, 0.10059642, 0.39940358, 0.39940358, 0.39940358,
351
+ 0.10059642],
352
+ [ 0.25 , 0.07142857, 0.07142857, 0.78571429, 0.07142857,
353
+ 0.39940358, 0.10059642, 0.10059642, 0.39940358, 0.10059642,
354
+ 0.39940358],
355
+ [ 0.25 , 0.07142857, 0.78571429, 0.07142857, 0.07142857,
356
+ 0.10059642, 0.39940358, 0.10059642, 0.10059642, 0.39940358,
357
+ 0.39940358]], dtype=np.float64)
358
+
359
+ @njit(c16[:,:](f8[:,:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
170
360
  def compute_field(coords: np.ndarray,
171
361
  vertices: np.ndarray,
172
362
  Etet: np.ndarray,
173
363
  l_edge_ids: np.ndarray,
174
364
  l_tri_ids: np.ndarray):
175
365
 
176
- x = coords[0]
177
- y = coords[1]
178
- z = coords[2]
366
+ x = coords[0,:]
367
+ y = coords[1,:]
368
+ z = coords[2,:]
369
+ N = coords.shape[1]
179
370
 
180
371
  xvs = vertices[0,:]
181
372
  yvs = vertices[1,:]
@@ -188,11 +379,12 @@ def compute_field(coords: np.ndarray,
188
379
  Em2s = Etet[10:16]
189
380
  Ef2s = Etet[16:20]
190
381
 
191
- Exl = 0.0 + 0.0j
192
- Eyl = 0.0 + 0.0j
193
- Ezl = 0.0 + 0.0j
382
+ Exl = np.zeros_like(x, dtype=np.complex128)
383
+ Eyl = np.zeros_like(x, dtype=np.complex128)
384
+ Ezl = np.zeros_like(x, dtype=np.complex128)
385
+
386
+ V1 = (6*V)**3
194
387
 
195
- V1 = (216*V**3)
196
388
  for ie in range(6):
197
389
  Em1, Em2 = Em1s[ie], Em2s[ie]
198
390
  edgeids = l_edge_ids[:, ie]
@@ -243,10 +435,10 @@ def compute_field(coords: np.ndarray,
243
435
  Eyl += ey
244
436
  Ezl += ez
245
437
 
246
- out = np.zeros((3,), dtype=np.complex128)
247
- out[0] = Exl
248
- out[1] = Eyl
249
- out[2] = Ezl
438
+ out = np.zeros((3,N), dtype=np.complex128)
439
+ out[0,:] = Exl
440
+ out[1,:] = Eyl
441
+ out[2,:] = Ezl
250
442
  return out
251
443
 
252
444
  @njit(c16[:,:](f8[:,:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
@@ -275,8 +467,8 @@ def compute_curl(coords: np.ndarray,
275
467
  Eyl = np.zeros((x.shape[0],), dtype=np.complex128)
276
468
  Ezl = np.zeros((x.shape[0],), dtype=np.complex128)
277
469
 
278
- V1 = (216*V**3)
279
- V2 = (72*V**3)
470
+ V1 = 216*V**3
471
+ V2 = 72*V**3
280
472
 
281
473
  for ie in range(6):
282
474
  Em1, Em2 = Em1s[ie], Em2s[ie]
@@ -370,8 +562,103 @@ def compute_curl(coords: np.ndarray,
370
562
  out[2,:] = Ezl
371
563
  return out
372
564
 
373
- @njit(c16[:](f8[:], f8[:,:], c16[:], i8[:,:], i8[:,:], c16[:,:]), cache=True, nogil=True)
374
- def compute_curl_curl(coords: np.ndarray,
565
+ @njit(c16[:](f8[:,:], f8[:,:], c16[:], i8[:,:], i8[:,:], c16[:,:]), cache=True, nogil=True)
566
+ def compute_div(coords: np.ndarray,
567
+ vertices: np.ndarray,
568
+ Etet: np.ndarray,
569
+ l_edge_ids: np.ndarray,
570
+ l_tri_ids: np.ndarray,
571
+ Um: np.ndarray):
572
+
573
+ uxx, uxy, uxz = Um[0,0], Um[0,1], Um[0,2]
574
+ uyx, uyy, uyz = Um[1,0], Um[1,1], Um[1,2]
575
+ uzx, uzy, uzz = Um[2,0], Um[2,1], Um[2,2]
576
+
577
+ xs = coords[0,:]
578
+ ys = coords[1,:]
579
+ zs = coords[2,:]
580
+
581
+ xvs = vertices[0,:]
582
+ yvs = vertices[1,:]
583
+ zvs = vertices[2,:]
584
+
585
+ a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
586
+
587
+ Em1s = Etet[0:6]
588
+ Ef1s = Etet[6:10]
589
+ Em2s = Etet[10:16]
590
+ Ef2s = Etet[16:20]
591
+
592
+ difE = np.zeros((xs.shape[0],), dtype=np.complex128)
593
+
594
+ V1 = (216*V**3)
595
+ V2 = (72*V**3)
596
+
597
+ for ie in range(6):
598
+ Em1, Em2 = Em1s[ie], Em2s[ie]
599
+ edgeids = l_edge_ids[:, ie]
600
+ a1, a2 = a_s[edgeids]
601
+ b1, b2 = b_s[edgeids]
602
+ c1, c2 = c_s[edgeids]
603
+ d1, d2 = d_s[edgeids]
604
+ x1, x2 = xvs[edgeids]
605
+ y1, y2 = yvs[edgeids]
606
+ z1, z2 = zvs[edgeids]
607
+
608
+ L = np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
609
+ C1 = (a2 + b2*xs + c2*ys + d2*zs)
610
+ C2 = (a1 + b1*xs + c1*ys + d1*zs)
611
+ C3 = (b1*C1 - b2*C2)
612
+ C4 = (c1*C1 - c2*C2)
613
+ C5 = (d1*C1 - d2*C2)
614
+ C6 = (b1*c2 - b2*c1)
615
+ C7 = (c1*d2 - c2*d1)
616
+ C8 = (b1*d2 - b2*d1)
617
+ difE += (Em1*L*(b1*uxx*C3 + b1*uxy*C4 + b1*uxz*C5 + c1*uyx*C3 + c1*uyy*C4 + c1*uyz*C5 + d1*uzx*C3
618
+ + d1*uzy*C4 + d1*uzz*C5 - uxy*C6*C2 - uxz*C8*C2 + uyx*C6*C2 - uyz*C7*C2 + uzx*C8*C2 + uzy*C7*C2) +
619
+ Em2*L*(b2*uxx*C3 + b2*uxy*C4 + b2*uxz*C5 + c2*uyx*C3 + c2*uyy*C4 + c2*uyz*C5 + d2*uzx*C3
620
+ + d2*uzy*C4 + d2*uzz*C5 - uxy*C6*C1 - uxz*C8*C1 + uyx*C6*C1 - uyz*C7*C1 + uzx*C8*C1 + uzy*C7*C1))/V1
621
+
622
+ for ie in range(4):
623
+ Em1, Em2 = Ef1s[ie], Ef2s[ie]
624
+ triids = l_tri_ids[:, ie]
625
+ a1, a2, a3 = a_s[triids]
626
+ b1, b2, b3 = b_s[triids]
627
+ c1, c2, c3 = c_s[triids]
628
+ d1, d2, d3 = d_s[triids]
629
+
630
+ x1, x2, x3 = xvs[l_tri_ids[:, ie]]
631
+ y1, y2, y3 = yvs[l_tri_ids[:, ie]]
632
+ z1, z2, z3 = zvs[l_tri_ids[:, ie]]
633
+
634
+ L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
635
+ L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
636
+ C1 = (a3 + b3*xs + c3*ys + d3*zs)
637
+ C2 = (a1 + b1*xs + c1*ys + d1*zs)
638
+ C6 = (a2 + b2*xs + c2*ys + d2*zs)
639
+ C3 = (b1*C1 - b3*C2)
640
+ C4 = (c1*C1 - c3*C2)
641
+ C5 = (d1*C1 - d3*C2)
642
+ C7 = (b1*c3 - b3*c1)
643
+ C8 = (b1*d3 - b3*d1)
644
+ C9 = (c1*d3 - c3*d1)
645
+ C10 = (b1*C6 - b2*C2)
646
+ C11 = (c1*C6 - c2*C2)
647
+ C12 = (d1*C6 - d2*C2)
648
+ C13 = (b1*c2 - b2*c1)
649
+ C14 = (b1*d2 - b2*d1)
650
+ C15 = (c1*d2 - c2*d1)
651
+
652
+ difE += (-Em1*L1*(b2*uxx*C3 + b2*uxy*C4 + b2*uxz*C5 + c2*uyx*C3 + c2*uyy*C4 + c2*uyz*C5 + d2*uzx*C3
653
+ + d2*uzy*C4 + d2*uzz*C5 - uxy*C7*C6 - uxz*C8*C6 + uyx*C7*C6 - uyz*C9*C6 + uzx*C8*C6 + uzy*C9*C6)
654
+ + Em2*L2*(b3*uxx*C10 + b3*uxy*C11 + b3*uxz*C12 + c3*uyx*C10 + c3*uyy*C11 + c3*uyz*C12
655
+ + d3*uzx*C10 + d3*uzy*C11 + d3*uzz*C12 - uxy*C13*C1 - uxz*C14*C1 + uyx*C13*C1 - uyz*C15*C1 + uzx*C14*C1 + uzy*C15*C1))/V1
656
+
657
+ return difE
658
+
659
+
660
+ @njit(c16[:](f8[:,:], c16[:], i8[:,:], i8[:,:], c16[:,:]), cache=True, nogil=True)
661
+ def compute_curl_curl(
375
662
  vertices: np.ndarray,
376
663
  Etet: np.ndarray,
377
664
  l_edge_ids: np.ndarray,
@@ -382,10 +669,6 @@ def compute_curl_curl(coords: np.ndarray,
382
669
  uyx, uyy, uyz = Um[1,0], Um[1,1], Um[1,2]
383
670
  uzx, uzy, uzz = Um[2,0], Um[2,1], Um[2,2]
384
671
 
385
- x = coords[0]
386
- y = coords[1]
387
- z = coords[2]
388
-
389
672
  xvs = vertices[0,:]
390
673
  yvs = vertices[1,:]
391
674
  zvs = vertices[2,:]
@@ -401,7 +684,7 @@ def compute_curl_curl(coords: np.ndarray,
401
684
  Em2s = Etet[10:16]
402
685
  Ef2s = Etet[16:20]
403
686
 
404
- V1 = (216*V**3)
687
+ V1 = (6*V)**3
405
688
 
406
689
  for ie in range(6):
407
690
  Em1, Em2 = Em1s[ie], Em2s[ie]
@@ -419,9 +702,9 @@ def compute_curl_curl(coords: np.ndarray,
419
702
  ey = 3*L1*(Em1*(b1**2*c2*uzz - b1**2*d2*uzy - b1*b2*c1*uzz + b1*b2*d1*uzy + b1*c1*d2*uzx - b1*c2*d1*uxz - b1*c2*d1*uzx + b1*d1*d2*uxy + b2*c1*d1*uxz - b2*d1**2*uxy - c1*d1*d2*uxx + c2*d1**2*uxx) + Em2*(b1*b2*c2*uzz - b1*b2*d2*uzy - b1*c2*d2*uxz + b1*d2**2*uxy - b2**2*c1*uzz + b2**2*d1*uzy + b2*c1*d2*uxz + b2*c1*d2*uzx - b2*c2*d1*uzx - b2*d1*d2*uxy - c1*d2**2*uxx + c2*d1*d2*uxx))
420
703
  ez = -3*L1*(Em1*(b1**2*c2*uyz - b1**2*d2*uyy - b1*b2*c1*uyz + b1*b2*d1*uyy - b1*c1*c2*uxz + b1*c1*d2*uxy + b1*c1*d2*uyx - b1*c2*d1*uyx + b2*c1**2*uxz - b2*c1*d1*uxy - c1**2*d2*uxx + c1*c2*d1*uxx) + Em2*(b1*b2*c2*uyz - b1*b2*d2*uyy - b1*c2**2*uxz + b1*c2*d2*uxy - b2**2*c1*uyz + b2**2*d1*uyy + b2*c1*c2*uxz + b2*c1*d2*uyx - b2*c2*d1*uxy - b2*c2*d1*uyx - c1*c2*d2*uxx + c2**2*d1*uxx))
421
704
 
422
- Exl += ex*V1
423
- Eyl += ey*V1
424
- Ezl += ez*V1
705
+ Exl += ex/V1
706
+ Eyl += ey/V1
707
+ Ezl += ez/V1
425
708
 
426
709
  for ie in range(4):
427
710
  Em1, Em2 = Ef1s[ie], Ef2s[ie]
@@ -438,13 +721,13 @@ def compute_curl_curl(coords: np.ndarray,
438
721
  L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
439
722
  L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
440
723
 
441
- ex = L1*(Em1*L1*(3*c2*uzx*(c1*d3 - c3*d1) + 3*c2*uzz*(b1*c3 - b3*c1) - 3*d2*uyx*(c1*d3 - c3*d1) + 3*d2*uyy*(b1*d3 - b3*d1) - uyz*(-b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) + 2*d2*(b1*c3 - b3*c1)) - uzy*(b2*(c1*d3 - c3*d1) + 2*c2*(b1*d3 - b3*d1) + d2*(b1*c3 - b3*c1))) - Em2*L2*(3*c3*uzx*(c1*d2 - c2*d1) + 3*c3*uzz*(b1*c2 - b2*c1) - 3*d3*uyx*(c1*d2 - c2*d1) + 3*d3*uyy*(b1*d2 - b2*d1) - uyz*(-b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) + 2*d3*(b1*c2 - b2*c1)) - uzy*(b3*(c1*d2 - c2*d1) + 2*c3*(b1*d2 - b2*d1) + d3*(b1*c2 - b2*c1))))
442
- ey = L1*(Em1*L1*(3*b2*uzy*(b1*d3 - b3*d1) - 3*b2*uzz*(b1*c3 - b3*c1) + 3*d2*uxx*(c1*d3 - c3*d1) - 3*d2*uxy*(b1*d3 - b3*d1) + uxz*(-b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) + 2*d2*(b1*c3 - b3*c1)) - uzx*(2*b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) - d2*(b1*c3 - b3*c1))) - Em2*L2*(3*b3*uzy*(b1*d2 - b2*d1) - 3*b3*uzz*(b1*c2 - b2*c1) + 3*d3*uxx*(c1*d2 - c2*d1) - 3*d3*uxy*(b1*d2 - b2*d1) + uxz*(-b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) + 2*d3*(b1*c2 - b2*c1)) - uzx*(2*b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) - d3*(b1*c2 - b2*c1))))
443
- ez = -L1*(Em1*L1*(3*b2*uyy*(b1*d3 - b3*d1) - 3*b2*uyz*(b1*c3 - b3*c1) + 3*c2*uxx*(c1*d3 - c3*d1) + 3*c2*uxz*(b1*c3 - b3*c1) - uxy*(b2*(c1*d3 - c3*d1) + 2*c2*(b1*d3 - b3*d1) + d2*(b1*c3 - b3*c1)) - uyx*(2*b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) - d2*(b1*c3 - b3*c1))) - Em2*L2*(3*b3*uyy*(b1*d2 - b2*d1) - 3*b3*uyz*(b1*c2 - b2*c1) + 3*c3*uxx*(c1*d2 - c2*d1) + 3*c3*uxz*(b1*c2 - b2*c1) - uxy*(b3*(c1*d2 - c2*d1) + 2*c3*(b1*d2 - b2*d1) + d3*(b1*c2 - b2*c1)) - uyx*(2*b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) - d3*(b1*c2 - b2*c1))))
724
+ ex = (Em1*L1*(3*c2*uzx*(c1*d3 - c3*d1) + 3*c2*uzz*(b1*c3 - b3*c1) - 3*d2*uyx*(c1*d3 - c3*d1) + 3*d2*uyy*(b1*d3 - b3*d1) - uyz*(-b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) + 2*d2*(b1*c3 - b3*c1)) - uzy*(b2*(c1*d3 - c3*d1) + 2*c2*(b1*d3 - b3*d1) + d2*(b1*c3 - b3*c1))) - Em2*L2*(3*c3*uzx*(c1*d2 - c2*d1) + 3*c3*uzz*(b1*c2 - b2*c1) - 3*d3*uyx*(c1*d2 - c2*d1) + 3*d3*uyy*(b1*d2 - b2*d1) - uyz*(-b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) + 2*d3*(b1*c2 - b2*c1)) - uzy*(b3*(c1*d2 - c2*d1) + 2*c3*(b1*d2 - b2*d1) + d3*(b1*c2 - b2*c1))))
725
+ ey = (Em1*L1*(3*b2*uzy*(b1*d3 - b3*d1) - 3*b2*uzz*(b1*c3 - b3*c1) + 3*d2*uxx*(c1*d3 - c3*d1) - 3*d2*uxy*(b1*d3 - b3*d1) + uxz*(-b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) + 2*d2*(b1*c3 - b3*c1)) - uzx*(2*b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) - d2*(b1*c3 - b3*c1))) - Em2*L2*(3*b3*uzy*(b1*d2 - b2*d1) - 3*b3*uzz*(b1*c2 - b2*c1) + 3*d3*uxx*(c1*d2 - c2*d1) - 3*d3*uxy*(b1*d2 - b2*d1) + uxz*(-b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) + 2*d3*(b1*c2 - b2*c1)) - uzx*(2*b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) - d3*(b1*c2 - b2*c1))))
726
+ ez = -(Em1*L1*(3*b2*uyy*(b1*d3 - b3*d1) - 3*b2*uyz*(b1*c3 - b3*c1) + 3*c2*uxx*(c1*d3 - c3*d1) + 3*c2*uxz*(b1*c3 - b3*c1) - uxy*(b2*(c1*d3 - c3*d1) + 2*c2*(b1*d3 - b3*d1) + d2*(b1*c3 - b3*c1)) - uyx*(2*b2*(c1*d3 - c3*d1) + c2*(b1*d3 - b3*d1) - d2*(b1*c3 - b3*c1))) - Em2*L2*(3*b3*uyy*(b1*d2 - b2*d1) - 3*b3*uyz*(b1*c2 - b2*c1) + 3*c3*uxx*(c1*d2 - c2*d1) + 3*c3*uxz*(b1*c2 - b2*c1) - uxy*(b3*(c1*d2 - c2*d1) + 2*c3*(b1*d2 - b2*d1) + d3*(b1*c2 - b2*c1)) - uyx*(2*b3*(c1*d2 - c2*d1) + c3*(b1*d2 - b2*d1) - d3*(b1*c2 - b2*c1))))
444
727
 
445
- Exl += ex*V1
446
- Eyl += ey*V1
447
- Ezl += ez*V1
728
+ Exl += ex/V1
729
+ Eyl += ey/V1
730
+ Ezl += ez/V1
448
731
 
449
732
  out = np.zeros((3,), dtype=np.complex128)
450
733
  out[0] = Exl
@@ -452,9 +735,40 @@ def compute_curl_curl(coords: np.ndarray,
452
735
  out[2] = Ezl
453
736
  return out
454
737
 
738
+ @njit(c16[:,:](c16[:], c16[:,:]), cache=True, fastmath=True, nogil=True)
739
+ def cross_c_arry(a: np.ndarray, b: np.ndarray):
740
+ """Optimized complex single vector cross product
741
+
742
+ Args:
743
+ a (np.ndarray): (3,) vector a
744
+ b (np.ndarray): (3,) vector b
745
+
746
+ Returns:
747
+ np.ndarray: a ⨉ b
748
+ """
749
+ crossv = np.empty((3,b.shape[1]), dtype=np.complex128)
750
+ crossv[0,:] = a[1]*b[2,:] - a[2]*b[1,:]
751
+ crossv[1,:] = a[2]*b[0,:] - a[0]*b[2,:]
752
+ crossv[2,:] = a[0]*b[1,:] - a[1]*b[0,:]
753
+ return crossv
754
+
755
+ @njit(c16[:](c16[:], c16[:,:]), cache=True, fastmath=True, nogil=True)
756
+ def dot_c_arry(a: np.ndarray, b: np.ndarray):
757
+ """Optimized complex single vector cross product
758
+
759
+ Args:
760
+ a (np.ndarray): (3,) vector a
761
+ b (np.ndarray): (3,) vector b
762
+
763
+ Returns:
764
+ np.ndarray: a ⨉ b
765
+ """
766
+ dotv = a[0]*b[0,:] + a[1]*b[1,:] + a[2]*b[2,:]
767
+ return dotv
768
+
455
769
  @njit(types.Tuple((f8[:], f8[:]))(f8[:,:], i8[:,:], i8[:,:], i8[:,:], f8[:,:],
456
770
  c16[:], f8[:], f8[:], i8[:,:], i8[:,:],
457
- f8[:,:], i8[:,:], i8[:,:], c16[:], c16[:], f8), cache=True, nogil=True)
771
+ f8[:,:], i8[:,:], i8[:,:], c16[:], c16[:], i8[:], f8), cache=True, nogil=True)
458
772
  def compute_error_single(nodes, tets, tris, edges, centers,
459
773
  Efield,
460
774
  edge_lengths,
@@ -466,83 +780,200 @@ def compute_error_single(nodes, tets, tris, edges, centers,
466
780
  tet_to_field,
467
781
  er,
468
782
  ur,
783
+ pec_tris,
469
784
  k0,) -> np.ndarray:
470
785
 
786
+ is_pec = np.zeros((tris.shape[1],), dtype=np.bool)
787
+ is_pec[pec_tris] = True
471
788
 
472
- # UNPACK DATA
789
+ # CONSTANTS
473
790
  ntet = tets.shape[1]
474
791
  nedges = edges.shape[1]
792
+ W0 = k0*C0
793
+ N2D = DPTS_2D.shape[1]
794
+ W_VOL = DPTS_3D[0,:]
795
+ Y0 = np.sqrt(1/MU0)
475
796
 
476
-
477
- # INIT POSTERIORI ERROR ESTIMATE
478
- error = np.zeros((ntet,), dtype=np.float64)
797
+ # INIT POSTERIORI ERROR ESTIMATE QUANTITIES
798
+ alpha_t = np.zeros((ntet,), dtype=np.complex128)
479
799
  max_elem_size = np.zeros((ntet,), dtype=np.float64)
480
800
 
481
- hks = np.zeros((ntet,), dtype=np.float64)
482
- hfs = np.zeros((4,ntet), dtype=np.float64)
483
- face_error1 = np.zeros((4,3,ntet), dtype=np.complex128)
484
- face_error2 = np.zeros((4,3,ntet), dtype=np.complex128)
801
+ Qf_face1 = np.zeros((4,N2D,ntet), dtype=np.complex128)
802
+ Qf_face2 = np.zeros((4,N2D,ntet), dtype=np.complex128)
803
+ Jf_face1 = np.zeros((4,3,N2D,ntet), dtype=np.complex128)
804
+ Jf_face2 = np.zeros((4,3,N2D,ntet), dtype=np.complex128)
805
+
806
+ areas_fr = np.zeros((4, N2D, ntet), dtype=np.float64)
807
+ Rf_fr = np.zeros((4, ntet), dtype=np.float64)
808
+ adj_tets_mat = -np.ones((4,ntet), dtype=np.int32)
485
809
 
486
-
487
810
  # Compute Error estimate
488
811
  for itet in range(ntet):
489
812
  uinv = (1/ur[itet])*np.eye(3)
490
813
  ermat = er[itet]*np.eye(3)
814
+ erc = er[itet]
815
+ urc = ur[itet]
491
816
 
817
+ # GEOMETRIC QUANTITIES
492
818
  vertices = nodes[:,tets[:, itet]]
819
+ v1 = vertices[:,0]
820
+ v2 = vertices[:,1]
821
+ v3 = vertices[:,2]
822
+ v4 = vertices[:,3]
493
823
 
824
+ # VOLUME INTEGRATION POINTS
825
+ vxs = DPTS_3D[1,:]*v1[0] + DPTS_3D[2,:]*v2[0] + DPTS_3D[3,:]*v3[0] + DPTS_3D[4,:]*v4[0]
826
+ vys = DPTS_3D[1,:]*v1[1] + DPTS_3D[2,:]*v2[1] + DPTS_3D[3,:]*v3[1] + DPTS_3D[4,:]*v4[1]
827
+ vzs = DPTS_3D[1,:]*v1[2] + DPTS_3D[2,:]*v2[2] + DPTS_3D[3,:]*v3[2] + DPTS_3D[4,:]*v4[2]
828
+
829
+ intpts = np.empty((3,DPTS_3D.shape[1]), dtype=np.float64)
830
+ intpts[0,:] = vxs
831
+ intpts[1,:] = vys
832
+ intpts[2,:] = vzs
833
+
834
+ # TET TRI NODE COUPLINGS
494
835
  g_node_ids = tets[:, itet]
495
836
  g_edge_ids = edges[:, tet_to_field[:6, itet]]
496
837
  g_tri_ids = tris[:, tet_to_field[6:10, itet]-nedges]
497
-
498
838
  l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
499
839
  l_tri_ids = local_mapping(g_node_ids, g_tri_ids)
500
-
501
- coords = centers[:,itet]
502
- Ef = Efield[tet_to_field[:,itet]]
503
- Rv1 = -compute_curl_curl(coords, vertices, Ef, l_edge_ids, l_tri_ids, uinv)
504
- Rv2 = k0**2*(ermat @ compute_field(coords, vertices, Ef, l_edge_ids, l_tri_ids))
505
-
506
840
  triids = tet_to_tri[:,itet]
507
- facecoords = tri_centers[:, triids]
841
+
842
+ size_max = circum_sphere_diam(v1,v2,v3,v4)
843
+ #size_max = np.max(edge_lengths[tet_to_edge[:,itet]])
508
844
 
509
845
  Volume = compute_volume(vertices[0,:], vertices[1,:], vertices[2,:])
510
- hks[itet] = Volume**(1/3)
511
- Rf = matmul(uinv, compute_curl(facecoords, vertices, Ef, l_edge_ids, l_tri_ids))
846
+
847
+ Rt = size_max
848
+ # Efield
849
+ Ef = Efield[tet_to_field[:,itet]]
850
+
851
+ # Qt term
852
+ Qt = Volume*EPS0*np.sum(W_VOL*compute_div(intpts, vertices, Ef, l_edge_ids, l_tri_ids, ermat), axis=0)
853
+
854
+ # Jt term
855
+
856
+ Rv1 = compute_curl_curl(vertices, Ef, l_edge_ids, l_tri_ids, uinv)
857
+ Rv2 = -k0**2*(ermat @ compute_field(intpts, vertices, Ef, l_edge_ids, l_tri_ids))
858
+ Rv = 1*Rv2
859
+ Rv[0,:] += Rv1[0] # X-component
860
+ Rv[1,:] += Rv1[1] # Y-component
861
+ Rv[2,:] += Rv1[2] # Z-component
862
+
863
+ Rv[0,:] = Rv[0,:]*W_VOL
864
+ Rv[1,:] = Rv[1,:]*W_VOL
865
+ Rv[2,:] = Rv[2,:]*W_VOL
866
+
867
+ Jt = -Volume*np.sum(1/(1j*W0*MU0) * Rv, axis=1)
868
+
869
+ Gt = (1j*W0*np.exp(-1j*k0*Rt)/(4*np.pi*Rt))
870
+ alpha_t[itet] = - Gt/(erc*EPS0) * Qt*Qt - Gt*urc*MU0 * np.sum(Jt*Jt)
871
+
872
+ # Face Residual computation
873
+
874
+ all_face_coords = np.empty((3,4*N2D), dtype=np.float64)
875
+ for itri in range(4):
876
+ triid = triids[itri]
877
+ tnodes = nodes[:,tris[:,triid]]
878
+ n1 = tnodes[:,0]
879
+ n2 = tnodes[:,1]
880
+ n3 = tnodes[:,2]
881
+ all_face_coords[0,itri*N2D:(itri+1)*N2D] = DPTS_2D[1,:]*n1[0] + DPTS_2D[2,:]*n2[0] + DPTS_2D[3,:]*n3[0]
882
+ all_face_coords[1,itri*N2D:(itri+1)*N2D] = DPTS_2D[1,:]*n1[1] + DPTS_2D[2,:]*n2[1] + DPTS_2D[3,:]*n3[1]
883
+ all_face_coords[2,itri*N2D:(itri+1)*N2D] = DPTS_2D[1,:]*n1[2] + DPTS_2D[2,:]*n2[2] + DPTS_2D[3,:]*n3[2]
884
+
885
+ Qf_all = erc*EPS0*compute_field(all_face_coords, vertices, Ef, l_edge_ids, l_tri_ids)
886
+ Jf_all = -1/(1j*MU0*W0)*matmul(uinv, compute_curl(all_face_coords, vertices, Ef, l_edge_ids, l_tri_ids))
887
+ E_face_all = compute_field(all_face_coords, vertices, Ef, l_edge_ids, l_tri_ids)
512
888
  tetc = centers[:,itet].flatten()
513
889
 
514
- max_elem_size[itet] = np.mean(edge_lengths[tet_to_edge[:,itet]])
890
+ max_elem_size[itet] = size_max
515
891
 
516
892
  for iface in range(4):
517
- i1, i2, i3 = tris[:, triids[iface]]
893
+ tri_index = triids[iface]
894
+
895
+ pec_face = is_pec[tri_index]
896
+
897
+ i1, i2, i3 = tris[:, tri_index]
898
+
899
+ slc1 = iface*N2D
900
+ slc2 = slc1+N2D
901
+
518
902
  normal = outward_normal(nodes[:,i1], nodes[:,i2], nodes[:,i3], tetc).astype(np.complex128)
519
903
 
904
+ area = areas[triids[iface]]
905
+
906
+ n1 = nodes[:,i1]
907
+ n2 = nodes[:,i2]
908
+ n3 = nodes[:,i3]
909
+ l1 = np.linalg.norm(n2-n1)
910
+ l2 = np.linalg.norm(n3-n1)
911
+ l3 = np.linalg.norm(n3-n2)
912
+ Rf = np.max(np.array([l1, l2, l3]))
913
+ Rf = diam_circum_circle(n1,n2,n3)
914
+ Rf_fr[iface,itet] = Rf
915
+ areas_fr[iface, :, itet] = area
916
+
520
917
  adj_tets = [int(tri_to_tet[j,triids[iface]]) for j in range(2)]
521
918
  adj_tets = [num for num in adj_tets if num not in (itet, -1234)]
522
919
 
523
920
  if len(adj_tets) == 0:
524
921
  continue
525
- area = areas[triids[iface]]
526
922
 
527
- hfs[iface,itet] = area**0.5
923
+ if pec_face is True:
924
+ Jtan = Y0*np.sqrt(1/urc)*cross_c_arry(normal, -cross_c_arry(normal, E_face_all[:, slc1: slc2]))
925
+
926
+ itet_adj = adj_tets[0]
927
+ iface_adj = np.argwhere(tet_to_tri[:,itet_adj]==triids[iface])[0][0]
928
+
929
+ Jf_face1[iface, :, :, itet] = Jtan
930
+
931
+ adj_tets_mat[iface,itet] = itet_adj
932
+ continue
528
933
 
529
934
  itet_adj = adj_tets[0]
530
935
  iface_adj = np.argwhere(tet_to_tri[:,itet_adj]==triids[iface])[0][0]
531
936
 
532
- face_error2[iface_adj, :, itet_adj] = -area*cross_c(normal, uinv @ Rf[:, iface])
533
- face_error1[iface, :, itet] = area*cross_c(normal, uinv @ Rf[:,iface])
534
-
937
+ Jf_face1[iface, :, :, itet] = cross_c_arry(normal, Jf_all[:, slc1:slc2])
938
+ Jf_face2[iface_adj, :, :, itet_adj] = -cross_c_arry(normal, Jf_all[:, slc1:slc2])
939
+
940
+ Qf_face1[iface, :, itet] = dot_c_arry(normal, Qf_all[:, slc1:slc2])
941
+ Qf_face2[iface_adj, :, itet_adj] = -dot_c_arry(normal, Qf_all[:, slc1:slc2])
535
942
 
536
- error[itet] = np.linalg.norm(np.abs(Volume*(Rv1 + Rv2)))**2
537
-
538
- fdiff = np.abs(face_error1 - face_error2)
539
- fnorm = fdiff[:,0,:]**2 + fdiff[:,1,:]**2 + fdiff[:,2,:]**2
540
- ferror = np.sum(fnorm*hfs, axis=0)
541
- error = hks**2*error + 0.5*ferror
943
+ adj_tets_mat[iface,itet] = itet_adj
542
944
 
945
+ # Compute 2D Gauss quadrature weight matrix
946
+ fWs = np.empty_like(areas_fr, dtype=np.float64)
947
+ for i in range(N2D):
948
+ fWs[:,i,:] = DPTS_2D[0,i]
949
+
950
+ # Compute the εE field difference (4, NDPTS, NTET)
951
+ Qf_delta = Qf_face1 - Qf_face2
952
+ Jf_delta = Jf_face1 - Jf_face2
953
+
954
+ # Perform Gauss-Quadrature integration (4, NTET)
955
+ Qf_int = np.sum(Qf_delta*areas_fr*fWs, axis=1)
956
+ Jf_int_x = np.sum(Jf_delta[:,0,:,:]*areas_fr*fWs, axis=1)
957
+ Jf_int_y = np.sum(Jf_delta[:,1,:,:]*areas_fr*fWs, axis=1)
958
+ Jf_int_z = np.sum(Jf_delta[:,2,:,:]*areas_fr*fWs, axis=1)
959
+
960
+ Gf = (1j*W0*np.exp(-1j*k0*Rf_fr)/(4*np.pi*Rf_fr))
961
+ alpha_Df = - Gf/(er*EPS0)*(Qf_int*Qf_int) - Gf*(ur*MU0) * (Jf_int_x*Jf_int_x + Jf_int_y*Jf_int_y + Jf_int_z*Jf_int_z)
962
+
963
+ alpha_Nf = np.zeros((4, ntet), dtype=np.complex128)
964
+ for it in range(ntet):
965
+ for iface in range(4):
966
+ it2 = adj_tets_mat[iface, it]
967
+ if it2==-1:
968
+ continue
969
+ alpha_Nf[iface,it] = alpha_t[it2]
970
+
971
+ alpha_f = np.sum((alpha_t/(alpha_t + alpha_Nf + 1e-13))*alpha_Df, axis=0)
972
+ error = (np.abs(alpha_t + alpha_f))**0.5
973
+
543
974
  return error, max_elem_size
544
975
 
545
- def compute_error_estimate(field: MWField) -> np.ndarray:
976
+ def compute_error_estimate(field: MWField, pec_tris: list[int]) -> np.ndarray:
546
977
  mesh = field.mesh
547
978
 
548
979
  nodes = mesh.nodes
@@ -559,8 +990,10 @@ def compute_error_estimate(field: MWField) -> np.ndarray:
559
990
  tet_to_field = field.basis.tet_to_field
560
991
  er = field._der
561
992
  ur = field._dur
993
+
562
994
  Ls = mesh.edge_lengths
563
995
 
996
+ pec_tris = np.sort(np.unique(np.array(pec_tris)))
564
997
  errors = []
565
998
  for key in field._fields.keys():
566
999
  excitation = field._fields[key]
@@ -568,7 +1001,7 @@ def compute_error_estimate(field: MWField) -> np.ndarray:
568
1001
  error, sizes = compute_error_single(nodes, tets, tris, edges,
569
1002
  centers, excitation, Ls, As,
570
1003
  tet_to_edge, tet_to_tri, tri_centers,
571
- tri_to_tet, tet_to_field, er, ur, field.k0)
1004
+ tri_to_tet, tet_to_field, er, ur, pec_tris, field.k0)
572
1005
 
573
1006
  errors.append(error)
574
1007