emerge 1.0.6__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of emerge might be problematic. Click here for more details.
- emerge/__init__.py +14 -3
- emerge/_emerge/elements/index_interp.py +45 -0
- emerge/_emerge/geo/pcb.py +132 -59
- emerge/_emerge/geo/shapes.py +11 -7
- emerge/_emerge/geometry.py +23 -8
- emerge/_emerge/logsettings.py +26 -2
- emerge/_emerge/material.py +1 -1
- emerge/_emerge/mesh3d.py +38 -8
- emerge/_emerge/mesher.py +61 -11
- emerge/_emerge/physics/microwave/adaptive_mesh.py +667 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +6 -5
- emerge/_emerge/physics/microwave/microwave_3d.py +251 -88
- emerge/_emerge/physics/microwave/microwave_bc.py +182 -11
- emerge/_emerge/physics/microwave/microwave_data.py +23 -6
- emerge/_emerge/plot/pyvista/display.py +31 -18
- emerge/_emerge/settings.py +124 -6
- emerge/_emerge/simmodel.py +238 -95
- emerge/_emerge/simstate.py +106 -0
- emerge/_emerge/simulation_data.py +11 -23
- emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
- emerge/_emerge/solver.py +2 -2
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/METADATA +9 -4
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/RECORD +26 -24
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/WHEEL +0 -0
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,667 @@
|
|
|
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, prange # type: ignore, p
|
|
22
|
+
from loguru import logger
|
|
23
|
+
|
|
24
|
+
def print_sparam_matrix(pre: str, S: np.ndarray):
|
|
25
|
+
"""
|
|
26
|
+
Print an N x N complex S-parameter matrix in dB∠deg format.
|
|
27
|
+
Magnitude in dB rounded to 2 decimals, phase in degrees with 1 decimal.
|
|
28
|
+
"""
|
|
29
|
+
S = np.asarray(S)
|
|
30
|
+
if S.ndim != 2 or S.shape[0] != S.shape[1]:
|
|
31
|
+
raise ValueError("S must be a square (N x N) complex matrix")
|
|
32
|
+
|
|
33
|
+
N = S.shape[0]
|
|
34
|
+
logger.debug(pre+"S-parameter matrix (dB ∠ deg):")
|
|
35
|
+
|
|
36
|
+
for i in range(N):
|
|
37
|
+
row_str = []
|
|
38
|
+
for j in range(N):
|
|
39
|
+
mag_db = 20 * np.log10(np.abs(S[i, j]) + np.finfo(float).eps)
|
|
40
|
+
phase_deg = np.degrees(np.angle(S[i, j]))
|
|
41
|
+
row_str.append(f"{mag_db:6.2f} dB ∠ {phase_deg:6.1f}°")
|
|
42
|
+
logger.debug(" | ".join(row_str))
|
|
43
|
+
|
|
44
|
+
def compute_convergence(Sold: np.ndarray, Snew: np.ndarray) -> float:
|
|
45
|
+
"""
|
|
46
|
+
Return a single scalar: max |Snew - Sold|.
|
|
47
|
+
Works for shapes (N,N) or (..., N, N); reduces over all axes.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
Sold = np.asarray(Sold)
|
|
51
|
+
Snew = np.asarray(Snew)
|
|
52
|
+
print_sparam_matrix('Old:',Sold)
|
|
53
|
+
print_sparam_matrix('New',Snew)
|
|
54
|
+
if Sold.shape != Snew.shape:
|
|
55
|
+
raise ValueError("Sold and Snew must have identical shapes")
|
|
56
|
+
#amp_conv = float(np.abs(np.abs(Snew) - np.abs(Sold)).max())
|
|
57
|
+
mag_conv = float(np.abs(np.abs(Snew)-np.abs(Sold)).max())
|
|
58
|
+
amp_conv = float(np.abs(Snew - Sold).max())
|
|
59
|
+
phase_conv = float(np.abs(np.angle(np.diag(Sold)/np.diag(Snew))).max()) * 180/np.pi
|
|
60
|
+
return amp_conv, mag_conv, phase_conv
|
|
61
|
+
|
|
62
|
+
def select_refinement_indices(errors: np.ndarray, refine: float) -> np.ndarray:
|
|
63
|
+
"""
|
|
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)
|
|
76
|
+
|
|
77
|
+
# Sorted indices by decreasing amplitude
|
|
78
|
+
sorted_desc = np.argsort(errs)[::-1]
|
|
79
|
+
|
|
80
|
+
# Rule A: threshold by amplitude
|
|
81
|
+
thresh = (1.0 - refine) * errs[sorted_desc[0]]
|
|
82
|
+
A = np.flatnonzero(errs >= thresh)
|
|
83
|
+
|
|
84
|
+
# Rule B: top-k by count
|
|
85
|
+
k = int(np.ceil(refine * N))
|
|
86
|
+
B = sorted_desc[:k]
|
|
87
|
+
|
|
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]]
|
|
95
|
+
|
|
96
|
+
@njit(f8[:](i8, f8[:,:], f8, f8, f8[:]), cache=True, nogil=True, parallel=False)
|
|
97
|
+
def compute_size(id: int, coords: np.ndarray, q: float, scaler: float, dss: np.ndarray) -> float:
|
|
98
|
+
"""Optimized function to compute the size impressed by size constraint points on each other size constraint point.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
id (int): _description_
|
|
102
|
+
coords (np.ndarray): _description_
|
|
103
|
+
q (float): _description_
|
|
104
|
+
scaler (float): _description_
|
|
105
|
+
dss (np.ndarray): _description_
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
float: _description_
|
|
109
|
+
"""
|
|
110
|
+
N = dss.shape[0]
|
|
111
|
+
sizes = np.zeros((N,), dtype=np.float64)-1.0
|
|
112
|
+
x, y, z = coords[:,id]
|
|
113
|
+
for n in prange(N):
|
|
114
|
+
if n == id:
|
|
115
|
+
sizes[n] = dss[n]*scaler
|
|
116
|
+
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
|
|
118
|
+
sizes[n] = nsize
|
|
119
|
+
return sizes
|
|
120
|
+
|
|
121
|
+
@njit(f8[:](f8[:,:], i8, i8[:]), cache=True, nogil=True, parallel=True)
|
|
122
|
+
def nbmin(matrix, axis, include):
|
|
123
|
+
|
|
124
|
+
if axis==0:
|
|
125
|
+
N = matrix.shape[1]
|
|
126
|
+
out = np.empty((N,), dtype=np.float64)
|
|
127
|
+
for n in prange(N):
|
|
128
|
+
out[n] = np.min(matrix[include==1,n])
|
|
129
|
+
return out
|
|
130
|
+
if axis==1:
|
|
131
|
+
N = matrix.shape[0]
|
|
132
|
+
out = np.empty((N,), dtype=np.float64)
|
|
133
|
+
for n in prange(N):
|
|
134
|
+
out[n] = np.min(matrix[n,include==1])
|
|
135
|
+
return out
|
|
136
|
+
else:
|
|
137
|
+
out = np.empty((N,), dtype=np.float64)
|
|
138
|
+
return out
|
|
139
|
+
|
|
140
|
+
@njit(i8(i8, f8[:,:], f8, i8[:]), cache=True, nogil=True, parallel=False)
|
|
141
|
+
def can_remove(index: int, M: np.ndarray, scaling: float, include: np.ndarray) -> int:
|
|
142
|
+
|
|
143
|
+
ratio = np.min(M[index,:] / nbmin(M, 1, include))
|
|
144
|
+
|
|
145
|
+
if ratio > scaling:
|
|
146
|
+
return 1
|
|
147
|
+
return 0
|
|
148
|
+
|
|
149
|
+
@njit(i8[:](f8[:,:], f8, f8[:], f8, f8), cache=True, nogil=True, parallel=False)
|
|
150
|
+
def reduce_point_set(coords: np.ndarray, q: float, dss: np.ndarray, scaler: float, keep_percentage: float) -> list[int]:
|
|
151
|
+
N = dss.shape[0]
|
|
152
|
+
impressed_size = np.zeros((N,N), np.float64)
|
|
153
|
+
|
|
154
|
+
include = np.ones((N,), dtype=np.int64)
|
|
155
|
+
|
|
156
|
+
for n in range(N):
|
|
157
|
+
impressed_size[:,n] = compute_size(n, coords, q, scaler, dss)
|
|
158
|
+
|
|
159
|
+
current_min = nbmin(impressed_size, 1, include)
|
|
160
|
+
|
|
161
|
+
counter = 0
|
|
162
|
+
for i in range(N):
|
|
163
|
+
|
|
164
|
+
if include[i]==0:
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
if (N-counter)/N < keep_percentage:
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
n_imposed = np.sum(impressed_size[:,i] <= (current_min*1.01))
|
|
171
|
+
|
|
172
|
+
if n_imposed == 0:
|
|
173
|
+
include[i] = 0
|
|
174
|
+
counter = counter + 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
ids = np.arange(N)
|
|
179
|
+
output = ids[include==1]
|
|
180
|
+
return output
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@njit(i8[:, :](i8[:], i8[:, :]), cache=True, nogil=True)
|
|
184
|
+
def local_mapping(vertex_ids, triangle_ids):
|
|
185
|
+
"""
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
vertex_ids : 1-D int64 array (length 4)
|
|
189
|
+
Global vertex 0.1005964238ers of one tetrahedron, in *its* order
|
|
190
|
+
(v0, v1, v2, v3).
|
|
191
|
+
|
|
192
|
+
triangle_ids : 2-D int64 array (nTri × 3)
|
|
193
|
+
Each row is a global-ID triple of one face that belongs to this tet.
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
local_tris : 2-D int64 array (nTri × 3)
|
|
198
|
+
Same triangles, but every entry replaced by the local index
|
|
199
|
+
0,1,2,3 that the vertex has inside this tetrahedron.
|
|
200
|
+
(Guaranteed to be ∈{0,1,2,3}; no -1 ever appears if the input
|
|
201
|
+
really belongs to the tet.)
|
|
202
|
+
"""
|
|
203
|
+
ndim = triangle_ids.shape[0]
|
|
204
|
+
ntri = triangle_ids.shape[1]
|
|
205
|
+
out = np.zeros(triangle_ids.shape, dtype=np.int64)
|
|
206
|
+
|
|
207
|
+
for t in range(ntri): # each triangle
|
|
208
|
+
for j in range(ndim): # each vertex in that triangle
|
|
209
|
+
gid = triangle_ids[j, t] # global ID to look up
|
|
210
|
+
|
|
211
|
+
# linear search over the four tet vertices
|
|
212
|
+
for k in range(4):
|
|
213
|
+
if vertex_ids[k] == gid:
|
|
214
|
+
out[j, t] = k # store local index 0-3
|
|
215
|
+
break # stop the k-loop
|
|
216
|
+
|
|
217
|
+
return out
|
|
218
|
+
|
|
219
|
+
@njit(f8(f8[:], f8[:], f8[:]), cache = True, nogil=True)
|
|
220
|
+
def compute_volume(xs, ys, zs):
|
|
221
|
+
x1, x2, x3, x4 = xs
|
|
222
|
+
y1, y2, y3, y4 = ys
|
|
223
|
+
z1, z2, z3, z4 = zs
|
|
224
|
+
|
|
225
|
+
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)
|
|
226
|
+
|
|
227
|
+
@njit(types.Tuple((f8[:], f8[:], f8[:], f8[:], f8))(f8[:], f8[:], f8[:]), cache = True, nogil=True)
|
|
228
|
+
def tet_coefficients(xs, ys, zs):
|
|
229
|
+
## THIS FUNCTION WORKS
|
|
230
|
+
x1, x2, x3, x4 = xs
|
|
231
|
+
y1, y2, y3, y4 = ys
|
|
232
|
+
z1, z2, z3, z4 = zs
|
|
233
|
+
|
|
234
|
+
aas = np.empty((4,), dtype=np.float64)
|
|
235
|
+
bbs = np.empty((4,), dtype=np.float64)
|
|
236
|
+
ccs = np.empty((4,), dtype=np.float64)
|
|
237
|
+
dds = np.empty((4,), dtype=np.float64)
|
|
238
|
+
|
|
239
|
+
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)
|
|
240
|
+
|
|
241
|
+
aas[0] = x2*y3*z4 - x2*y4*z3 - x3*y2*z4 + x3*y4*z2 + x4*y2*z3 - x4*y3*z2
|
|
242
|
+
aas[1] = -x1*y3*z4 + x1*y4*z3 + x3*y1*z4 - x3*y4*z1 - x4*y1*z3 + x4*y3*z1
|
|
243
|
+
aas[2] = x1*y2*z4 - x1*y4*z2 - x2*y1*z4 + x2*y4*z1 + x4*y1*z2 - x4*y2*z1
|
|
244
|
+
aas[3] = -x1*y2*z3 + x1*y3*z2 + x2*y1*z3 - x2*y3*z1 - x3*y1*z2 + x3*y2*z1
|
|
245
|
+
bbs[0] = -y2*z3 + y2*z4 + y3*z2 - y3*z4 - y4*z2 + y4*z3
|
|
246
|
+
bbs[1] = y1*z3 - y1*z4 - y3*z1 + y3*z4 + y4*z1 - y4*z3
|
|
247
|
+
bbs[2] = -y1*z2 + y1*z4 + y2*z1 - y2*z4 - y4*z1 + y4*z2
|
|
248
|
+
bbs[3] = y1*z2 - y1*z3 - y2*z1 + y2*z3 + y3*z1 - y3*z2
|
|
249
|
+
ccs[0] = x2*z3 - x2*z4 - x3*z2 + x3*z4 + x4*z2 - x4*z3
|
|
250
|
+
ccs[1] = -x1*z3 + x1*z4 + x3*z1 - x3*z4 - x4*z1 + x4*z3
|
|
251
|
+
ccs[2] = x1*z2 - x1*z4 - x2*z1 + x2*z4 + x4*z1 - x4*z2
|
|
252
|
+
ccs[3] = -x1*z2 + x1*z3 + x2*z1 - x2*z3 - x3*z1 + x3*z2
|
|
253
|
+
dds[0] = -x2*y3 + x2*y4 + x3*y2 - x3*y4 - x4*y2 + x4*y3
|
|
254
|
+
dds[1] = x1*y3 - x1*y4 - x3*y1 + x3*y4 + x4*y1 - x4*y3
|
|
255
|
+
dds[2] = -x1*y2 + x1*y4 + x2*y1 - x2*y4 - x4*y1 + x4*y2
|
|
256
|
+
dds[3] = x1*y2 - x1*y3 - x2*y1 + x2*y3 + x3*y1 - x3*y2
|
|
257
|
+
|
|
258
|
+
return aas, bbs, ccs, dds, V
|
|
259
|
+
|
|
260
|
+
@njit(c16[:](f8[:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
|
|
261
|
+
def compute_field(coords: np.ndarray,
|
|
262
|
+
vertices: np.ndarray,
|
|
263
|
+
Etet: np.ndarray,
|
|
264
|
+
l_edge_ids: np.ndarray,
|
|
265
|
+
l_tri_ids: np.ndarray):
|
|
266
|
+
|
|
267
|
+
x = coords[0]
|
|
268
|
+
y = coords[1]
|
|
269
|
+
z = coords[2]
|
|
270
|
+
|
|
271
|
+
xvs = vertices[0,:]
|
|
272
|
+
yvs = vertices[1,:]
|
|
273
|
+
zvs = vertices[2,:]
|
|
274
|
+
|
|
275
|
+
a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
|
|
276
|
+
|
|
277
|
+
Em1s = Etet[0:6]
|
|
278
|
+
Ef1s = Etet[6:10]
|
|
279
|
+
Em2s = Etet[10:16]
|
|
280
|
+
Ef2s = Etet[16:20]
|
|
281
|
+
|
|
282
|
+
Exl = 0.0 + 0.0j
|
|
283
|
+
Eyl = 0.0 + 0.0j
|
|
284
|
+
Ezl = 0.0 + 0.0j
|
|
285
|
+
|
|
286
|
+
V1 = (216*V**3)
|
|
287
|
+
for ie in range(6):
|
|
288
|
+
Em1, Em2 = Em1s[ie], Em2s[ie]
|
|
289
|
+
edgeids = l_edge_ids[:, ie]
|
|
290
|
+
a1, a2 = a_s[edgeids]
|
|
291
|
+
b1, b2 = b_s[edgeids]
|
|
292
|
+
c1, c2 = c_s[edgeids]
|
|
293
|
+
d1, d2 = d_s[edgeids]
|
|
294
|
+
x1, x2 = xvs[edgeids]
|
|
295
|
+
y1, y2 = yvs[edgeids]
|
|
296
|
+
z1, z2 = zvs[edgeids]
|
|
297
|
+
F1 = (a1 + b1*x + c1*y + d1*z)
|
|
298
|
+
F2 = (a2 + b2*x + c2*y + d2*z)
|
|
299
|
+
L = np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
|
|
300
|
+
ex = L*(Em1*F1 + Em2*F2)*(b1*F2 - b2*F1)/V1
|
|
301
|
+
ey = L*(Em1*F1 + Em2*F2)*(c1*F2 - c2*F1)/V1
|
|
302
|
+
ez = L*(Em1*F1 + Em2*F2)*(d1*F2 - d2*F1)/V1
|
|
303
|
+
|
|
304
|
+
Exl += ex
|
|
305
|
+
Eyl += ey
|
|
306
|
+
Ezl += ez
|
|
307
|
+
|
|
308
|
+
for ie in range(4):
|
|
309
|
+
Em1, Em2 = Ef1s[ie], Ef2s[ie]
|
|
310
|
+
triids = l_tri_ids[:, ie]
|
|
311
|
+
a1, a2, a3 = a_s[triids]
|
|
312
|
+
b1, b2, b3 = b_s[triids]
|
|
313
|
+
c1, c2, c3 = c_s[triids]
|
|
314
|
+
d1, d2, d3 = d_s[triids]
|
|
315
|
+
|
|
316
|
+
x1, x2, x3 = xvs[l_tri_ids[:, ie]]
|
|
317
|
+
y1, y2, y3 = yvs[l_tri_ids[:, ie]]
|
|
318
|
+
z1, z2, z3 = zvs[l_tri_ids[:, ie]]
|
|
319
|
+
|
|
320
|
+
L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
|
|
321
|
+
L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
|
|
322
|
+
|
|
323
|
+
F1 = (a1 + b1*x + c1*y + d1*z)
|
|
324
|
+
F2 = (a2 + b2*x + c2*y + d2*z)
|
|
325
|
+
F3 = (a3 + b3*x + c3*y + d3*z)
|
|
326
|
+
|
|
327
|
+
Q1 = Em1*L1*F2
|
|
328
|
+
Q2 = Em2*L2*F3
|
|
329
|
+
ex = (-Q1*(b1*F3 - b3*F1) + Q2*(b1*F2 - b2*F1))/V1
|
|
330
|
+
ey = (-Q1*(c1*F3 - c3*F1) + Q2*(c1*F2 - c2*F1))/V1
|
|
331
|
+
ez = (-Q1*(d1*F3 - d3*F1) + Q2*(d1*F2 - d2*F1))/V1
|
|
332
|
+
|
|
333
|
+
Exl += ex
|
|
334
|
+
Eyl += ey
|
|
335
|
+
Ezl += ez
|
|
336
|
+
|
|
337
|
+
out = np.zeros((3,), dtype=np.complex128)
|
|
338
|
+
out[0] = Exl
|
|
339
|
+
out[1] = Eyl
|
|
340
|
+
out[2] = Ezl
|
|
341
|
+
return out
|
|
342
|
+
|
|
343
|
+
@njit(c16[:,:](f8[:,:], f8[:,:], c16[:], i8[:,:], i8[:,:]), cache=True, nogil=True)
|
|
344
|
+
def compute_curl(coords: np.ndarray,
|
|
345
|
+
vertices: np.ndarray,
|
|
346
|
+
Etet: np.ndarray,
|
|
347
|
+
l_edge_ids: np.ndarray,
|
|
348
|
+
l_tri_ids: np.ndarray):
|
|
349
|
+
|
|
350
|
+
x = coords[0,:]
|
|
351
|
+
y = coords[1,:]
|
|
352
|
+
z = coords[2,:]
|
|
353
|
+
|
|
354
|
+
xvs = vertices[0,:]
|
|
355
|
+
yvs = vertices[1,:]
|
|
356
|
+
zvs = vertices[2,:]
|
|
357
|
+
|
|
358
|
+
a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
|
|
359
|
+
|
|
360
|
+
Em1s = Etet[0:6]
|
|
361
|
+
Ef1s = Etet[6:10]
|
|
362
|
+
Em2s = Etet[10:16]
|
|
363
|
+
Ef2s = Etet[16:20]
|
|
364
|
+
|
|
365
|
+
Exl = np.zeros((x.shape[0],), dtype=np.complex128)
|
|
366
|
+
Eyl = np.zeros((x.shape[0],), dtype=np.complex128)
|
|
367
|
+
Ezl = np.zeros((x.shape[0],), dtype=np.complex128)
|
|
368
|
+
|
|
369
|
+
V1 = (216*V**3)
|
|
370
|
+
V2 = (72*V**3)
|
|
371
|
+
|
|
372
|
+
for ie in range(6):
|
|
373
|
+
Em1, Em2 = Em1s[ie], Em2s[ie]
|
|
374
|
+
edgeids = l_edge_ids[:, ie]
|
|
375
|
+
a1, a2 = a_s[edgeids]
|
|
376
|
+
b1, b2 = b_s[edgeids]
|
|
377
|
+
c1, c2 = c_s[edgeids]
|
|
378
|
+
d1, d2 = d_s[edgeids]
|
|
379
|
+
x1, x2 = xvs[edgeids]
|
|
380
|
+
y1, y2 = yvs[edgeids]
|
|
381
|
+
z1, z2 = zvs[edgeids]
|
|
382
|
+
|
|
383
|
+
L = np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
|
|
384
|
+
C1 = Em1*a1
|
|
385
|
+
C2 = Em1*b1
|
|
386
|
+
C3 = Em1*c1
|
|
387
|
+
C4 = Em1*c2
|
|
388
|
+
C5 = Em2*a2
|
|
389
|
+
C6 = Em2*b2
|
|
390
|
+
C7 = Em2*c1
|
|
391
|
+
C8 = Em2*c2
|
|
392
|
+
C9 = Em1*b2
|
|
393
|
+
C10 = Em2*b1
|
|
394
|
+
D1 = c1*d2
|
|
395
|
+
D2 = c2*d1
|
|
396
|
+
D3 = d1*d2
|
|
397
|
+
D4 = d1*d1
|
|
398
|
+
D5 = c2*d2
|
|
399
|
+
D6 = d2*d2
|
|
400
|
+
D7 = b1*d2
|
|
401
|
+
D8 = b2*d1
|
|
402
|
+
D9 = c1*d1
|
|
403
|
+
D10 = b2*d2
|
|
404
|
+
D11 = b1*c2
|
|
405
|
+
D12 = b2*c1
|
|
406
|
+
D13 = c1*c2
|
|
407
|
+
D14 = c1*c1
|
|
408
|
+
D15 = b2*c2
|
|
409
|
+
|
|
410
|
+
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
|
|
411
|
+
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
|
|
412
|
+
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
|
|
413
|
+
Exl += ex
|
|
414
|
+
Eyl += ey
|
|
415
|
+
Ezl += ez
|
|
416
|
+
|
|
417
|
+
for ie in range(4):
|
|
418
|
+
Em1, Em2 = Ef1s[ie], Ef2s[ie]
|
|
419
|
+
triids = l_tri_ids[:, ie]
|
|
420
|
+
a1, a2, a3 = a_s[triids]
|
|
421
|
+
b1, b2, b3 = b_s[triids]
|
|
422
|
+
c1, c2, c3 = c_s[triids]
|
|
423
|
+
d1, d2, d3 = d_s[triids]
|
|
424
|
+
|
|
425
|
+
x1, x2, x3 = xvs[l_tri_ids[:, ie]]
|
|
426
|
+
y1, y2, y3 = yvs[l_tri_ids[:, ie]]
|
|
427
|
+
z1, z2, z3 = zvs[l_tri_ids[:, ie]]
|
|
428
|
+
|
|
429
|
+
L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
|
|
430
|
+
L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
|
|
431
|
+
F1 = (a3 + b3*x + c3*y + d3*z)
|
|
432
|
+
F2 = (a1 + b1*x + c1*y + d1*z)
|
|
433
|
+
F3 = (a2 + b2*x + c2*y + d2*z)
|
|
434
|
+
N1 = (d1*F1 - d3*F2)
|
|
435
|
+
N2 = (c1*F1 - c3*F2)
|
|
436
|
+
N3 = (c1*d3 - c3*d1)
|
|
437
|
+
N4 = (d1*F3 - d2*F2)
|
|
438
|
+
N5 = (c1*F3 - c2*F2)
|
|
439
|
+
D1 = c1*d2
|
|
440
|
+
D2 = c2*d1
|
|
441
|
+
N6 = (D1 - D2)
|
|
442
|
+
N7 = (b1*F1 - b3*F2)
|
|
443
|
+
N8 = (b1*d3 - b3*d1)
|
|
444
|
+
N9 = (b1*F3 - b2*F2)
|
|
445
|
+
D7 = b1*d2
|
|
446
|
+
D8 = b2*d1
|
|
447
|
+
N10 = (D7 - D8)
|
|
448
|
+
D11 = b1*c2
|
|
449
|
+
D12 = b2*c1
|
|
450
|
+
ex = (Em1*L1*(-c2*N1 + d2*N2 + 2*N3*F3) - Em2*L2*(-c3*N4 + d3*N5 + 2*N6*F1))/V1
|
|
451
|
+
ey = (-Em1*L1*(-b2*N1 + d2*N7 + 2*N8*F3) + Em2*L2*(-b3*N4 + d3*N9 + 2*N10*F1))/V1
|
|
452
|
+
ez = (Em1*L1*(-b2*N2 + c2*N7 + 2*(b1*c3 - b3*c1)*F3) - Em2*L2*(-b3*N5 + c3*N9 + 2*(D11 - D12)*F1))/V1
|
|
453
|
+
|
|
454
|
+
Exl += ex
|
|
455
|
+
Eyl += ey
|
|
456
|
+
Ezl += ez
|
|
457
|
+
|
|
458
|
+
out = np.zeros((3,x.shape[0]), dtype=np.complex128)
|
|
459
|
+
out[0,:] = Exl
|
|
460
|
+
out[1,:] = Eyl
|
|
461
|
+
out[2,:] = Ezl
|
|
462
|
+
return out
|
|
463
|
+
|
|
464
|
+
@njit(c16[:](f8[:], f8[:,:], c16[:], i8[:,:], i8[:,:], c16[:,:]), cache=True, nogil=True)
|
|
465
|
+
def compute_curl_curl(coords: np.ndarray,
|
|
466
|
+
vertices: np.ndarray,
|
|
467
|
+
Etet: np.ndarray,
|
|
468
|
+
l_edge_ids: np.ndarray,
|
|
469
|
+
l_tri_ids: np.ndarray,
|
|
470
|
+
Um: np.ndarray):
|
|
471
|
+
|
|
472
|
+
uxx, uxy, uxz = Um[0,0], Um[0,1], Um[0,2]
|
|
473
|
+
uyx, uyy, uyz = Um[1,0], Um[1,1], Um[1,2]
|
|
474
|
+
uzx, uzy, uzz = Um[2,0], Um[2,1], Um[2,2]
|
|
475
|
+
|
|
476
|
+
x = coords[0]
|
|
477
|
+
y = coords[1]
|
|
478
|
+
z = coords[2]
|
|
479
|
+
|
|
480
|
+
xvs = vertices[0,:]
|
|
481
|
+
yvs = vertices[1,:]
|
|
482
|
+
zvs = vertices[2,:]
|
|
483
|
+
|
|
484
|
+
Exl = 0.0 + 0.0j
|
|
485
|
+
Eyl = 0.0 + 0.0j
|
|
486
|
+
Ezl = 0.0 + 0.0j
|
|
487
|
+
|
|
488
|
+
a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
|
|
489
|
+
|
|
490
|
+
Em1s = Etet[0:6]
|
|
491
|
+
Ef1s = Etet[6:10]
|
|
492
|
+
Em2s = Etet[10:16]
|
|
493
|
+
Ef2s = Etet[16:20]
|
|
494
|
+
|
|
495
|
+
V1 = (216*V**3)
|
|
496
|
+
|
|
497
|
+
for ie in range(6):
|
|
498
|
+
Em1, Em2 = Em1s[ie], Em2s[ie]
|
|
499
|
+
edgeids = l_edge_ids[:, ie]
|
|
500
|
+
a1, a2 = a_s[edgeids]
|
|
501
|
+
b1, b2 = b_s[edgeids]
|
|
502
|
+
c1, c2 = c_s[edgeids]
|
|
503
|
+
d1, d2 = d_s[edgeids]
|
|
504
|
+
x1, x2 = xvs[edgeids]
|
|
505
|
+
y1, y2 = yvs[edgeids]
|
|
506
|
+
z1, z2 = zvs[edgeids]
|
|
507
|
+
|
|
508
|
+
L1 = np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
|
|
509
|
+
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))
|
|
510
|
+
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
|
+
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
|
+
|
|
513
|
+
Exl += ex*V1
|
|
514
|
+
Eyl += ey*V1
|
|
515
|
+
Ezl += ez*V1
|
|
516
|
+
|
|
517
|
+
for ie in range(4):
|
|
518
|
+
Em1, Em2 = Ef1s[ie], Ef2s[ie]
|
|
519
|
+
triids = l_tri_ids[:, ie]
|
|
520
|
+
a1, a2, a3 = a_s[triids]
|
|
521
|
+
b1, b2, b3 = b_s[triids]
|
|
522
|
+
c1, c2, c3 = c_s[triids]
|
|
523
|
+
d1, d2, d3 = d_s[triids]
|
|
524
|
+
|
|
525
|
+
x1, x2, x3 = xvs[l_tri_ids[:, ie]]
|
|
526
|
+
y1, y2, y3 = yvs[l_tri_ids[:, ie]]
|
|
527
|
+
z1, z2, z3 = zvs[l_tri_ids[:, ie]]
|
|
528
|
+
|
|
529
|
+
L1 = np.sqrt((x1-x3)**2 + (y1-y3)**2 + (z1-z3)**2)
|
|
530
|
+
L2 = np.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
|
|
531
|
+
|
|
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))))
|
|
535
|
+
|
|
536
|
+
Exl += ex*V1
|
|
537
|
+
Eyl += ey*V1
|
|
538
|
+
Ezl += ez*V1
|
|
539
|
+
|
|
540
|
+
out = np.zeros((3,), dtype=np.complex128)
|
|
541
|
+
out[0] = Exl
|
|
542
|
+
out[1] = Eyl
|
|
543
|
+
out[2] = Ezl
|
|
544
|
+
return out
|
|
545
|
+
|
|
546
|
+
@njit(types.Tuple((f8[:], f8[:]))(f8[:,:], i8[:,:], i8[:,:], i8[:,:], f8[:,:],
|
|
547
|
+
c16[:], f8[:], f8[:], i8[:,:], i8[:,:],
|
|
548
|
+
f8[:,:], i8[:,:], i8[:,:], c16[:], c16[:], f8), cache=True, nogil=True)
|
|
549
|
+
def compute_error_single(nodes, tets, tris, edges, centers,
|
|
550
|
+
Efield,
|
|
551
|
+
edge_lengths,
|
|
552
|
+
areas,
|
|
553
|
+
tet_to_edge,
|
|
554
|
+
tet_to_tri,
|
|
555
|
+
tri_centers,
|
|
556
|
+
tri_to_tet,
|
|
557
|
+
tet_to_field,
|
|
558
|
+
er,
|
|
559
|
+
ur,
|
|
560
|
+
k0,) -> np.ndarray:
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
# UNPACK DATA
|
|
564
|
+
ntet = tets.shape[1]
|
|
565
|
+
nedges = edges.shape[1]
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
# INIT POSTERIORI ERROR ESTIMATE
|
|
569
|
+
error = np.zeros((ntet,), dtype=np.float64)
|
|
570
|
+
max_elem_size = np.zeros((ntet,), dtype=np.float64)
|
|
571
|
+
|
|
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)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# Compute Error estimate
|
|
579
|
+
for itet in range(ntet):
|
|
580
|
+
uinv = (1/ur[itet])*np.eye(3)
|
|
581
|
+
ermat = er[itet]*np.eye(3)
|
|
582
|
+
|
|
583
|
+
vertices = nodes[:,tets[:, itet]]
|
|
584
|
+
|
|
585
|
+
g_node_ids = tets[:, itet]
|
|
586
|
+
g_edge_ids = edges[:, tet_to_field[:6, itet]]
|
|
587
|
+
g_tri_ids = tris[:, tet_to_field[6:10, itet]-nedges]
|
|
588
|
+
|
|
589
|
+
l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
|
|
590
|
+
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
|
+
triids = tet_to_tri[:,itet]
|
|
598
|
+
facecoords = tri_centers[:, triids]
|
|
599
|
+
|
|
600
|
+
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))
|
|
603
|
+
tetc = centers[:,itet].flatten()
|
|
604
|
+
|
|
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
|
|
606
|
+
|
|
607
|
+
for iface in range(4):
|
|
608
|
+
i1, i2, i3 = tris[:, triids[iface]]
|
|
609
|
+
normal = outward_normal(nodes[:,i1], nodes[:,i2], nodes[:,i3], tetc).astype(np.complex128)
|
|
610
|
+
|
|
611
|
+
adj_tets = [int(tri_to_tet[j,triids[iface]]) for j in range(2)]
|
|
612
|
+
adj_tets = [num for num in adj_tets if num not in (itet, -1234)]
|
|
613
|
+
|
|
614
|
+
if len(adj_tets) == 0:
|
|
615
|
+
continue
|
|
616
|
+
area = areas[triids[iface]]
|
|
617
|
+
|
|
618
|
+
hfs[iface,itet] = area**0.5
|
|
619
|
+
|
|
620
|
+
itet_adj = adj_tets[0]
|
|
621
|
+
iface_adj = np.argwhere(tet_to_tri[:,itet_adj]==triids[iface])[0][0]
|
|
622
|
+
|
|
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
|
+
|
|
626
|
+
|
|
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
|
|
633
|
+
|
|
634
|
+
return error, max_elem_size
|
|
635
|
+
|
|
636
|
+
def compute_error_estimate(field: MWField) -> np.ndarray:
|
|
637
|
+
mesh = field.mesh
|
|
638
|
+
|
|
639
|
+
nodes = mesh.nodes
|
|
640
|
+
tris = mesh.tris
|
|
641
|
+
tets = mesh.tets
|
|
642
|
+
edges = mesh.edges
|
|
643
|
+
centers = mesh.centers
|
|
644
|
+
|
|
645
|
+
As = mesh.areas
|
|
646
|
+
tet_to_edge = mesh.tet_to_edge
|
|
647
|
+
tet_to_tri = mesh.tet_to_tri
|
|
648
|
+
tri_centers = mesh.tri_centers
|
|
649
|
+
tri_to_tet = mesh.tri_to_tet
|
|
650
|
+
tet_to_field = field.basis.tet_to_field
|
|
651
|
+
er = field._der
|
|
652
|
+
ur = field._dur
|
|
653
|
+
Ls = mesh.edge_lengths
|
|
654
|
+
|
|
655
|
+
errors = []
|
|
656
|
+
for key in field._fields.keys():
|
|
657
|
+
excitation = field._fields[key]
|
|
658
|
+
|
|
659
|
+
error, sizes = compute_error_single(nodes, tets, tris, edges,
|
|
660
|
+
centers, excitation, Ls, As,
|
|
661
|
+
tet_to_edge, tet_to_tri, tri_centers,
|
|
662
|
+
tri_to_tet, tet_to_field, er, ur, field.k0)
|
|
663
|
+
|
|
664
|
+
errors.append(error)
|
|
665
|
+
|
|
666
|
+
error = np.max(np.array(errors), axis=0)
|
|
667
|
+
return error, sizes
|
|
@@ -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,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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:
|