emerge 1.0.5__py3-none-any.whl → 1.0.7__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.

@@ -0,0 +1,576 @@
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
+ from .microwave_data import MWField
19
+ import numpy as np
20
+ from ...mth.optimized import matmul, outward_normal, cross_c
21
+ from numba import njit, f8, c16, i8, types # type: ignore
22
+
23
+ def compute_convergence(Sold: np.ndarray, Snew: np.ndarray) -> float:
24
+ """
25
+ Return a single scalar: max |Snew - Sold|.
26
+ Works for shapes (N,N) or (..., N, N); reduces over all axes.
27
+ """
28
+ Sold = np.asarray(Sold)
29
+ Snew = np.asarray(Snew)
30
+ if Sold.shape != Snew.shape:
31
+ raise ValueError("Sold and Snew must have identical shapes")
32
+ return float(np.abs(Snew - Sold).max())
33
+
34
+ def select_refinement_indices(errors: np.ndarray, refine: float) -> np.ndarray:
35
+ """
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|.
40
+ """
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 = []
72
+ x, y, z = coords[:,id]
73
+ for n in range(N):
74
+ if n == id:
75
+ 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 = []
83
+ 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]
90
+
91
+
92
+ @njit(i8[:, :](i8[:], i8[:, :]), cache=True, nogil=True)
93
+ def local_mapping(vertex_ids, triangle_ids):
94
+ """
95
+ Parameters
96
+ ----------
97
+ vertex_ids : 1-D int64 array (length 4)
98
+ Global vertex 0.1005964238ers of one tetrahedron, in *its* order
99
+ (v0, v1, v2, v3).
100
+
101
+ triangle_ids : 2-D int64 array (nTri × 3)
102
+ Each row is a global-ID triple of one face that belongs to this tet.
103
+
104
+ Returns
105
+ -------
106
+ local_tris : 2-D int64 array (nTri × 3)
107
+ Same triangles, but every entry replaced by the local index
108
+ 0,1,2,3 that the vertex has inside this tetrahedron.
109
+ (Guaranteed to be ∈{0,1,2,3}; no -1 ever appears if the input
110
+ really belongs to the tet.)
111
+ """
112
+ ndim = triangle_ids.shape[0]
113
+ ntri = triangle_ids.shape[1]
114
+ out = np.zeros(triangle_ids.shape, dtype=np.int64)
115
+
116
+ for t in range(ntri): # each triangle
117
+ for j in range(ndim): # each vertex in that triangle
118
+ gid = triangle_ids[j, t] # global ID to look up
119
+
120
+ # linear search over the four tet vertices
121
+ for k in range(4):
122
+ if vertex_ids[k] == gid:
123
+ out[j, t] = k # store local index 0-3
124
+ break # stop the k-loop
125
+
126
+ return out
127
+
128
+ @njit(f8(f8[:], f8[:], f8[:]), cache = True, nogil=True)
129
+ def compute_volume(xs, ys, zs):
130
+ x1, x2, x3, x4 = xs
131
+ y1, y2, y3, y4 = ys
132
+ z1, z2, z3, z4 = zs
133
+
134
+ return np.abs(-x1*y2*z3/6 + x1*y2*z4/6 + x1*y3*z2/6 - x1*y3*z4/6 - x1*y4*z2/6 + x1*y4*z3/6 + x2*y1*z3/6 - x2*y1*z4/6 - x2*y3*z1/6 + x2*y3*z4/6 + x2*y4*z1/6 - x2*y4*z3/6 - x3*y1*z2/6 + x3*y1*z4/6 + x3*y2*z1/6 - x3*y2*z4/6 - x3*y4*z1/6 + x3*y4*z2/6 + x4*y1*z2/6 - x4*y1*z3/6 - x4*y2*z1/6 + x4*y2*z3/6 + x4*y3*z1/6 - x4*y3*z2/6)
135
+
136
+ @njit(types.Tuple((f8[:], f8[:], f8[:], f8[:], f8))(f8[:], f8[:], f8[:]), cache = True, nogil=True)
137
+ def tet_coefficients(xs, ys, zs):
138
+ ## THIS FUNCTION WORKS
139
+ x1, x2, x3, x4 = xs
140
+ y1, y2, y3, y4 = ys
141
+ z1, z2, z3, z4 = zs
142
+
143
+ aas = np.empty((4,), dtype=np.float64)
144
+ bbs = np.empty((4,), dtype=np.float64)
145
+ ccs = np.empty((4,), dtype=np.float64)
146
+ dds = np.empty((4,), dtype=np.float64)
147
+
148
+ V = np.abs(-x1*y2*z3/6 + x1*y2*z4/6 + x1*y3*z2/6 - x1*y3*z4/6 - x1*y4*z2/6 + x1*y4*z3/6 + x2*y1*z3/6 - x2*y1*z4/6 - x2*y3*z1/6 + x2*y3*z4/6 + x2*y4*z1/6 - x2*y4*z3/6 - x3*y1*z2/6 + x3*y1*z4/6 + x3*y2*z1/6 - x3*y2*z4/6 - x3*y4*z1/6 + x3*y4*z2/6 + x4*y1*z2/6 - x4*y1*z3/6 - x4*y2*z1/6 + x4*y2*z3/6 + x4*y3*z1/6 - x4*y3*z2/6)
149
+
150
+ aas[0] = x2*y3*z4 - x2*y4*z3 - x3*y2*z4 + x3*y4*z2 + x4*y2*z3 - x4*y3*z2
151
+ aas[1] = -x1*y3*z4 + x1*y4*z3 + x3*y1*z4 - x3*y4*z1 - x4*y1*z3 + x4*y3*z1
152
+ aas[2] = x1*y2*z4 - x1*y4*z2 - x2*y1*z4 + x2*y4*z1 + x4*y1*z2 - x4*y2*z1
153
+ aas[3] = -x1*y2*z3 + x1*y3*z2 + x2*y1*z3 - x2*y3*z1 - x3*y1*z2 + x3*y2*z1
154
+ bbs[0] = -y2*z3 + y2*z4 + y3*z2 - y3*z4 - y4*z2 + y4*z3
155
+ bbs[1] = y1*z3 - y1*z4 - y3*z1 + y3*z4 + y4*z1 - y4*z3
156
+ bbs[2] = -y1*z2 + y1*z4 + y2*z1 - y2*z4 - y4*z1 + y4*z2
157
+ bbs[3] = y1*z2 - y1*z3 - y2*z1 + y2*z3 + y3*z1 - y3*z2
158
+ ccs[0] = x2*z3 - x2*z4 - x3*z2 + x3*z4 + x4*z2 - x4*z3
159
+ ccs[1] = -x1*z3 + x1*z4 + x3*z1 - x3*z4 - x4*z1 + x4*z3
160
+ ccs[2] = x1*z2 - x1*z4 - x2*z1 + x2*z4 + x4*z1 - x4*z2
161
+ ccs[3] = -x1*z2 + x1*z3 + x2*z1 - x2*z3 - x3*z1 + x3*z2
162
+ dds[0] = -x2*y3 + x2*y4 + x3*y2 - x3*y4 - x4*y2 + x4*y3
163
+ dds[1] = x1*y3 - x1*y4 - x3*y1 + x3*y4 + x4*y1 - x4*y3
164
+ dds[2] = -x1*y2 + x1*y4 + x2*y1 - x2*y4 - x4*y1 + x4*y2
165
+ dds[3] = x1*y2 - x1*y3 - x2*y1 + x2*y3 + x3*y1 - x3*y2
166
+
167
+ return aas, bbs, ccs, dds, V
168
+
169
+ @njit(c16[:](f8[:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
170
+ def compute_field(coords: np.ndarray,
171
+ vertices: np.ndarray,
172
+ Etet: np.ndarray,
173
+ l_edge_ids: np.ndarray,
174
+ l_tri_ids: np.ndarray):
175
+
176
+ x = coords[0]
177
+ y = coords[1]
178
+ z = coords[2]
179
+
180
+ xvs = vertices[0,:]
181
+ yvs = vertices[1,:]
182
+ zvs = vertices[2,:]
183
+
184
+ a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
185
+
186
+ Em1s = Etet[0:6]
187
+ Ef1s = Etet[6:10]
188
+ Em2s = Etet[10:16]
189
+ Ef2s = Etet[16:20]
190
+
191
+ Exl = 0.0 + 0.0j
192
+ Eyl = 0.0 + 0.0j
193
+ Ezl = 0.0 + 0.0j
194
+
195
+ V1 = (216*V**3)
196
+ for ie in range(6):
197
+ Em1, Em2 = Em1s[ie], Em2s[ie]
198
+ edgeids = l_edge_ids[:, ie]
199
+ a1, a2 = a_s[edgeids]
200
+ b1, b2 = b_s[edgeids]
201
+ c1, c2 = c_s[edgeids]
202
+ d1, d2 = d_s[edgeids]
203
+ x1, x2 = xvs[edgeids]
204
+ y1, y2 = yvs[edgeids]
205
+ z1, z2 = zvs[edgeids]
206
+ F1 = (a1 + b1*x + c1*y + d1*z)
207
+ F2 = (a2 + b2*x + c2*y + d2*z)
208
+ L = np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
209
+ ex = L*(Em1*F1 + Em2*F2)*(b1*F2 - b2*F1)/V1
210
+ ey = L*(Em1*F1 + Em2*F2)*(c1*F2 - c2*F1)/V1
211
+ ez = L*(Em1*F1 + Em2*F2)*(d1*F2 - d2*F1)/V1
212
+
213
+ Exl += ex
214
+ Eyl += ey
215
+ Ezl += ez
216
+
217
+ for ie in range(4):
218
+ Em1, Em2 = Ef1s[ie], Ef2s[ie]
219
+ triids = l_tri_ids[:, ie]
220
+ a1, a2, a3 = a_s[triids]
221
+ b1, b2, b3 = b_s[triids]
222
+ c1, c2, c3 = c_s[triids]
223
+ d1, d2, d3 = d_s[triids]
224
+
225
+ x1, x2, x3 = xvs[l_tri_ids[:, ie]]
226
+ y1, y2, y3 = yvs[l_tri_ids[:, ie]]
227
+ z1, z2, z3 = zvs[l_tri_ids[:, ie]]
228
+
229
+ L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
230
+ L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
231
+
232
+ F1 = (a1 + b1*x + c1*y + d1*z)
233
+ F2 = (a2 + b2*x + c2*y + d2*z)
234
+ F3 = (a3 + b3*x + c3*y + d3*z)
235
+
236
+ Q1 = Em1*L1*F2
237
+ Q2 = Em2*L2*F3
238
+ ex = (-Q1*(b1*F3 - b3*F1) + Q2*(b1*F2 - b2*F1))/V1
239
+ ey = (-Q1*(c1*F3 - c3*F1) + Q2*(c1*F2 - c2*F1))/V1
240
+ ez = (-Q1*(d1*F3 - d3*F1) + Q2*(d1*F2 - d2*F1))/V1
241
+
242
+ Exl += ex
243
+ Eyl += ey
244
+ Ezl += ez
245
+
246
+ out = np.zeros((3,), dtype=np.complex128)
247
+ out[0] = Exl
248
+ out[1] = Eyl
249
+ out[2] = Ezl
250
+ return out
251
+
252
+ @njit(c16[:,:](f8[:,:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
253
+ def compute_curl(coords: np.ndarray,
254
+ vertices: np.ndarray,
255
+ Etet: np.ndarray,
256
+ l_edge_ids: np.ndarray,
257
+ l_tri_ids: np.ndarray):
258
+
259
+ x = coords[0,:]
260
+ y = coords[1,:]
261
+ z = coords[2,:]
262
+
263
+ xvs = vertices[0,:]
264
+ yvs = vertices[1,:]
265
+ zvs = vertices[2,:]
266
+
267
+ a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
268
+
269
+ Em1s = Etet[0:6]
270
+ Ef1s = Etet[6:10]
271
+ Em2s = Etet[10:16]
272
+ Ef2s = Etet[16:20]
273
+
274
+ Exl = np.zeros((x.shape[0],), dtype=np.complex128)
275
+ Eyl = np.zeros((x.shape[0],), dtype=np.complex128)
276
+ Ezl = np.zeros((x.shape[0],), dtype=np.complex128)
277
+
278
+ V1 = (216*V**3)
279
+ V2 = (72*V**3)
280
+
281
+ for ie in range(6):
282
+ Em1, Em2 = Em1s[ie], Em2s[ie]
283
+ edgeids = l_edge_ids[:, ie]
284
+ a1, a2 = a_s[edgeids]
285
+ b1, b2 = b_s[edgeids]
286
+ c1, c2 = c_s[edgeids]
287
+ d1, d2 = d_s[edgeids]
288
+ x1, x2 = xvs[edgeids]
289
+ y1, y2 = yvs[edgeids]
290
+ z1, z2 = zvs[edgeids]
291
+
292
+ L = np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
293
+ C1 = Em1*a1
294
+ C2 = Em1*b1
295
+ C3 = Em1*c1
296
+ C4 = Em1*c2
297
+ C5 = Em2*a2
298
+ C6 = Em2*b2
299
+ C7 = Em2*c1
300
+ C8 = Em2*c2
301
+ C9 = Em1*b2
302
+ C10 = Em2*b1
303
+ D1 = c1*d2
304
+ D2 = c2*d1
305
+ D3 = d1*d2
306
+ D4 = d1*d1
307
+ D5 = c2*d2
308
+ D6 = d2*d2
309
+ D7 = b1*d2
310
+ D8 = b2*d1
311
+ D9 = c1*d1
312
+ D10 = b2*d2
313
+ D11 = b1*c2
314
+ D12 = b2*c1
315
+ D13 = c1*c2
316
+ D14 = c1*c1
317
+ D15 = b2*c2
318
+
319
+ ex = L*(-C1*D1 + C1*D2 - C2*D1*x + C2*D2*x - C3*D1*y + C3*D2*y - C3*D3*z + C4*D4*z - C5*D1 + C5*D2 - C6*D1*x + C6*D2*x - C7*D5*y - C7*D6*z + C8*D2*y + C8*D3*z)/V2
320
+ ey = L*(C1*D7 - C1*D8 + C2*D7*x - C2*D8*x + C2*D1*y + C2*D3*z - C9*D9*y - C9*D4*z + C5*D7 - C5*D8 + C10*D10*x + C10*D5*y + C10*D6*z - C6*D8*x - C6*D2*y - C6*D3*z)/V2
321
+ ez = L*(-C1*D11 + C1*D12 - C2*D11*x + C2*D12*x - C2*D13*y - C2*D2*z + C9*D14*y + C9*D9*z - C5*D11 + C5*D12 - C10*D15*x - C10*c2*c2*y - C10*D5*z + C6*D12*x + C6*D13*y + C6*D1*z)/V2
322
+ Exl += ex
323
+ Eyl += ey
324
+ Ezl += ez
325
+
326
+ for ie in range(4):
327
+ Em1, Em2 = Ef1s[ie], Ef2s[ie]
328
+ triids = l_tri_ids[:, ie]
329
+ a1, a2, a3 = a_s[triids]
330
+ b1, b2, b3 = b_s[triids]
331
+ c1, c2, c3 = c_s[triids]
332
+ d1, d2, d3 = d_s[triids]
333
+
334
+ x1, x2, x3 = xvs[l_tri_ids[:, ie]]
335
+ y1, y2, y3 = yvs[l_tri_ids[:, ie]]
336
+ z1, z2, z3 = zvs[l_tri_ids[:, ie]]
337
+
338
+ L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
339
+ L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
340
+ F1 = (a3 + b3*x + c3*y + d3*z)
341
+ F2 = (a1 + b1*x + c1*y + d1*z)
342
+ F3 = (a2 + b2*x + c2*y + d2*z)
343
+ N1 = (d1*F1 - d3*F2)
344
+ N2 = (c1*F1 - c3*F2)
345
+ N3 = (c1*d3 - c3*d1)
346
+ N4 = (d1*F3 - d2*F2)
347
+ N5 = (c1*F3 - c2*F2)
348
+ D1 = c1*d2
349
+ D2 = c2*d1
350
+ N6 = (D1 - D2)
351
+ N7 = (b1*F1 - b3*F2)
352
+ N8 = (b1*d3 - b3*d1)
353
+ N9 = (b1*F3 - b2*F2)
354
+ D7 = b1*d2
355
+ D8 = b2*d1
356
+ N10 = (D7 - D8)
357
+ D11 = b1*c2
358
+ D12 = b2*c1
359
+ ex = (Em1*L1*(-c2*N1 + d2*N2 + 2*N3*F3) - Em2*L2*(-c3*N4 + d3*N5 + 2*N6*F1))/V1
360
+ ey = (-Em1*L1*(-b2*N1 + d2*N7 + 2*N8*F3) + Em2*L2*(-b3*N4 + d3*N9 + 2*N10*F1))/V1
361
+ ez = (Em1*L1*(-b2*N2 + c2*N7 + 2*(b1*c3 - b3*c1)*F3) - Em2*L2*(-b3*N5 + c3*N9 + 2*(D11 - D12)*F1))/V1
362
+
363
+ Exl += ex
364
+ Eyl += ey
365
+ Ezl += ez
366
+
367
+ out = np.zeros((3,x.shape[0]), dtype=np.complex128)
368
+ out[0,:] = Exl
369
+ out[1,:] = Eyl
370
+ out[2,:] = Ezl
371
+ return out
372
+
373
+ @njit(c16[:](f8[:], f8[:,:], c16[:], i8[:,:], i8[:,:], c16[:,:]), cache=True, nogil=True)
374
+ def compute_curl_curl(coords: np.ndarray,
375
+ vertices: np.ndarray,
376
+ Etet: np.ndarray,
377
+ l_edge_ids: np.ndarray,
378
+ l_tri_ids: np.ndarray,
379
+ Um: np.ndarray):
380
+
381
+ uxx, uxy, uxz = Um[0,0], Um[0,1], Um[0,2]
382
+ uyx, uyy, uyz = Um[1,0], Um[1,1], Um[1,2]
383
+ uzx, uzy, uzz = Um[2,0], Um[2,1], Um[2,2]
384
+
385
+ x = coords[0]
386
+ y = coords[1]
387
+ z = coords[2]
388
+
389
+ xvs = vertices[0,:]
390
+ yvs = vertices[1,:]
391
+ zvs = vertices[2,:]
392
+
393
+ Exl = 0.0 + 0.0j
394
+ Eyl = 0.0 + 0.0j
395
+ Ezl = 0.0 + 0.0j
396
+
397
+ a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
398
+
399
+ Em1s = Etet[0:6]
400
+ Ef1s = Etet[6:10]
401
+ Em2s = Etet[10:16]
402
+ Ef2s = Etet[16:20]
403
+
404
+ V1 = (216*V**3)
405
+
406
+ for ie in range(6):
407
+ Em1, Em2 = Em1s[ie], Em2s[ie]
408
+ edgeids = l_edge_ids[:, ie]
409
+ a1, a2 = a_s[edgeids]
410
+ b1, b2 = b_s[edgeids]
411
+ c1, c2 = c_s[edgeids]
412
+ d1, d2 = d_s[edgeids]
413
+ x1, x2 = xvs[edgeids]
414
+ y1, y2 = yvs[edgeids]
415
+ z1, z2 = zvs[edgeids]
416
+
417
+ L1 = np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
418
+ ex = -3*L1*(Em1*(b1*c1*c2*uzz - b1*c1*d2*uzy - b1*c2*d1*uyz + b1*d1*d2*uyy - b2*c1**2*uzz + b2*c1*d1*uyz + b2*c1*d1*uzy - b2*d1**2*uyy + c1**2*d2*uzx - c1*c2*d1*uzx - c1*d1*d2*uyx + c2*d1**2*uyx) + Em2*(b1*c2**2*uzz - b1*c2*d2*uyz - b1*c2*d2*uzy + b1*d2**2*uyy - b2*c1*c2*uzz + b2*c1*d2*uyz + b2*c2*d1*uzy - b2*d1*d2*uyy + c1*c2*d2*uzx - c1*d2**2*uyx - c2**2*d1*uzx + c2*d1*d2*uyx))
419
+ 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
+ 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
+
422
+ Exl += ex*V1
423
+ Eyl += ey*V1
424
+ Ezl += ez*V1
425
+
426
+ for ie in range(4):
427
+ Em1, Em2 = Ef1s[ie], Ef2s[ie]
428
+ triids = l_tri_ids[:, ie]
429
+ a1, a2, a3 = a_s[triids]
430
+ b1, b2, b3 = b_s[triids]
431
+ c1, c2, c3 = c_s[triids]
432
+ d1, d2, d3 = d_s[triids]
433
+
434
+ x1, x2, x3 = xvs[l_tri_ids[:, ie]]
435
+ y1, y2, y3 = yvs[l_tri_ids[:, ie]]
436
+ z1, z2, z3 = zvs[l_tri_ids[:, ie]]
437
+
438
+ L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
439
+ L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
440
+
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))))
444
+
445
+ Exl += ex*V1
446
+ Eyl += ey*V1
447
+ Ezl += ez*V1
448
+
449
+ out = np.zeros((3,), dtype=np.complex128)
450
+ out[0] = Exl
451
+ out[1] = Eyl
452
+ out[2] = Ezl
453
+ return out
454
+
455
+ @njit(types.Tuple((f8[:], f8[:]))(f8[:,:], i8[:,:], i8[:,:], i8[:,:], f8[:,:],
456
+ c16[:], f8[:], f8[:], i8[:,:], i8[:,:],
457
+ f8[:,:], i8[:,:], i8[:,:], c16[:], c16[:], f8), cache=True, nogil=True)
458
+ def compute_error_single(nodes, tets, tris, edges, centers,
459
+ Efield,
460
+ edge_lengths,
461
+ areas,
462
+ tet_to_edge,
463
+ tet_to_tri,
464
+ tri_centers,
465
+ tri_to_tet,
466
+ tet_to_field,
467
+ er,
468
+ ur,
469
+ k0,) -> np.ndarray:
470
+
471
+
472
+ # UNPACK DATA
473
+ ntet = tets.shape[1]
474
+ nedges = edges.shape[1]
475
+
476
+
477
+ # INIT POSTERIORI ERROR ESTIMATE
478
+ error = np.zeros((ntet,), dtype=np.float64)
479
+ max_elem_size = np.zeros((ntet,), dtype=np.float64)
480
+
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)
485
+
486
+
487
+ # Compute Error estimate
488
+ for itet in range(ntet):
489
+ uinv = (1/ur[itet])*np.eye(3)
490
+ ermat = er[itet]*np.eye(3)
491
+
492
+ vertices = nodes[:,tets[:, itet]]
493
+
494
+ g_node_ids = tets[:, itet]
495
+ g_edge_ids = edges[:, tet_to_field[:6, itet]]
496
+ g_tri_ids = tris[:, tet_to_field[6:10, itet]-nedges]
497
+
498
+ l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
499
+ 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
+ triids = tet_to_tri[:,itet]
507
+ facecoords = tri_centers[:, triids]
508
+
509
+ 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))
512
+ tetc = centers[:,itet].flatten()
513
+
514
+ max_elem_size[itet] = np.mean(edge_lengths[tet_to_edge[:,itet]])
515
+
516
+ for iface in range(4):
517
+ i1, i2, i3 = tris[:, triids[iface]]
518
+ normal = outward_normal(nodes[:,i1], nodes[:,i2], nodes[:,i3], tetc).astype(np.complex128)
519
+
520
+ adj_tets = [int(tri_to_tet[j,triids[iface]]) for j in range(2)]
521
+ adj_tets = [num for num in adj_tets if num not in (itet, -1234)]
522
+
523
+ if len(adj_tets) == 0:
524
+ continue
525
+ area = areas[triids[iface]]
526
+
527
+ hfs[iface,itet] = area**0.5
528
+
529
+ itet_adj = adj_tets[0]
530
+ iface_adj = np.argwhere(tet_to_tri[:,itet_adj]==triids[iface])[0][0]
531
+
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
+
535
+
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
542
+
543
+ return error, max_elem_size
544
+
545
+ def compute_error_estimate(field: MWField) -> np.ndarray:
546
+ mesh = field.mesh
547
+
548
+ nodes = mesh.nodes
549
+ tris = mesh.tris
550
+ tets = mesh.tets
551
+ edges = mesh.edges
552
+ centers = mesh.centers
553
+
554
+ As = mesh.areas
555
+ tet_to_edge = mesh.tet_to_edge
556
+ tet_to_tri = mesh.tet_to_tri
557
+ tri_centers = mesh.tri_centers
558
+ tri_to_tet = mesh.tri_to_tet
559
+ tet_to_field = field.basis.tet_to_field
560
+ er = field._der
561
+ ur = field._dur
562
+ Ls = mesh.edge_lengths
563
+
564
+ errors = []
565
+ for key in field._fields.keys():
566
+ excitation = field._fields[key]
567
+
568
+ error, sizes = compute_error_single(nodes, tets, tris, edges,
569
+ centers, excitation, Ls, As,
570
+ tet_to_edge, tet_to_tri, tri_centers,
571
+ tri_to_tet, tet_to_field, er, ur, field.k0)
572
+
573
+ errors.append(error)
574
+
575
+ error = np.max(np.array(errors), axis=0)
576
+ return error, sizes
@@ -105,7 +105,7 @@ class Assembler:
105
105
  def __init__(self, settings: Settings):
106
106
 
107
107
  self.cached_matrices = None
108
- self.settings = settings
108
+ self.settings: Settings = settings
109
109
 
110
110
  def assemble_bma_matrices(self,
111
111
  field: Nedelec2,
@@ -186,11 +186,12 @@ class Assembler:
186
186
 
187
187
  return E, B, np.array(solve_ids), nedlegfield
188
188
 
189
- def assemble_freq_matrix(self, field: Nedelec2,
190
- materials: list[Material],
191
- bcs: list[BoundaryCondition],
192
- frequency: float,
193
- cache_matrices: bool = False) -> SimJob:
189
+ def assemble_freq_matrix(self,
190
+ field: Nedelec2,
191
+ materials: list[Material],
192
+ bcs: list[BoundaryCondition],
193
+ frequency: float,
194
+ cache_matrices: bool = False) -> SimJob:
194
195
  """Assembles the frequency domain FEM matrix
195
196
 
196
197
  Args: