emerge 0.5.5__py3-none-any.whl → 0.5.6__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 (32) hide show
  1. emerge/__init__.py +3 -0
  2. emerge/_emerge/cs.py +2 -2
  3. emerge/_emerge/elements/ned2_interp.py +21 -26
  4. emerge/_emerge/elements/nedleg2.py +25 -43
  5. emerge/_emerge/geo/shapes.py +26 -3
  6. emerge/_emerge/geometry.py +27 -1
  7. emerge/_emerge/material.py +1 -0
  8. emerge/_emerge/mesh3d.py +63 -14
  9. emerge/_emerge/mesher.py +7 -4
  10. emerge/_emerge/mth/optimized.py +30 -0
  11. emerge/_emerge/periodic.py +46 -16
  12. emerge/_emerge/physics/microwave/assembly/assembler.py +4 -21
  13. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +23 -19
  14. emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +465 -0
  15. emerge/_emerge/physics/microwave/assembly/robinbc.py +59 -18
  16. emerge/_emerge/physics/microwave/microwave_3d.py +22 -4
  17. emerge/_emerge/physics/microwave/microwave_bc.py +101 -35
  18. emerge/_emerge/physics/microwave/microwave_data.py +1 -1
  19. emerge/_emerge/plot/pyvista/display.py +40 -7
  20. emerge/_emerge/plot/pyvista/display_settings.py +1 -0
  21. emerge/_emerge/simmodel.py +15 -1
  22. emerge/_emerge/solve_interfaces/cudss_interface.py +44 -2
  23. emerge/_emerge/solve_interfaces/pardiso_interface.py +1 -0
  24. emerge/_emerge/solver.py +26 -19
  25. emerge/ext.py +4 -0
  26. emerge/lib.py +1 -1
  27. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/METADATA +5 -3
  28. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/RECORD +31 -30
  29. emerge/_emerge/elements/legrange2.py +0 -172
  30. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/WHEEL +0 -0
  31. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/entry_points.txt +0 -0
  32. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,465 @@
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
+ import numpy as np
19
+ from ....elements.nedleg2 import NedelecLegrange2
20
+ from scipy.sparse import csr_matrix
21
+ from numba_progress import ProgressBar, ProgressBarType
22
+ from ....mth.optimized import local_mapping, matinv, compute_distances, gaus_quad_tri
23
+ from numba import c16, types, f8, i8, njit, prange
24
+
25
+
26
+
27
+ ############################################################
28
+ # FIELD MAPPING #
29
+ ############################################################
30
+
31
+ @njit(i8[:,:](i8, i8[:,:], i8[:,:], i8[:,:]), cache=True, nogil=True)
32
+ def local_tri_to_edgeid(itri: int, tris, edges, tri_to_edge) -> np.ndarray:
33
+ global_edge_map = edges[:, tri_to_edge[:,itri]]
34
+ return local_mapping(tris[:, itri], global_edge_map)
35
+
36
+
37
+
38
+ ############################################################
39
+ # PYTHON INTERFACE #
40
+ ############################################################
41
+
42
+ def generelized_eigenvalue_matrix(field: NedelecLegrange2,
43
+ er: np.ndarray,
44
+ ur: np.ndarray,
45
+ basis: np.ndarray,
46
+ k0: float,) -> tuple[csr_matrix, csr_matrix]:
47
+
48
+ tris = field.mesh.tris
49
+ edges = field.mesh.edges
50
+ nodes = field.mesh.nodes
51
+
52
+ nT = tris.shape[1]
53
+ tri_to_field = field.tri_to_field
54
+
55
+ nodes = field.local_nodes
56
+
57
+ with ProgressBar(total=nT, ncols=100, dynamic_ncols=False) as pgb:
58
+ dataE, dataB, rows, cols = _matrix_builder(nodes, tris, edges, tri_to_field, ur, er, k0, pgb)
59
+
60
+ nfield = field.n_field
61
+
62
+ E = csr_matrix((dataE, (rows, cols)), shape=(nfield, nfield))
63
+ B = csr_matrix((dataB, (rows, cols)), shape=(nfield, nfield))
64
+
65
+ return E, B
66
+
67
+
68
+ ############################################################
69
+ # MATRIX MULTIPLICATION #
70
+ ############################################################
71
+
72
+ @njit(c16[:,:](c16[:,:], c16[:,:]), cache=True, nogil=True)
73
+ def matmul(a, b):
74
+ out = np.empty((2,b.shape[1]), dtype=np.complex128)
75
+ out[0,:] = a[0,0]*b[0,:] + a[0,1]*b[1,:]
76
+ out[1,:] = a[1,0]*b[0,:] + a[1,1]*b[1,:]
77
+ return out
78
+
79
+
80
+ ############################################################
81
+ # GAUSS QUADRATURE IMPLEMENTATION #
82
+ ############################################################
83
+
84
+ @njit(c16(c16[:], c16[:], types.Array(types.float64, 1, 'A', readonly=True)), cache=True, nogil=True)
85
+ def _gqi(v1, v2, W):
86
+ return np.sum(v1*v2*W)
87
+
88
+ @njit(c16(c16[:,:], c16[:,:], types.Array(types.float64, 1, 'A', readonly=True)), cache=True, nogil=True)
89
+ def _gqi2(v1, v2, W):
90
+ return np.sum(W*np.sum(v1*v2,axis=0))
91
+
92
+ @njit(c16[:,:](f8[:,:], f8[:,:]), cache=True, nogil=True)
93
+ def _ne1(coeff, coords):
94
+ a1, b1, c1 = coeff[:,0]
95
+ a2, b2, c2 = coeff[:,1]
96
+ xs = coords[0,:]
97
+ ys = coords[1,:]
98
+ out=np.empty((2,xs.shape[0]), dtype=np.complex128)
99
+ out[0,:] = (-b1*(a2 + b2*xs + c2*ys) + b2*(a1 + b1*xs + c1*ys))
100
+ out[1,:] = (-c1*(a2 + b2*xs + c2*ys) + c2*(a1 + b1*xs + c1*ys))
101
+ return out
102
+
103
+ @njit(c16[:,:](f8[:,:], f8[:,:]), cache=True, nogil=True)
104
+ def _ne2(coeff, coords):
105
+ a1, b1, c1 = coeff[:,0]
106
+ a2, b2, c2 = coeff[:,1]
107
+ xs = coords[0,:]
108
+ ys = coords[1,:]
109
+ out=np.empty((2,xs.shape[0]), dtype=np.complex128)
110
+ out[0,:] = (-b1*(a2 + b2*xs + c2*ys) + b2*(a1 + b1*xs + c1*ys))*(a1 - a2 + b1*xs - b2*xs + c1*ys - c2*ys)
111
+ out[1,:] = (-c1*(a2 + b2*xs + c2*ys) + c2*(a1 + b1*xs + c1*ys))*(a1 - a2 + b1*xs - b2*xs + c1*ys - c2*ys)
112
+ return out
113
+
114
+ @njit(c16[:,:](f8[:,:], f8[:,:]), cache=True, nogil=True)
115
+ def _nf1(coeff, coords):
116
+ a1, b1, c1 = coeff[:,0]
117
+ a2, b2, c2 = coeff[:,1]
118
+ a3, b3, c3 = coeff[:,2]
119
+ xs = coords[0,:]
120
+ ys = coords[1,:]
121
+ out=np.empty((2,xs.shape[0]), dtype=np.complex128)
122
+ out[0,:] = -(b1*(a3 + b3*xs + c3*ys) - b3*(a1 + b1*xs + c1*ys))*(a2 + b2*xs + c2*ys) - (b2*(a3 + b3*xs + c3*ys) - b3*(a2 + b2*xs + c2*ys))*(a1 + b1*xs + c1*ys)
123
+ out[1,:] = -(c1*(a3 + b3*xs + c3*ys) - c3*(a1 + b1*xs + c1*ys))*(a2 + b2*xs + c2*ys) + (-c2*(a3 + b3*xs + c3*ys) + c3*(a2 + b2*xs + c2*ys))*(a1 + b1*xs + c1*ys)
124
+ return out
125
+
126
+ @njit(c16[:,:](f8[:,:], f8[:,:]), cache=True, nogil=True)
127
+ def _nf2(coeff, coords):
128
+ a1, b1, c1 = coeff[:,0]
129
+ a2, b2, c2 = coeff[:,1]
130
+ a3, b3, c3 = coeff[:,2]
131
+ xs = coords[0,:]
132
+ ys = coords[1,:]
133
+ out=np.empty((2,xs.shape[0]), dtype=np.complex128)
134
+ out[0,:] = (b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys))*(a3 + b3*xs + c3*ys) + (b1*(a3 + b3*xs + c3*ys) - b3*(a1 + b1*xs + c1*ys))*(a2 + b2*xs + c2*ys)
135
+ out[1,:] = -(-c1*(a2 + b2*xs + c2*ys) + c2*(a1 + b1*xs + c1*ys))*(a3 + b3*xs + c3*ys) + (c1*(a3 + b3*xs + c3*ys) - c3*(a1 + b1*xs + c1*ys))*(a2 + b2*xs + c2*ys)
136
+ return out
137
+
138
+ @njit(c16[:](f8[:], f8[:,:]), cache=True, nogil=True)
139
+ def _lv(coeff, coords):
140
+ a1, b1, c1 = coeff
141
+ xs = coords[0,:]
142
+ ys = coords[1,:]
143
+ return -a1 - b1*xs - c1*ys + 2*(a1 + b1*xs + c1*ys)**2 + 0*1j
144
+
145
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True, nogil=True)
146
+ def _le(coeff, coords):
147
+ a1, b1, c1 = coeff[:,0]
148
+ a2, b2, c2 = coeff[:,1]
149
+ xs = coords[0,:]
150
+ ys = coords[1,:]
151
+ return 4*(a1 + b1*xs + c1*ys)*(a2 + b2*xs + c2*ys)+ 0*1j
152
+
153
+ @njit(c16[:,:](f8[:], f8[:,:]), cache=True, nogil=True)
154
+ def _lv_grad(coeff, coords):
155
+ a1, b1, c1 = coeff
156
+ xs = coords[0,:]
157
+ ys = coords[1,:]
158
+ out=np.empty((2,xs.shape[0]), dtype=np.complex128)
159
+ out[0,:] = b1*(4*a1 + 4*b1*xs + 4*c1*ys - 1)
160
+ out[1,:] = c1*(4*a1 + 4*b1*xs + 4*c1*ys - 1)
161
+ return out
162
+
163
+ @njit(c16[:,:](f8[:,:], f8[:,:]), cache=True, nogil=True)
164
+ def _le_grad(coeff, coords):
165
+ a1, b1, c1 = coeff[:,0]
166
+ a2, b2, c2 = coeff[:,1]
167
+ xs = coords[0,:]
168
+ ys = coords[1,:]
169
+ out=np.empty((2,xs.shape[0]), dtype=np.complex128)
170
+ out[0,:] = 4*b1*(a2 + b2*xs + c2*ys) + 4*b2*(a1 + b1*xs + c1*ys)
171
+ out[1,:] = 4*c1*(a2 + b2*xs + c2*ys) + 4*c2*(a1 + b1*xs + c1*ys)
172
+ return out
173
+
174
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True, nogil=True)
175
+ def _ne1_curl(coeff, coords):
176
+ a1, b1, c1 = coeff[:,0]
177
+ a2, b2, c2 = coeff[:,1]
178
+ xs = coords[0,:]
179
+ ys = coords[1,:]
180
+ return (2*b1*c2 - 2*b2*c1)*np.ones_like(xs) + 0j#-3*a1*b1*c2 + 3*a1*b2*c1 - 3*b1**2*c2*xs + 3*b1*b2*c1*xs - 3*b1*c1*c2*ys + 3*b2*c1**2*ys + 0j
181
+
182
+
183
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True, nogil=True)
184
+ def _ne2_curl(coeff, coords):
185
+ a1, b1, c1 = coeff[:,0]
186
+ a2, b2, c2 = coeff[:,1]
187
+ xs = coords[0,:]
188
+ ys = coords[1,:]
189
+ return (-(b1 - b2)*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) + (c1 - c2)*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) + 2*(b1*c2 - b2*c1)*(a1 - a2 + b1*xs - b2*xs + c1*ys - c2*ys)) + 0j
190
+
191
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True, nogil=True)
192
+ def _nf1_curl(coeff, coords):
193
+ a1, b1, c1 = coeff[:,0]
194
+ a2, b2, c2 = coeff[:,1]
195
+ a3, b3, c3 = coeff[:,2]
196
+ xs = coords[0,:]
197
+ ys = coords[1,:]
198
+ return 3*a1*b2*c3 - 3*a1*b3*c2 + 3*a2*b1*c3 - 3*a2*b3*c1 + 6*b1*b2*c3*xs - 3*b1*b3*c2*xs + 3*b1*c2*c3*ys - 3*b2*b3*c1*xs + 3*b2*c1*c3*ys - 6*b3*c1*c2*ys + 0*1j
199
+
200
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True, nogil=True)
201
+ def _nf2_curl(coeff, coords):
202
+ a1, b1, c1 = coeff[:,0]
203
+ a2, b2, c2 = coeff[:,1]
204
+ a3, b3, c3 = coeff[:,2]
205
+ xs = coords[0,:]
206
+ ys = coords[1,:]
207
+ return -3*a2*b1*c3 + 3*a2*b3*c1 - 3*a3*b1*c2 + 3*a3*b2*c1 - 3*b1*b2*c3*xs - 3*b1*b3*c2*xs - 6*b1*c2*c3*ys + 6*b2*b3*c1*xs + 3*b2*c1*c3*ys + 3*b3*c1*c2*ys + 0*1j
208
+
209
+
210
+ ############################################################
211
+ # TRIANGLE BARYCENTRIC COORDINATE LIN. COEFFICIENTS #
212
+ ############################################################
213
+
214
+
215
+ @njit(types.Tuple((f8[:], f8[:], f8[:], f8))(f8[:], f8[:]), cache = True, nogil=True)
216
+ def tri_coefficients(vxs, vys):
217
+
218
+ x1, x2, x3 = vxs
219
+ y1, y2, y3 = vys
220
+
221
+ a1 = x2*y3-y2*x3
222
+ a2 = x3*y1-y3*x1
223
+ a3 = x1*y2-y1*x2
224
+ b1 = y2-y3
225
+ b2 = y3-y1
226
+ b3 = y1-y2
227
+ c1 = x3-x2
228
+ c2 = x1-x3
229
+ c3 = x2-x1
230
+
231
+ #A = 0.5*(b1*c2 - b2*c1)
232
+ sA = 0.5*(((x1-x3)*(y2-y1) - (x1-x2)*(y3-y1)))
233
+ sign = np.sign(sA)
234
+ A = np.abs(sA)
235
+ As = np.array([a1, a2, a3])*sign
236
+ Bs = np.array([b1, b2, b3])*sign
237
+ Cs = np.array([c1, c2, c3])*sign
238
+ return As, Bs, Cs, A
239
+
240
+
241
+ ############################################################
242
+ # CONSTANT DEFINITION #
243
+ ############################################################
244
+
245
+
246
+ DPTS = np.array([[0.22338159, 0.22338159, 0.22338159, 0.10995174, 0.10995174, 0.10995174],
247
+ [0.10810302, 0.44594849, 0.44594849, 0.81684757, 0.09157621, 0.09157621],
248
+ [0.44594849, 0.44594849, 0.10810302, 0.09157621, 0.09157621, 0.81684757],
249
+ [0.44594849, 0.10810302, 0.44594849, 0.09157621, 0.81684757, 0.09157621]], dtype=np.float64)
250
+
251
+
252
+ ############################################################
253
+ # NUMBA OPTIMIZED ASSEMBLER #
254
+ ############################################################
255
+
256
+
257
+ @njit(types.Tuple((c16[:,:], c16[:,:]))(f8[:,:], i8[:,:], c16[:,:], c16[:,:], f8), cache=True, nogil=True)
258
+ def generalized_matrix_GQ(tri_vertices, local_edge_map, Ms, Mm, k0):
259
+ '''Nedelec-2 Triangle stiffness and mass submatrix'''
260
+ Att = np.zeros((8,8), dtype=np.complex128)
261
+ Btt = np.zeros((8,8), dtype=np.complex128)
262
+
263
+ Dtt = np.zeros((8,8), dtype=np.complex128)
264
+ Dzt = np.zeros((6,8), dtype=np.complex128)
265
+
266
+ Dzz1 = np.zeros((6,6), dtype=np.complex128)
267
+ Dzz2 = np.zeros((6,6), dtype=np.complex128)
268
+
269
+ Ls = np.ones((14,14), dtype=np.float64)
270
+
271
+ WEIGHTS = DPTS[0,:]
272
+ DPTS1 = DPTS[1,:]
273
+ DPTS2 = DPTS[2,:]
274
+ DPTS3 = DPTS[3,:]
275
+
276
+ txs = tri_vertices[0,:]
277
+ tys = tri_vertices[1,:]
278
+
279
+ Ds = compute_distances(txs, tys, 0*txs)
280
+
281
+ xs = txs[0]*DPTS1 + txs[1]*DPTS2 + txs[2]*DPTS3
282
+ ys = tys[0]*DPTS1 + tys[1]*DPTS2 + tys[2]*DPTS3
283
+
284
+ cs = np.empty((2,xs.shape[0]), dtype=np.float64)
285
+ cs[0,:] = xs
286
+ cs[1,:] = ys
287
+
288
+ aas, bbs, ccs, Area = tri_coefficients(txs, tys)
289
+
290
+ coeff = np.empty((3,3), dtype=np.float64)
291
+ coeff[0,:] = aas/(2*Area)
292
+ coeff[1,:] = bbs/(2*Area)
293
+ coeff[2,:] = ccs/(2*Area)
294
+
295
+ Msz = Ms[2,2]
296
+ Mmz = Mm[2,2]
297
+ Ms = Ms[:2,:2]
298
+ Mm = Mm[:2,:2]
299
+
300
+ Ls[3,:] *= Ds[0,2]
301
+ Ls[7,:] *= Ds[0,1]
302
+ Ls[:,3] *= Ds[0,2]
303
+ Ls[:,7] *= Ds[0,1]
304
+
305
+ for iv1 in range(3):
306
+ ie1 = local_edge_map[:, iv1]
307
+
308
+ Le = Ds[ie1[0], ie1[1]]
309
+ Ls[iv1,:] *= Le
310
+ Ls[:,iv1] *= Le
311
+ Ls[iv1+4,:] *= Le
312
+ Ls[:,iv1+4] *= Le
313
+ F1 = _ne1_curl(coeff[:,ie1], cs)
314
+ F2 = _ne2_curl(coeff[:,ie1], cs)
315
+ F3 = _ne1(coeff[:,ie1], cs)
316
+ F4 = _ne2(coeff[:,ie1], cs)
317
+ F5 = _lv_grad(coeff[:,iv1],cs)
318
+ F6 = _le_grad(coeff[:,ie1],cs)
319
+
320
+ for iv2 in range(3):
321
+ ei2 = local_edge_map[:, iv2]
322
+
323
+ H1 = matmul(Ms,_ne1(coeff[:,ei2],cs))
324
+ H2 = matmul(Ms,_ne2(coeff[:,ei2],cs))
325
+
326
+ Att[iv1,iv2] = _gqi(F1, Msz * _ne1_curl(coeff[:,ei2],cs), WEIGHTS)
327
+ Att[iv1+4,iv2] = _gqi(F2, Msz * _ne1_curl(coeff[:,ei2],cs), WEIGHTS)
328
+ Att[iv1,iv2+4] = _gqi(F1, Msz * _ne2_curl(coeff[:,ei2],cs), WEIGHTS)
329
+ Att[iv1+4,iv2+4] = _gqi(F2, Msz * _ne2_curl(coeff[:,ei2],cs), WEIGHTS)
330
+
331
+ Btt[iv1,iv2] = _gqi2(F3, matmul(Mm,_ne1(coeff[:,ei2],cs)), WEIGHTS)
332
+ Btt[iv1+4,iv2] = _gqi2(F4, matmul(Mm,_ne1(coeff[:,ei2],cs)), WEIGHTS)
333
+ Btt[iv1,iv2+4] = _gqi2(F3, matmul(Mm,_ne2(coeff[:,ei2],cs)), WEIGHTS)
334
+ Btt[iv1+4,iv2+4] = _gqi2(F4, matmul(Mm,_ne2(coeff[:,ei2],cs)), WEIGHTS)
335
+
336
+ Dtt[iv1,iv2] = _gqi2(F3, H1, WEIGHTS)
337
+ Dtt[iv1+4,iv2] = _gqi2(F4, H1, WEIGHTS)
338
+ Dtt[iv1,iv2+4] = _gqi2(F3, H2, WEIGHTS)
339
+ Dtt[iv1+4,iv2+4] = _gqi2(F4, H2, WEIGHTS)
340
+
341
+ Dzt[iv1, iv2] = _gqi2(F5, H1, WEIGHTS)
342
+ Dzt[iv1+3, iv2] = _gqi2(F6, H1, WEIGHTS)
343
+ Dzt[iv1, iv2+4] = _gqi2(F5, H2, WEIGHTS)
344
+ Dzt[iv1+3, iv2+4] = _gqi2(F6, H2, WEIGHTS)
345
+
346
+ Dzz1[iv1, iv2] = _gqi2(_lv_grad(coeff[:,iv1], cs), matmul(Ms,_lv_grad(coeff[:,iv2],cs)), WEIGHTS)
347
+ Dzz1[iv1, iv2+3] = _gqi2(_lv_grad(coeff[:,iv1], cs), matmul(Ms,_le_grad(coeff[:,ei2],cs)), WEIGHTS)
348
+ Dzz1[iv1+3, iv2] = _gqi2(_le_grad(coeff[:,ie1], cs), matmul(Ms,_lv_grad(coeff[:,iv2],cs)), WEIGHTS)
349
+ Dzz1[iv1+3, iv2+3] = _gqi2(_le_grad(coeff[:,ie1], cs), matmul(Ms,_le_grad(coeff[:,ei2],cs)), WEIGHTS)
350
+
351
+ Dzz2[iv1, iv2] = _gqi(_lv(coeff[:,iv1], cs), Mmz * _lv(coeff[:,iv2],cs), WEIGHTS)
352
+ Dzz2[iv1, iv2+3] = _gqi(_lv(coeff[:,iv1], cs), Mmz * _le(coeff[:,ei2],cs), WEIGHTS)
353
+ Dzz2[iv1+3, iv2] = _gqi(_le(coeff[:,ie1], cs), Mmz * _lv(coeff[:,iv2],cs), WEIGHTS)
354
+ Dzz2[iv1+3, iv2+3] = _gqi(_le(coeff[:,ie1], cs), Mmz * _le(coeff[:,ei2],cs), WEIGHTS)
355
+
356
+
357
+ G1 = matmul(Mm,_nf1(coeff,cs))
358
+ G2 = matmul(Mm,_nf2(coeff,cs))
359
+ G3 = matmul(Ms,_nf1(coeff,cs))
360
+ G4 = matmul(Ms,_nf2(coeff,cs))
361
+
362
+ Att[iv1,3] = _gqi(F1, Msz * _nf1_curl(coeff,cs), WEIGHTS)
363
+ Att[iv1+4,3] = _gqi(_ne2_curl(coeff[:,ie1], cs), Msz * _nf1_curl(coeff,cs), WEIGHTS)
364
+ Att[iv1,7] = _gqi(F1, Msz * _nf2_curl(coeff,cs), WEIGHTS)
365
+ Att[iv1+4,7] = _gqi(_ne2_curl(coeff[:,ie1], cs), Msz * _nf2_curl(coeff,cs), WEIGHTS)
366
+
367
+ Att[3, iv1] = Att[iv1,3]
368
+ Att[7, iv1] = Att[iv1,7]
369
+ Att[3, iv1+4] = Att[iv1+4,3]
370
+ Att[7, iv1+4] = Att[iv1+4,7]
371
+
372
+ Btt[iv1,3] = _gqi2(F3, G1, WEIGHTS)
373
+ Btt[iv1+4,3] = _gqi2(F4, G1, WEIGHTS)
374
+ Btt[iv1,7] = _gqi2(F3, G2, WEIGHTS)
375
+ Btt[iv1+4,7] = _gqi2(F4, G2, WEIGHTS)
376
+
377
+ Btt[3, iv1] = Btt[iv1,3]
378
+ Btt[7, iv1] = Btt[iv1,7]
379
+ Btt[3, iv1+4] = Btt[iv1+4,3]
380
+ Btt[7, iv1+4] = Btt[iv1+4,7]
381
+
382
+ Dtt[iv1,3] = _gqi2(F3, G3, WEIGHTS)
383
+ Dtt[iv1+4,3] = _gqi2(F4, G3, WEIGHTS)
384
+ Dtt[iv1,7] = _gqi2(F3, G4, WEIGHTS)
385
+ Dtt[iv1+4,7] = _gqi2(F4, G4, WEIGHTS)
386
+
387
+ Dtt[3, iv1] = Dtt[iv1,3]
388
+ Dtt[7, iv1] = Dtt[iv1,7]
389
+ Dtt[3, iv1+4] = Dtt[iv1+4,3]
390
+ Dtt[7, iv1+4] = Dtt[iv1+4,7]
391
+
392
+ Dzt[iv1, 3] = _gqi2(F5, G3, WEIGHTS)
393
+ Dzt[iv1, 7] = _gqi2(F5, G4, WEIGHTS)
394
+ Dzt[iv1+3, 3] = _gqi2(F6, G3, WEIGHTS)
395
+ Dzt[iv1+3, 7] = _gqi2(F6, G4, WEIGHTS)
396
+
397
+ Att[3,3] = _gqi(_nf1_curl(coeff, cs), Msz * _nf1_curl(coeff,cs), WEIGHTS)
398
+ Att[7,3] = _gqi(_nf2_curl(coeff, cs), Msz * _nf1_curl(coeff,cs), WEIGHTS)
399
+ Att[3,7] = _gqi(_nf1_curl(coeff, cs), Msz * _nf2_curl(coeff,cs), WEIGHTS)
400
+ Att[7,7] = _gqi(_nf2_curl(coeff, cs), Msz * _nf2_curl(coeff,cs), WEIGHTS)
401
+
402
+ Btt[3,3] = _gqi2(_nf1(coeff, cs), G1, WEIGHTS)
403
+ Btt[7,3] = _gqi2(_nf2(coeff, cs), G1, WEIGHTS)
404
+ Btt[3,7] = _gqi2(_nf1(coeff, cs), G2, WEIGHTS)
405
+ Btt[7,7] = _gqi2(_nf2(coeff, cs), G2, WEIGHTS)
406
+
407
+ A = np.zeros((14, 14), dtype = np.complex128)
408
+ B = np.zeros((14, 14), dtype = np.complex128)
409
+
410
+ A[:8,:8] = (Att - k0**2 * Btt)
411
+
412
+ B[:8,:8] = Dtt
413
+ B[8:,:8] = Dzt
414
+ B[:8,8:] = Dzt.T
415
+ B[8:,8:] = Dzz1 - k0**2 * Dzz2
416
+
417
+ Ls = np.ones((14,14), dtype=np.float64)
418
+
419
+ B = Ls*B*np.abs(Area)
420
+ A = Ls*A*np.abs(Area)
421
+ return A, B
422
+
423
+
424
+ @njit(types.Tuple((c16[:], c16[:], i8[:], i8[:]))(f8[:,:],
425
+ i8[:,:],
426
+ i8[:,:],
427
+ i8[:,:],
428
+ c16[:,:,:],
429
+ c16[:,:,:],
430
+ f8,
431
+ ProgressBarType), cache=True, nogil=True, parallel=True)
432
+ def _matrix_builder(nodes, tris, edges, tri_to_field, ur, er, k0, pgb: ProgressBar):
433
+
434
+ ntritot = tris.shape[1]
435
+ nnz = ntritot*196
436
+
437
+ rows = np.zeros(nnz, dtype=np.int64)
438
+ cols = np.zeros(nnz, dtype=np.int64)
439
+ dataE = np.zeros_like(rows, dtype=np.complex128)
440
+ dataB = np.zeros_like(rows, dtype=np.complex128)
441
+
442
+ tri_to_edge = tri_to_field[:3,:]
443
+
444
+ for itri in prange(ntritot): # type: ignore
445
+ p = itri*196
446
+ if np.mod(itri,10)==0:
447
+ pgb.update(10)
448
+ urt = ur[:,:,itri]
449
+ ert = er[:,:,itri]
450
+
451
+ # Construct a local mapping to global triangle orientations
452
+ local_tri_map = local_tri_to_edgeid(itri, tris, edges, tri_to_edge)
453
+
454
+ # Construct the local edge map
455
+ tri_nodes = nodes[:, tris[:,itri]]
456
+ Esub, Bsub = generalized_matrix_GQ(tri_nodes,local_tri_map, matinv(urt), ert, k0)
457
+
458
+ indices = tri_to_field[:, itri]
459
+ for ii in range(14):
460
+ rows[p+14*ii:p+14*(ii+1)] = indices[ii]
461
+ cols[p+ii:p+ii+196:14] = indices[ii]
462
+
463
+ dataE[p:p+196] = Esub.ravel()
464
+ dataB[p:p+196] = Bsub.ravel()
465
+ return dataE, dataB, rows, cols
@@ -18,6 +18,7 @@
18
18
 
19
19
  import numpy as np
20
20
  from numba import njit, f8, c16, i8, types, prange
21
+ from ....mth.optimized import cross, matinv_r
21
22
  from ....elements import Nedelec2
22
23
  from typing import Callable
23
24
  from loguru import logger
@@ -86,6 +87,28 @@ def generate_points(vertices_local, tris, DPTs, surf_triangle_indices):
86
87
  yflat = yall.flatten()
87
88
  return xflat, yflat
88
89
 
90
+ @njit(types.Tuple((f8[:],f8[:], f8[:]))(f8[:,:], i8[:,:], f8[:,:], i8[:]), cache=True, nogil=True)
91
+ def generate_points_3d(vertices, tris, DPTs, surf_triangle_indices):
92
+ NS = surf_triangle_indices.shape[0]
93
+ xall = np.zeros((DPTs.shape[1], NS))
94
+ yall = np.zeros((DPTs.shape[1], NS))
95
+ zall = np.zeros((DPTs.shape[1], NS))
96
+ for i in range(NS):
97
+ itri = surf_triangle_indices[i]
98
+ vertex_ids = tris[:, itri]
99
+
100
+ x1, x2, x3 = vertices[0, vertex_ids]
101
+ y1, y2, y3 = vertices[1, vertex_ids]
102
+ z1, z2, z3 = vertices[2, vertex_ids]
103
+
104
+ xall[:,i] = x1*DPTs[1,:] + x2*DPTs[2,:] + x3*DPTs[3,:]
105
+ yall[:,i] = y1*DPTs[1,:] + y2*DPTs[2,:] + y3*DPTs[3,:]
106
+ zall[:,i] = z1*DPTs[1,:] + z2*DPTs[2,:] + z3*DPTs[3,:]
107
+ xflat = xall.flatten()
108
+ yflat = yall.flatten()
109
+ zflat = zall.flatten()
110
+ return xflat, yflat, zflat
111
+
89
112
  @njit(f8[:,:](f8[:], f8[:], f8[:]), cache=True, nogil=True, fastmath=True)
90
113
  def compute_distances(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndarray:
91
114
  N = xs.shape[0]
@@ -96,8 +119,12 @@ def compute_distances(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndar
96
119
  Ds[j,i] = Ds[i,j]
97
120
  return Ds
98
121
 
122
+ @njit(cache=True, nogil=True)
123
+ def normalize(a: np.ndarray):
124
+ return a/((a[0]**2 + a[1]**2 + a[2]**2)**0.5)
125
+
99
126
  @njit(types.Tuple((c16[:,:],c16[:]))(f8[:,:], c16, c16[:,:], f8[:,:]), cache=True, nogil=True, parallel=False)
100
- def ned2_tri_stiff_force(lcs_vertices, gamma, lcs_Uinc, DPTs):
127
+ def ned2_tri_stiff_force(glob_vertices, gamma, glob_Uinc, DPTs):
101
128
  ''' Nedelec-2 Triangle Stiffness matrix and forcing vector (For Boundary Condition of the Third Kind)
102
129
 
103
130
  '''
@@ -105,9 +132,25 @@ def ned2_tri_stiff_force(lcs_vertices, gamma, lcs_Uinc, DPTs):
105
132
  Bmat = np.zeros((8,8), dtype=np.complex128)
106
133
  bvec = np.zeros((8,), dtype=np.complex128)
107
134
 
135
+ orig = glob_vertices[:,0]
136
+ v2 = glob_vertices[:,1]
137
+ v3 = glob_vertices[:,2]
138
+
139
+ e1 = v2-orig
140
+ e2 = v3-orig
141
+ zhat = normalize(cross(e1, e2))
142
+ xhat = normalize(e1)
143
+ yhat = normalize(cross(zhat, xhat))
144
+ basis = np.zeros((3,3), dtype=np.float64)
145
+ basis[0,:] = xhat
146
+ basis[1,:] = yhat
147
+ basis[2,:] = zhat
148
+ lcs_vertices = optim_matmul(basis, glob_vertices - orig[:,np.newaxis])
149
+ lcs_Uinc = optim_matmul(basis, glob_Uinc)
150
+
108
151
  xs = lcs_vertices[0,:]
109
152
  ys = lcs_vertices[1,:]
110
-
153
+
111
154
  x1, x2, x3 = xs
112
155
  y1, y2, y3 = ys
113
156
 
@@ -249,7 +292,7 @@ def ned2_tri_stiff_force(lcs_vertices, gamma, lcs_Uinc, DPTs):
249
292
  return Bmat, bvec
250
293
 
251
294
  @njit(types.Tuple((c16[:], c16[:]))(f8[:,:], i8[:,:], c16[:], c16[:], i8[:], c16, c16[:,:,:], f8[:,:], i8[:,:]), cache=True, nogil=True, parallel=False)
252
- def compute_bc_entries_excited(vertices_local, tris, Bmat, Bvec, surf_triangle_indices, gamma, Ulocal_all, DPTs, tri_to_field):
295
+ def compute_bc_entries_excited(vertices_global, tris, Bmat, Bvec, surf_triangle_indices, gamma, Uglobal_all, DPTs, tri_to_field):
253
296
  N = 64
254
297
  Niter = surf_triangle_indices.shape[0]
255
298
  for i in prange(Niter): # type: ignore
@@ -257,9 +300,9 @@ def compute_bc_entries_excited(vertices_local, tris, Bmat, Bvec, surf_triangle_i
257
300
 
258
301
  vertex_ids = tris[:, itri]
259
302
 
260
- Ulocal = Ulocal_all[:,:, i]
303
+ Ulocal = Uglobal_all[:,:, i]
261
304
 
262
- Bsub, bvec = ned2_tri_stiff_force(vertices_local[:,vertex_ids], gamma, Ulocal, DPTs)
305
+ Bsub, bvec = ned2_tri_stiff_force(vertices_global[:,vertex_ids], gamma, Ulocal, DPTs)
263
306
 
264
307
  indices = tri_to_field[:, itri]
265
308
 
@@ -402,30 +445,28 @@ def compute_bc_entries(vertices, tris, Bmat, all_edge_lengths, surf_triangle_ind
402
445
 
403
446
  def assemble_robin_bc_excited(field: Nedelec2,
404
447
  Bmat: np.ndarray,
405
- surf_triangle_indices: np.ndarray,
406
- Ufunc: Callable,
407
- gamma: complex,
408
- local_basis: np.ndarray,
409
- origin: np.ndarray,
410
- DPTs: np.ndarray):
448
+ surf_triangle_indices: np.ndarray,
449
+ Ufunc: Callable,
450
+ gamma: complex,
451
+ DPTs: np.ndarray):
411
452
 
412
453
  Bvec = np.zeros((field.n_field,), dtype=np.complex128)
413
454
 
414
- vertices_local = optim_matmul(local_basis, field.mesh.nodes - origin[:,np.newaxis])
455
+ vertices = field.mesh.nodes
415
456
 
416
- xflat, yflat = generate_points(vertices_local, field.mesh.tris, DPTs, surf_triangle_indices)
457
+ xflat, yflat, zflat = generate_points_3d(vertices, field.mesh.tris, DPTs, surf_triangle_indices)
417
458
 
418
- Ulocal = Ufunc(xflat, yflat)
459
+ U_global = Ufunc(xflat, yflat, zflat)
419
460
 
420
- Ulocal_all = Ulocal.reshape((3, DPTs.shape[1], surf_triangle_indices.shape[0]))
461
+ U_global_all = U_global.reshape((3, DPTs.shape[1], surf_triangle_indices.shape[0]))
421
462
 
422
- Bmat, Bvec = compute_bc_entries_excited(vertices_local, field.mesh.tris, Bmat, Bvec, surf_triangle_indices, gamma, Ulocal_all, DPTs, field.tri_to_field)
463
+ Bmat, Bvec = compute_bc_entries_excited(vertices, field.mesh.tris, Bmat, Bvec, surf_triangle_indices, gamma, U_global_all, DPTs, field.tri_to_field)
423
464
  return Bmat, Bvec
424
465
 
425
466
  def assemble_robin_bc(field: Nedelec2,
426
467
  Bmat: np.ndarray,
427
- surf_triangle_indices: np.ndarray,
428
- gamma: np.ndarray):
468
+ surf_triangle_indices: np.ndarray,
469
+ gamma: np.ndarray):
429
470
  vertices = field.mesh.nodes
430
471
  all_edge_lengths = field.mesh.edge_lengths[field.mesh.tri_to_edge]
431
472
  Bmat = compute_bc_entries(vertices, field.mesh.tris, Bmat, all_edge_lengths, surf_triangle_indices, gamma)
@@ -24,6 +24,7 @@ from ...elements.nedelec2 import Nedelec2
24
24
  from ...solver import DEFAULT_ROUTINE, SolveRoutine
25
25
  from ...system import called_from_main_function
26
26
  from ...selection import FaceSelection
27
+ from ...mth.optimized import compute_distances
27
28
  from .microwave_bc import MWBoundaryConditionSet, PEC, ModalPort, LumpedPort, PortBC
28
29
  from .microwave_data import MWData
29
30
  from .assembly.assembler import Assembler
@@ -60,7 +61,7 @@ def run_job_multi(job: SimJob) -> SimJob:
60
61
  return job
61
62
 
62
63
 
63
- def _dimstring(data: list[float]) -> str:
64
+ def _dimstring(data: list[float] | np.ndarray) -> str:
64
65
  """A String formatter for dimensions in millimeters
65
66
 
66
67
  Args:
@@ -99,6 +100,21 @@ def shortest_path(xyz1: np.ndarray, xyz2: np.ndarray, Npts: int) -> np.ndarray:
99
100
 
100
101
  return path
101
102
 
103
+ def _pick_central(vertices: np.ndarray) -> np.ndarray:
104
+ """Computes the coordinate in the vertex set that has the shortest square distance to all other points.
105
+
106
+
107
+ Args:
108
+ vertices (np.ndarray): The set of coordinates [3,:]
109
+
110
+ Returns:
111
+ np.ndarray: The most central point
112
+ """
113
+ Ds = compute_distances(vertices[0,:], vertices[1,:], vertices[2,:])
114
+ sumDs = np.sum(Ds**2, axis=1)
115
+ id_central = np.argwhere(sumDs==np.min(sumDs)).flatten()[0]
116
+ return vertices[:, id_central].squeeze()
117
+
102
118
  class Microwave3D:
103
119
  """The Electrodynamics time harmonic physics class.
104
120
 
@@ -302,8 +318,8 @@ class Microwave3D:
302
318
  dotprod = xs*field_axis[0] + ys*field_axis[1] + zs*field_axis[2]
303
319
 
304
320
  start_id = points[np.argwhere(dotprod == np.min(dotprod))]
305
-
306
- start = np.squeeze(np.mean(self.mesh.nodes[:,start_id],axis=1))
321
+
322
+ start = _pick_central(self.mesh.nodes[:,start_id.flatten()])
307
323
  logger.info(f'Starting node = {_dimstring(start)}')
308
324
  end = start + port.Vdirection.np*port.height
309
325
 
@@ -493,6 +509,7 @@ class Microwave3D:
493
509
  if TEM:
494
510
  target_kz = ermean*urmean*1.1*k0
495
511
  else:
512
+
496
513
  target_kz = ermean*urmean*0.7*k0
497
514
 
498
515
 
@@ -514,7 +531,8 @@ class Microwave3D:
514
531
  Emode[solve_ids] = np.squeeze(eigenmode)
515
532
  Emode = Emode * np.exp(-1j*np.angle(np.max(Emode)))
516
533
 
517
- beta = min(k0*np.sqrt(ermax*urmax), np.emath.sqrt(-eigen_values[i]))
534
+ beta_base = np.emath.sqrt(-eigen_values[i])
535
+ beta = min(k0*np.sqrt(ermax*urmax), beta_base)
518
536
 
519
537
  residuals = -1
520
538