emerge 1.1.0__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.

@@ -20,6 +20,66 @@ import numpy as np
20
20
  from ...mth.optimized import matmul, outward_normal, cross_c
21
21
  from numba import njit, f8, c16, i8, types, prange # type: ignore, p
22
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
23
83
 
24
84
  def print_sparam_matrix(pre: str, S: np.ndarray):
25
85
  """
@@ -40,7 +100,7 @@ def print_sparam_matrix(pre: str, S: np.ndarray):
40
100
  phase_deg = np.degrees(np.angle(S[i, j]))
41
101
  row_str.append(f"{mag_db:6.2f} dB ∠ {phase_deg:6.1f}°")
42
102
  logger.debug(" | ".join(row_str))
43
-
103
+
44
104
  def compute_convergence(Sold: np.ndarray, Snew: np.ndarray) -> float:
45
105
  """
46
106
  Return a single scalar: max |Snew - Sold|.
@@ -61,37 +121,51 @@ def compute_convergence(Sold: np.ndarray, Snew: np.ndarray) -> float:
61
121
 
62
122
  def select_refinement_indices(errors: np.ndarray, refine: float) -> np.ndarray:
63
123
  """
64
- Pick indices to refine based on two rules, then take the smaller set:
65
- (A) All indices with |error| >= (1 - refine) * max(|error|)
66
- (B) The top ceil(refine * N) indices by |error|
67
- Returns indices sorted from largest to smallest |error|.
68
- """
69
- errs = np.abs(np.ravel(errors))
70
- N = errs.size
71
- if N == 0:
72
- return np.array([], dtype=int)
73
- refine = float(np.clip(refine, 0.0, 1.0))
74
- if refine == 0.0:
75
- return np.array([], dtype=int)
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.
76
127
 
77
- # Sorted indices by decreasing amplitude
78
- sorted_desc = np.argsort(errs)[::-1]
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.
79
131
 
80
- # Rule A: threshold by amplitude
81
- thresh = (1.0 - refine) * errs[sorted_desc[0]]
82
- A = np.flatnonzero(errs >= thresh)
132
+ Returns:
133
+ np.ndarray of indices (ints) sorted by decreasing error.
134
+ """
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])
83
163
 
84
- # Rule B: top-k by count
85
- k = int(np.ceil(refine * N))
86
- B = sorted_desc[:k]
164
+ # Smallest m with cumulative ≥ theta * total
165
+ #m = int(np.searchsorted(cum, theta * total, side="left")) + 1
87
166
 
88
- # Choose the smaller set (tie-breaker: use B)
89
- chosen = B#A if A.size > B.size else B
90
-
91
- # Return chosen indices sorted from largest to smallest amplitude
92
- mask = np.zeros(N, dtype=bool)
93
- mask[chosen] = True
94
- return sorted_desc[mask[sorted_desc]]
167
+ #chosen = order[:m] # already from largest to smallest
168
+ return np.array(indices)#chosen.astype(int)
95
169
 
96
170
  @njit(f8[:](i8, f8[:,:], f8, f8, f8[:]), cache=True, nogil=True, parallel=False)
97
171
  def compute_size(id: int, coords: np.ndarray, q: float, scaler: float, dss: np.ndarray) -> float:
@@ -114,7 +188,7 @@ def compute_size(id: int, coords: np.ndarray, q: float, scaler: float, dss: np.n
114
188
  if n == id:
115
189
  sizes[n] = dss[n]*scaler
116
190
  continue
117
- nsize = scaler*dss[n]/q - (1-q)/q * ((x-coords[0,n])**2 + (y-coords[1,n])**2 + (z-coords[2,n])**2)**0.5
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))
118
192
  sizes[n] = nsize
119
193
  return sizes
120
194
 
@@ -167,7 +241,7 @@ def reduce_point_set(coords: np.ndarray, q: float, dss: np.ndarray, scaler: floa
167
241
  if (N-counter)/N < keep_percentage:
168
242
  break
169
243
 
170
- n_imposed = np.sum(impressed_size[:,i] <= (current_min*1.01))
244
+ n_imposed = np.sum(impressed_size[:,i] <= (current_min*1.3))
171
245
 
172
246
  if n_imposed == 0:
173
247
  include[i] = 0
@@ -257,16 +331,42 @@ def tet_coefficients(xs, ys, zs):
257
331
 
258
332
  return aas, bbs, ccs, dds, V
259
333
 
260
- @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)
261
360
  def compute_field(coords: np.ndarray,
262
361
  vertices: np.ndarray,
263
362
  Etet: np.ndarray,
264
363
  l_edge_ids: np.ndarray,
265
364
  l_tri_ids: np.ndarray):
266
365
 
267
- x = coords[0]
268
- y = coords[1]
269
- z = coords[2]
366
+ x = coords[0,:]
367
+ y = coords[1,:]
368
+ z = coords[2,:]
369
+ N = coords.shape[1]
270
370
 
271
371
  xvs = vertices[0,:]
272
372
  yvs = vertices[1,:]
@@ -279,11 +379,12 @@ def compute_field(coords: np.ndarray,
279
379
  Em2s = Etet[10:16]
280
380
  Ef2s = Etet[16:20]
281
381
 
282
- Exl = 0.0 + 0.0j
283
- Eyl = 0.0 + 0.0j
284
- 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
285
387
 
286
- V1 = (216*V**3)
287
388
  for ie in range(6):
288
389
  Em1, Em2 = Em1s[ie], Em2s[ie]
289
390
  edgeids = l_edge_ids[:, ie]
@@ -334,10 +435,10 @@ def compute_field(coords: np.ndarray,
334
435
  Eyl += ey
335
436
  Ezl += ez
336
437
 
337
- out = np.zeros((3,), dtype=np.complex128)
338
- out[0] = Exl
339
- out[1] = Eyl
340
- out[2] = Ezl
438
+ out = np.zeros((3,N), dtype=np.complex128)
439
+ out[0,:] = Exl
440
+ out[1,:] = Eyl
441
+ out[2,:] = Ezl
341
442
  return out
342
443
 
343
444
  @njit(c16[:,:](f8[:,:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
@@ -366,8 +467,8 @@ def compute_curl(coords: np.ndarray,
366
467
  Eyl = np.zeros((x.shape[0],), dtype=np.complex128)
367
468
  Ezl = np.zeros((x.shape[0],), dtype=np.complex128)
368
469
 
369
- V1 = (216*V**3)
370
- V2 = (72*V**3)
470
+ V1 = 216*V**3
471
+ V2 = 72*V**3
371
472
 
372
473
  for ie in range(6):
373
474
  Em1, Em2 = Em1s[ie], Em2s[ie]
@@ -461,8 +562,103 @@ def compute_curl(coords: np.ndarray,
461
562
  out[2,:] = Ezl
462
563
  return out
463
564
 
464
- @njit(c16[:](f8[:], f8[:,:], c16[:], i8[:,:], i8[:,:], c16[:,:]), cache=True, nogil=True)
465
- 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(
466
662
  vertices: np.ndarray,
467
663
  Etet: np.ndarray,
468
664
  l_edge_ids: np.ndarray,
@@ -473,10 +669,6 @@ def compute_curl_curl(coords: np.ndarray,
473
669
  uyx, uyy, uyz = Um[1,0], Um[1,1], Um[1,2]
474
670
  uzx, uzy, uzz = Um[2,0], Um[2,1], Um[2,2]
475
671
 
476
- x = coords[0]
477
- y = coords[1]
478
- z = coords[2]
479
-
480
672
  xvs = vertices[0,:]
481
673
  yvs = vertices[1,:]
482
674
  zvs = vertices[2,:]
@@ -492,7 +684,7 @@ def compute_curl_curl(coords: np.ndarray,
492
684
  Em2s = Etet[10:16]
493
685
  Ef2s = Etet[16:20]
494
686
 
495
- V1 = (216*V**3)
687
+ V1 = (6*V)**3
496
688
 
497
689
  for ie in range(6):
498
690
  Em1, Em2 = Em1s[ie], Em2s[ie]
@@ -510,9 +702,9 @@ def compute_curl_curl(coords: np.ndarray,
510
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))
511
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))
512
704
 
513
- Exl += ex*V1
514
- Eyl += ey*V1
515
- Ezl += ez*V1
705
+ Exl += ex/V1
706
+ Eyl += ey/V1
707
+ Ezl += ez/V1
516
708
 
517
709
  for ie in range(4):
518
710
  Em1, Em2 = Ef1s[ie], Ef2s[ie]
@@ -529,13 +721,13 @@ def compute_curl_curl(coords: np.ndarray,
529
721
  L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
530
722
  L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
531
723
 
532
- 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))))
533
- 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))))
534
- 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))))
535
727
 
536
- Exl += ex*V1
537
- Eyl += ey*V1
538
- Ezl += ez*V1
728
+ Exl += ex/V1
729
+ Eyl += ey/V1
730
+ Ezl += ez/V1
539
731
 
540
732
  out = np.zeros((3,), dtype=np.complex128)
541
733
  out[0] = Exl
@@ -543,9 +735,40 @@ def compute_curl_curl(coords: np.ndarray,
543
735
  out[2] = Ezl
544
736
  return out
545
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
+
546
769
  @njit(types.Tuple((f8[:], f8[:]))(f8[:,:], i8[:,:], i8[:,:], i8[:,:], f8[:,:],
547
770
  c16[:], f8[:], f8[:], i8[:,:], i8[:,:],
548
- f8[:,:], i8[:,:], i8[:,:], c16[:], c16[:], f8), cache=True, nogil=True)
771
+ f8[:,:], i8[:,:], i8[:,:], c16[:], c16[:], i8[:], f8), cache=True, nogil=True)
549
772
  def compute_error_single(nodes, tets, tris, edges, centers,
550
773
  Efield,
551
774
  edge_lengths,
@@ -557,83 +780,200 @@ def compute_error_single(nodes, tets, tris, edges, centers,
557
780
  tet_to_field,
558
781
  er,
559
782
  ur,
783
+ pec_tris,
560
784
  k0,) -> np.ndarray:
561
785
 
786
+ is_pec = np.zeros((tris.shape[1],), dtype=np.bool)
787
+ is_pec[pec_tris] = True
562
788
 
563
- # UNPACK DATA
789
+ # CONSTANTS
564
790
  ntet = tets.shape[1]
565
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)
566
796
 
567
-
568
- # INIT POSTERIORI ERROR ESTIMATE
569
- error = np.zeros((ntet,), dtype=np.float64)
797
+ # INIT POSTERIORI ERROR ESTIMATE QUANTITIES
798
+ alpha_t = np.zeros((ntet,), dtype=np.complex128)
570
799
  max_elem_size = np.zeros((ntet,), dtype=np.float64)
571
800
 
572
- hks = np.zeros((ntet,), dtype=np.float64)
573
- hfs = np.zeros((4,ntet), dtype=np.float64)
574
- face_error1 = np.zeros((4,3,ntet), dtype=np.complex128)
575
- 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)
576
809
 
577
-
578
810
  # Compute Error estimate
579
811
  for itet in range(ntet):
580
812
  uinv = (1/ur[itet])*np.eye(3)
581
813
  ermat = er[itet]*np.eye(3)
814
+ erc = er[itet]
815
+ urc = ur[itet]
582
816
 
817
+ # GEOMETRIC QUANTITIES
583
818
  vertices = nodes[:,tets[:, itet]]
819
+ v1 = vertices[:,0]
820
+ v2 = vertices[:,1]
821
+ v3 = vertices[:,2]
822
+ v4 = vertices[:,3]
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]
584
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
585
835
  g_node_ids = tets[:, itet]
586
836
  g_edge_ids = edges[:, tet_to_field[:6, itet]]
587
837
  g_tri_ids = tris[:, tet_to_field[6:10, itet]-nedges]
588
-
589
838
  l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
590
839
  l_tri_ids = local_mapping(g_node_ids, g_tri_ids)
591
-
592
- coords = centers[:,itet]
593
- Ef = Efield[tet_to_field[:,itet]]
594
- Rv1 = -compute_curl_curl(coords, vertices, Ef, l_edge_ids, l_tri_ids, uinv)
595
- Rv2 = k0**2*(ermat @ compute_field(coords, vertices, Ef, l_edge_ids, l_tri_ids))
596
-
597
840
  triids = tet_to_tri[:,itet]
598
- 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]])
599
844
 
600
845
  Volume = compute_volume(vertices[0,:], vertices[1,:], vertices[2,:])
601
- hks[itet] = Volume**(1/3)
602
- 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)
603
888
  tetc = centers[:,itet].flatten()
604
889
 
605
- max_elem_size[itet] = (Volume*12/np.sqrt(2))**(1/3)#(np.max(edge_lengths[tet_to_edge[:,itet]]) + np.min(edge_lengths[tet_to_edge[:,itet]]))/2
890
+ max_elem_size[itet] = size_max
606
891
 
607
892
  for iface in range(4):
608
- 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
+
609
902
  normal = outward_normal(nodes[:,i1], nodes[:,i2], nodes[:,i3], tetc).astype(np.complex128)
610
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
+
611
917
  adj_tets = [int(tri_to_tet[j,triids[iface]]) for j in range(2)]
612
918
  adj_tets = [num for num in adj_tets if num not in (itet, -1234)]
613
919
 
614
920
  if len(adj_tets) == 0:
615
921
  continue
616
- area = areas[triids[iface]]
617
922
 
618
- 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
619
933
 
620
934
  itet_adj = adj_tets[0]
621
935
  iface_adj = np.argwhere(tet_to_tri[:,itet_adj]==triids[iface])[0][0]
622
936
 
623
- face_error2[iface_adj, :, itet_adj] = -area*cross_c(normal, uinv @ Rf[:, iface])
624
- face_error1[iface, :, itet] = area*cross_c(normal, uinv @ Rf[:,iface])
625
-
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])
626
942
 
627
- error[itet] = np.linalg.norm(np.abs(Volume*(Rv1 + Rv2)))**2
628
-
629
- fdiff = np.abs(face_error1 - face_error2)
630
- fnorm = fdiff[:,0,:]**2 + fdiff[:,1,:]**2 + fdiff[:,2,:]**2
631
- ferror = np.sum(fnorm*hfs, axis=0)
632
- error = hks**2*error + 0.5*ferror
943
+ adj_tets_mat[iface,itet] = itet_adj
633
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
+
634
974
  return error, max_elem_size
635
975
 
636
- def compute_error_estimate(field: MWField) -> np.ndarray:
976
+ def compute_error_estimate(field: MWField, pec_tris: list[int]) -> np.ndarray:
637
977
  mesh = field.mesh
638
978
 
639
979
  nodes = mesh.nodes
@@ -650,8 +990,10 @@ def compute_error_estimate(field: MWField) -> np.ndarray:
650
990
  tet_to_field = field.basis.tet_to_field
651
991
  er = field._der
652
992
  ur = field._dur
993
+
653
994
  Ls = mesh.edge_lengths
654
995
 
996
+ pec_tris = np.sort(np.unique(np.array(pec_tris)))
655
997
  errors = []
656
998
  for key in field._fields.keys():
657
999
  excitation = field._fields[key]
@@ -659,7 +1001,7 @@ def compute_error_estimate(field: MWField) -> np.ndarray:
659
1001
  error, sizes = compute_error_single(nodes, tets, tris, edges,
660
1002
  centers, excitation, Ls, As,
661
1003
  tet_to_edge, tet_to_tri, tri_centers,
662
- tri_to_tet, tet_to_field, er, ur, field.k0)
1004
+ tri_to_tet, tet_to_field, er, ur, pec_tris, field.k0)
663
1005
 
664
1006
  errors.append(error)
665
1007