emerge 1.0.1__py3-none-any.whl → 1.0.3__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 (30) hide show
  1. emerge/__init__.py +6 -7
  2. emerge/_emerge/elements/femdata.py +4 -3
  3. emerge/_emerge/elements/nedelec2.py +8 -4
  4. emerge/_emerge/elements/nedleg2.py +6 -2
  5. emerge/_emerge/geo/pcb.py +6 -2
  6. emerge/_emerge/geo/pcb_tools/dxf.py +4 -3
  7. emerge/_emerge/geo/polybased.py +2 -59
  8. emerge/_emerge/mesh3d.py +23 -31
  9. emerge/_emerge/{_cache_check.py → mth/_cache_check.py} +2 -2
  10. emerge/_emerge/mth/optimized.py +69 -3
  11. emerge/_emerge/physics/microwave/__init__.py +0 -1
  12. emerge/_emerge/physics/microwave/assembly/assembler.py +27 -5
  13. emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +2 -3
  14. emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -1
  15. emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +375 -0
  16. emerge/_emerge/physics/microwave/assembly/robinbc.py +37 -38
  17. emerge/_emerge/physics/microwave/microwave_3d.py +11 -19
  18. emerge/_emerge/physics/microwave/microwave_bc.py +30 -17
  19. emerge/_emerge/physics/microwave/microwave_data.py +0 -26
  20. emerge/_emerge/physics/microwave/port_functions.py +4 -4
  21. emerge/_emerge/plot/pyvista/display.py +14 -4
  22. emerge/_emerge/selection.py +2 -1
  23. emerge/_emerge/simmodel.py +1 -1
  24. emerge/_emerge/solver.py +1 -0
  25. emerge/lib.py +0 -2
  26. {emerge-1.0.1.dist-info → emerge-1.0.3.dist-info}/METADATA +16 -4
  27. {emerge-1.0.1.dist-info → emerge-1.0.3.dist-info}/RECORD +30 -29
  28. {emerge-1.0.1.dist-info → emerge-1.0.3.dist-info}/WHEEL +0 -0
  29. {emerge-1.0.1.dist-info → emerge-1.0.3.dist-info}/entry_points.txt +0 -0
  30. {emerge-1.0.1.dist-info → emerge-1.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,375 @@
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
+
19
+ import numpy as np
20
+ from ....elements import Nedelec2
21
+ from scipy.sparse import csr_matrix
22
+ from ....mth.optimized import local_mapping, compute_distances
23
+ from numba import c16, types, f8, i8, njit, prange
24
+
25
+
26
+ ############################################################
27
+ # FIELD MAPPING #
28
+ ############################################################
29
+
30
+ @njit(i8[:,:](i8, i8[:,:], i8[:,:], i8[:,:]), cache=True, nogil=True)
31
+ def local_tri_to_edgeid(itri: int, tris, edges, tri_to_edge) -> np.ndarray:
32
+ global_edge_map = edges[:, tri_to_edge[:,itri]]
33
+ return local_mapping(tris[:, itri], global_edge_map)
34
+
35
+
36
+ @njit(cache=True, fastmath=True, nogil=True)
37
+ def optim_matmul(B: np.ndarray, data: np.ndarray):
38
+ dnew = np.zeros_like(data)
39
+ dnew[0,:] = B[0,0]*data[0,:] + B[0,1]*data[1,:] + B[0,2]*data[2,:]
40
+ dnew[1,:] = B[1,0]*data[0,:] + B[1,1]*data[1,:] + B[1,2]*data[2,:]
41
+ dnew[2,:] = B[2,0]*data[0,:] + B[2,1]*data[1,:] + B[2,2]*data[2,:]
42
+ return dnew
43
+
44
+ @njit(f8[:](f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
45
+ def cross(a: np.ndarray, b: np.ndarray):
46
+ crossv = np.empty((3,), dtype=np.float64)
47
+ crossv[0] = a[1]*b[2] - a[2]*b[1]
48
+ crossv[1] = a[2]*b[0] - a[0]*b[2]
49
+ crossv[2] = a[0]*b[1] - a[1]*b[0]
50
+ return crossv
51
+
52
+ @njit(cache=True, nogil=True)
53
+ def normalize(a: np.ndarray):
54
+ return a/((a[0]**2 + a[1]**2 + a[2]**2)**0.5)
55
+
56
+
57
+ ############################################################
58
+ # GAUSS QUADRATURE IMPLEMENTATION #
59
+ ############################################################
60
+
61
+ @njit(c16(c16[:], c16[:], types.Array(types.float64, 1, 'A', readonly=True)), cache=True, nogil=True)
62
+ def _gqi(v1, v2, W):
63
+ return np.sum(v1*v2*W)
64
+
65
+
66
+ ############################################################
67
+ # BASIS FUNCTION DERIVATIVES #
68
+ ############################################################
69
+
70
+
71
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
72
+ def _curl_edge_1(coeff, coords):
73
+ a1, b1, c1 = coeff[:,0]
74
+ a2, b2, c2 = coeff[:,1]
75
+ xs = coords[0,:]
76
+ ys = coords[1,:]
77
+ out = -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
78
+ return out
79
+
80
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
81
+ def _curl_edge_2(coeff, coords):
82
+ a1, b1, c1 = coeff[:,0]
83
+ a2, b2, c2 = coeff[:,1]
84
+ xs = coords[0,:]
85
+ ys = coords[1,:]
86
+ out = -3*a2*b1*c2 + 3*a2*b2*c1 - 3*b1*b2*c2*xs - 3*b1*c2**2*ys + 3*b2**2*c1*xs + 3*b2*c1*c2*ys + 0j
87
+ return out
88
+
89
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
90
+ def _curl_face_1(coeff, coords):
91
+ a1, b1, c1 = coeff[:,0]
92
+ a2, b2, c2 = coeff[:,1]
93
+ a3, b3, c3 = coeff[:,2]
94
+ xs = coords[0,:]
95
+ ys = coords[1,:]
96
+ out = -b2*(c1*(a3 + b3*xs + c3*ys) - c3*(a1 + b1*xs + c1*ys)) + c2*(b1*(a3 + b3*xs + c3*ys) - b3*(a1 + b1*xs + c1*ys)) + 2*(b1*c3 - b3*c1)*(a2 + b2*xs + c2*ys) + 0j
97
+ return out
98
+
99
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
100
+ def _curl_face_2(coeff, coords):
101
+ a1, b1, c1 = coeff[:,0]
102
+ a2, b2, c2 = coeff[:,1]
103
+ a3, b3, c3 = coeff[:,2]
104
+ xs = coords[0,:]
105
+ ys = coords[1,:]
106
+ out = b3*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) - c3*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) - 2*(b1*c2 - b2*c1)*(a3 + b3*xs + c3*ys) + 0j
107
+ return out
108
+
109
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
110
+ def _divergence_edge_1(coeff, coords):
111
+ a1, b1, c1 = coeff[:,0]
112
+ a2, b2, c2 = coeff[:,1]
113
+ xs = coords[0,:]
114
+ ys = coords[1,:]
115
+ out = b1*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) + c1*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) + 0j
116
+ return out
117
+
118
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
119
+ def _divergence_edge_2(coeff, coords):
120
+ a1, b1, c1 = coeff[:,0]
121
+ a2, b2, c2 = coeff[:,1]
122
+ xs = coords[0,:]
123
+ ys = coords[1,:]
124
+ out = b2*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) + c2*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) + 0j
125
+ return out
126
+
127
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
128
+ def _divergence_face_1(coeff, coords):
129
+ a1, b1, c1 = coeff[:,0]
130
+ a2, b2, c2 = coeff[:,1]
131
+ a3, b3, c3 = coeff[:,2]
132
+ xs = coords[0,:]
133
+ ys = coords[1,:]
134
+ out = -b2*(b1*(a3 + b3*xs + c3*ys) - b3*(a1 + b1*xs + c1*ys)) - c2*(c1*(a3 + b3*xs + c3*ys) - c3*(a1 + b1*xs + c1*ys)) + 0j
135
+ return out
136
+
137
+ @njit(c16[:](f8[:,:], f8[:,:]), cache=True)
138
+ def _divergence_face_2(coeff, coords):
139
+ a1, b1, c1 = coeff[:,0]
140
+ a2, b2, c2 = coeff[:,1]
141
+ a3, b3, c3 = coeff[:,2]
142
+ xs = coords[0,:]
143
+ ys = coords[1,:]
144
+ out = b3*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) + c3*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) + 0j
145
+ return out
146
+
147
+ ############################################################
148
+ # TRIANGLE BARYCENTRIC COORDINATE LIN. COEFFICIENTS #
149
+ ############################################################
150
+
151
+
152
+ @njit(types.Tuple((f8[:], f8[:], f8[:], f8))(f8[:], f8[:]), cache = True, nogil=True)
153
+ def tri_coefficients(vxs, vys):
154
+
155
+ x1, x2, x3 = vxs
156
+ y1, y2, y3 = vys
157
+
158
+ a1 = x2*y3-y2*x3
159
+ a2 = x3*y1-y3*x1
160
+ a3 = x1*y2-y1*x2
161
+ b1 = y2-y3
162
+ b2 = y3-y1
163
+ b3 = y1-y2
164
+ c1 = x3-x2
165
+ c2 = x1-x3
166
+ c3 = x2-x1
167
+
168
+ sA = 0.5*(((x1-x3)*(y2-y1) - (x1-x2)*(y3-y1)))
169
+ sign = np.sign(sA)
170
+ A = np.abs(sA)
171
+ As = np.array([a1, a2, a3])*sign
172
+ Bs = np.array([b1, b2, b3])*sign
173
+ Cs = np.array([c1, c2, c3])*sign
174
+ return As, Bs, Cs, A
175
+
176
+
177
+ ############################################################
178
+ # GAUSS QUADRATURE POINTS #
179
+ ############################################################
180
+
181
+
182
+ DPTS = np.array([[0.22338159, 0.22338159, 0.22338159, 0.10995174, 0.10995174, 0.10995174],
183
+ [0.10810302, 0.44594849, 0.44594849, 0.81684757, 0.09157621, 0.09157621],
184
+ [0.44594849, 0.44594849, 0.10810302, 0.09157621, 0.09157621, 0.81684757],
185
+ [0.44594849, 0.10810302, 0.44594849, 0.09157621, 0.81684757, 0.09157621]], dtype=np.float64)
186
+
187
+
188
+ ############################################################
189
+ # NUMBA OPTIMIZED ASSEMBLER #
190
+ ############################################################
191
+
192
+
193
+ @njit(c16[:,:](f8[:,:], i8[:,:], c16), cache=True, nogil=True)
194
+ def _abc_order_2_terms(tri_vertices, local_edge_map, cf):
195
+ '''ABC order 2 tangent gradient term'''
196
+
197
+ origin = tri_vertices[:,0]
198
+ vertex_2 = tri_vertices[:,1]
199
+ vertex_3 = tri_vertices[:,2]
200
+
201
+ edge_1 = vertex_2-origin
202
+ edge_2 = vertex_3-origin
203
+
204
+ zhat = normalize(cross(edge_1, edge_2))
205
+ xhat = normalize(edge_1)
206
+ yhat = normalize(cross(zhat, xhat))
207
+
208
+ basis = np.zeros((3,3), dtype=np.float64)
209
+ basis[0,:] = xhat
210
+ basis[1,:] = yhat
211
+ basis[2,:] = zhat
212
+
213
+ local_vertices = optim_matmul(basis, tri_vertices - origin[:,np.newaxis])
214
+
215
+ CurlMatrix = np.zeros((8,8), dtype=np.complex128)
216
+ DivMatrix = np.zeros((8,8), dtype=np.complex128)
217
+
218
+ Lengths = np.ones((8,8), dtype=np.float64)
219
+
220
+ WEIGHTS = DPTS[0,:]
221
+ DPTS1 = DPTS[1,:]
222
+ DPTS2 = DPTS[2,:]
223
+ DPTS3 = DPTS[3,:]
224
+
225
+ xpts = local_vertices[0,:]
226
+ ypts = local_vertices[1,:]
227
+
228
+ distances = compute_distances(xpts, ypts, 0*xpts)
229
+
230
+ xs = xpts[0]*DPTS1 + xpts[1]*DPTS2 + xpts[2]*DPTS3
231
+ ys = ypts[0]*DPTS1 + ypts[1]*DPTS2 + ypts[2]*DPTS3
232
+
233
+ int_coords = np.empty((2,xs.shape[0]), dtype=np.float64)
234
+ int_coords[0,:] = xs
235
+ int_coords[1,:] = ys
236
+
237
+ aas, bbs, ccs, Area = tri_coefficients(xpts, ypts)
238
+
239
+ bary_coeff = np.empty((3,3), dtype=np.float64)
240
+ bary_coeff[0,:] = aas/(2*Area)
241
+ bary_coeff[1,:] = bbs/(2*Area)
242
+ bary_coeff[2,:] = ccs/(2*Area)
243
+
244
+ Lengths[3,:] *= distances[0,2]
245
+ Lengths[7,:] *= distances[0,1]
246
+ Lengths[:,3] *= distances[0,2]
247
+ Lengths[:,7] *= distances[0,1]
248
+
249
+ FF1C = _curl_face_1(bary_coeff, int_coords)
250
+ FF2C = _curl_face_2(bary_coeff, int_coords)
251
+ FF1D = _divergence_face_1(bary_coeff, int_coords)
252
+ FF2D = _divergence_face_2(bary_coeff, int_coords)
253
+
254
+ for iv1 in range(3):
255
+ ie1 = local_edge_map[:, iv1]
256
+
257
+ Le = distances[ie1[0], ie1[1]]
258
+ Lengths[iv1,:] *= Le
259
+ Lengths[iv1+4,:] *= Le
260
+ Lengths[:,iv1] *= Le
261
+ Lengths[:,iv1+4] *= Le
262
+
263
+ FE1C_1 = _curl_edge_1(bary_coeff[:,ie1], int_coords)
264
+ FE2C_1 = _curl_edge_2(bary_coeff[:,ie1], int_coords)
265
+ FE1D_1 = _divergence_edge_1(bary_coeff[:,ie1], int_coords)
266
+ FE2D_1 = _divergence_edge_2(bary_coeff[:,ie1], int_coords)
267
+
268
+ for iv2 in range(3):
269
+ ie2 = local_edge_map[:, iv2]
270
+
271
+ FE1C_2 = _curl_edge_1(bary_coeff[:,ie2], int_coords)
272
+ FE2C_2 = _curl_edge_2(bary_coeff[:,ie2], int_coords)
273
+ FE1D_2 = _divergence_edge_1(bary_coeff[:,ie2], int_coords)
274
+ FE2D_2 = _divergence_edge_2(bary_coeff[:,ie2], int_coords)
275
+
276
+ CurlMatrix[iv1, iv2] = _gqi(FE1C_1, FE1C_2, WEIGHTS)
277
+ CurlMatrix[iv1, iv2+4] = _gqi(FE1C_1, FE2C_2, WEIGHTS)
278
+ CurlMatrix[iv1+4, iv2] = _gqi(FE2C_1, FE1C_2, WEIGHTS)
279
+ CurlMatrix[iv1+4, iv2+4] = _gqi(FE2C_1, FE2C_2, WEIGHTS)
280
+
281
+ DivMatrix[iv1, iv2] = _gqi(FE1D_1, FE1D_2, WEIGHTS)
282
+ DivMatrix[iv1, iv2+4] = _gqi(FE1D_1, FE2D_2, WEIGHTS)
283
+ DivMatrix[iv1+4, iv2] = _gqi(FE2D_1, FE1D_2, WEIGHTS)
284
+ DivMatrix[iv1+4, iv2+4] = _gqi(FE2D_1, FE2D_2, WEIGHTS)
285
+
286
+ CurlMatrix[iv1, 3] = _gqi(FE1C_1, FF1C, WEIGHTS)
287
+ CurlMatrix[iv1+4,3] = _gqi(FE2C_1, FF1C, WEIGHTS)
288
+ CurlMatrix[iv1, 7] = _gqi(FE1C_1, FF2C, WEIGHTS)
289
+ CurlMatrix[iv1+4,7] = _gqi(FE2C_1, FF2C, WEIGHTS)
290
+
291
+ CurlMatrix[3, iv1] = CurlMatrix[iv1, 3]
292
+ CurlMatrix[3, iv1+4] = CurlMatrix[iv1+4, 3]
293
+ CurlMatrix[7, iv1] = CurlMatrix[iv1, 7]
294
+ CurlMatrix[7, iv1+4] = CurlMatrix[iv1+4, 7]
295
+
296
+ DivMatrix[iv1, 3] = _gqi(FE1D_1, FF1D, WEIGHTS)
297
+ DivMatrix[iv1+4,3] = _gqi(FE2D_1, FF1D, WEIGHTS)
298
+ DivMatrix[iv1, 7] = _gqi(FE1D_1, FF2D, WEIGHTS)
299
+ DivMatrix[iv1+4,7] = _gqi(FE2D_1, FF2D, WEIGHTS)
300
+
301
+ DivMatrix[3, iv1] = DivMatrix[iv1, 3]
302
+ DivMatrix[3, iv1+4] = DivMatrix[iv1+4, 3]
303
+ DivMatrix[7, iv1] = DivMatrix[iv1, 7]
304
+ DivMatrix[7, iv1+4] = DivMatrix[iv1+4, 7]
305
+
306
+ CurlMatrix[3, 3] = _gqi(FF1C, FF1C, WEIGHTS)
307
+ CurlMatrix[3, 7] = _gqi(FF1C, FF2C, WEIGHTS)
308
+ CurlMatrix[7, 3] = _gqi(FF2C, FF1C, WEIGHTS)
309
+ CurlMatrix[7, 7] = _gqi(FF2C, FF2C, WEIGHTS)
310
+
311
+ DivMatrix[3, 3] = _gqi(FF1D, FF1D, WEIGHTS)
312
+ DivMatrix[3, 7] = _gqi(FF1D, FF2D, WEIGHTS)
313
+ DivMatrix[7, 3] = _gqi(FF2D, FF1D, WEIGHTS)
314
+ DivMatrix[7, 7] = _gqi(FF2D, FF2D, WEIGHTS)
315
+
316
+ Mat = cf*Lengths*(CurlMatrix-DivMatrix)*np.abs(Area)
317
+ return Mat
318
+
319
+
320
+ ############################################################
321
+ # NUMBA OPTIMIZED INTEGRAL OVER TETS #
322
+ ############################################################
323
+
324
+ @njit((c16[:])(f8[:,:],
325
+ i8[:,:],
326
+ i8[:,:],
327
+ i8[:,:],
328
+ i8[:],
329
+ c16), cache=True, nogil=True, parallel=True)
330
+ def _matrix_builder(nodes, tris, edges, tri_to_field, tri_ids, coeff):
331
+ """ Numba optimized loop over each face triangle."""
332
+ ntritot = tris.shape[1]
333
+ nnz = ntritot*64
334
+
335
+ Mat = np.zeros(nnz, dtype=np.complex128)
336
+
337
+ tri_to_edge = tri_to_field[:3,:]
338
+
339
+ Ntris = tri_ids.shape[0]
340
+ for itri_sub in prange(Ntris): # type: ignore
341
+
342
+ itri = tri_ids[itri_sub]
343
+ p = itri*64
344
+
345
+ # Construct a local mapping to global triangle orientations
346
+ local_tri_map = local_tri_to_edgeid(itri, tris, edges, tri_to_edge)
347
+
348
+ # Construct the local edge map
349
+ tri_nodes = nodes[:, tris[:,itri]]
350
+ subMat = _abc_order_2_terms(tri_nodes, local_tri_map, coeff)
351
+
352
+ Mat[p:p+64] += subMat.ravel()
353
+
354
+ return Mat
355
+
356
+ ############################################################
357
+ # PYTHON INTERFACE #
358
+ ############################################################
359
+
360
+
361
+ def abc_order_2_matrix(field: Nedelec2,
362
+ surf_triangle_indices: np.ndarray,
363
+ coeff: complex) -> np.ndarray:
364
+ """Computes the second order absorbing boundary condition correction terms.
365
+
366
+ Args:
367
+ field (Nedelec2): The Basis function object
368
+ surf_triangle_indices (np.ndarray): The surface triangle indices to add
369
+ coeff (complex): The integral coefficient jp2/k0
370
+
371
+ Returns:
372
+ np.ndarray: The resultant matrix items
373
+ """
374
+ Mat = _matrix_builder(field.mesh.nodes, field.mesh.tris, field.mesh.edges, field.tri_to_field, surf_triangle_indices, coeff)
375
+ return Mat
@@ -312,34 +312,31 @@ def compute_bc_entries_excited(vertices_global, tris, Bmat, Bvec, surf_triangle_
312
312
 
313
313
 
314
314
  @njit(c16[:,:](f8[:,:], f8[:], c16), cache=True, nogil=True, parallel=False)
315
- def ned2_tri_stiff(vertices, edge_lengths, gamma):
315
+ def ned2_tri_stiff(glob_vertices, edge_lengths, gamma):
316
316
  ''' Nedelec-2 Triangle Stiffness matrix and forcing vector (For Boundary Condition of the Third Kind)
317
317
 
318
318
  '''
319
319
  local_edge_map = np.array([[0,1,0],[1,2,2]])
320
320
  Bmat = np.zeros((8,8), dtype=np.complex128)
321
321
 
322
- xs = vertices[0,:]
323
- ys = vertices[1,:]
324
- zs = vertices[2,:]
325
-
326
- ax1 = np.array([xs[1]-xs[0], ys[1]-ys[0], zs[1]-zs[0]])
327
- ax2 = np.array([xs[2]-xs[0], ys[2]-ys[0], zs[2]-zs[0]])
328
- ax1 = ax1/np.linalg.norm(ax1)
329
- ax2 = ax2/np.linalg.norm(ax2)
330
-
331
- axn = cross(ax1, ax2)
332
- ax2 = -cross(axn, ax1)
322
+ orig = glob_vertices[:,0]
323
+ v2 = glob_vertices[:,1]
324
+ v3 = glob_vertices[:,2]
325
+
326
+ e1 = v2-orig
327
+ e2 = v3-orig
328
+ zhat = normalize(cross(e1, e2))
329
+ xhat = normalize(e1)
330
+ yhat = normalize(cross(zhat, xhat))
333
331
  basis = np.zeros((3,3), dtype=np.float64)
334
- basis[:,0] = ax1
335
- basis[:,1] = ax2
336
- basis[:,2] = axn
337
- basis = np.linalg.pinv(basis)
338
- lcs_vertices = basis @ np.ascontiguousarray(vertices)
339
-
332
+ basis[0,:] = xhat
333
+ basis[1,:] = yhat
334
+ basis[2,:] = zhat
335
+ lcs_vertices = optim_matmul(basis, glob_vertices - orig[:,np.newaxis])
336
+
340
337
  xs = lcs_vertices[0,:]
341
338
  ys = lcs_vertices[1,:]
342
-
339
+
343
340
  x1, x2, x3 = xs
344
341
  y1, y2, y3 = ys
345
342
 
@@ -359,7 +356,7 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
359
356
  GLs = (GL1, GL2, GL3)
360
357
 
361
358
  Area = 0.5 * np.abs((x1 - x3) * (y2 - y1) - (x1 - x2) * (y3 - y1))
362
-
359
+
363
360
  letters = [1,2,3,4,5,6]
364
361
 
365
362
  tA, tB, tC = letters[0], letters[1], letters[2]
@@ -367,11 +364,12 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
367
364
 
368
365
  Lt1, Lt2 = Ds[2, 0], Ds[1, 0]
369
366
 
367
+
370
368
  COEFF = gamma/(2*Area)**2
371
369
  AREA_COEFF = AREA_COEFF_CACHE_BASE * Area
372
370
  for ei in range(3):
373
371
  ei1, ei2 = local_edge_map[:, ei]
374
- Li = edge_lengths[ei]
372
+ Li = Ds[ei1, ei2]
375
373
 
376
374
  A = letters[ei1]
377
375
  B = letters[ei2]
@@ -381,24 +379,25 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
381
379
 
382
380
  for ej in range(3):
383
381
  ej1, ej2 = local_edge_map[:, ej]
384
- Lj = edge_lengths[ej]
382
+ Lj = Ds[ej1, ej2]
385
383
 
386
384
  C = letters[ej1]
387
385
  D = letters[ej2]
388
386
 
389
387
  GC = GLs[ej1]
390
388
  GD = GLs[ej2]
389
+
391
390
  DAC = dot(GA,GC)
392
391
  DAD = dot(GA,GD)
393
392
  DBC = dot(GB,GC)
394
393
  DBD = dot(GB,GD)
395
394
  LL = Li*Lj
396
-
397
- Bmat[ei,ej] += LL*COEFF*(AREA_COEFF[A,B,C,D]*DAC-AREA_COEFF[A,B,C,C]*DAD-AREA_COEFF[A,A,C,D]*DBC+AREA_COEFF[A,A,C,C]*DBD)
398
- Bmat[ei,ej+4] += LL*COEFF*(AREA_COEFF[A,B,D,D]*DAC-AREA_COEFF[A,B,C,D]*DAD-AREA_COEFF[A,A,D,D]*DBC+AREA_COEFF[A,A,C,D]*DBD)
399
- Bmat[ei+4,ej] += LL*COEFF*(AREA_COEFF[B,B,C,D]*DAC-AREA_COEFF[B,B,C,C]*DAD-AREA_COEFF[A,B,C,D]*DBC+AREA_COEFF[A,B,C,C]*DBD)
400
- Bmat[ei+4,ej+4] += LL*COEFF*(AREA_COEFF[B,B,D,D]*DAC-AREA_COEFF[B,B,C,D]*DAD-AREA_COEFF[A,B,D,D]*DBC+AREA_COEFF[A,B,C,D]*DBD)
401
-
395
+
396
+ Bmat[ei,ej] += LL*(AREA_COEFF[A,B,C,D]*DAC-AREA_COEFF[A,B,C,C]*DAD-AREA_COEFF[A,A,C,D]*DBC+AREA_COEFF[A,A,C,C]*DBD)
397
+ Bmat[ei,ej+4] += LL*(AREA_COEFF[A,B,D,D]*DAC-AREA_COEFF[A,B,C,D]*DAD-AREA_COEFF[A,A,D,D]*DBC+AREA_COEFF[A,A,C,D]*DBD)
398
+ Bmat[ei+4,ej] += LL*(AREA_COEFF[B,B,C,D]*DAC-AREA_COEFF[B,B,C,C]*DAD-AREA_COEFF[A,B,C,D]*DBC+AREA_COEFF[A,B,C,C]*DBD)
399
+ Bmat[ei+4,ej+4] += LL*(AREA_COEFF[B,B,D,D]*DAC-AREA_COEFF[B,B,C,D]*DAD-AREA_COEFF[A,B,D,D]*DBC+AREA_COEFF[A,B,C,D]*DBD)
400
+
402
401
  FA = dot(GA,GtC)
403
402
  FB = dot(GA,GtA)
404
403
  FC = dot(GB,GtC)
@@ -406,15 +405,15 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
406
405
  FE = dot(GA,GtB)
407
406
  FF = dot(GB,GtB)
408
407
 
409
- Bmat[ei,3] += Li*Lt1*COEFF*(AREA_COEFF[A,B,tA,tB]*FA-AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,A,tA,tB]*FC+AREA_COEFF[A,A,tB,tC]*FD)
410
- Bmat[ei,7] += Li*Lt2*COEFF*(AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,B,tC,tA]*FE-AREA_COEFF[A,A,tB,tC]*FD+AREA_COEFF[A,A,tC,tA]*FF)
411
- Bmat[3,ei] += Lt1*Li*COEFF*(AREA_COEFF[tA,tB,A,B]*FA-AREA_COEFF[tA,tB,A,A]*FC-AREA_COEFF[tB,tC,A,B]*FB+AREA_COEFF[tB,tC,A,A]*FD)
412
- Bmat[7,ei] += Lt2*Li*COEFF*(AREA_COEFF[tB,tC,A,B]*FB-AREA_COEFF[tB,tC,A,A]*FD-AREA_COEFF[tC,tA,A,B]*FE+AREA_COEFF[tC,tA,A,A]*FF)
413
- Bmat[ei+4,3] += Li*Lt1*COEFF*(AREA_COEFF[B,B,tA,tB]*FA-AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[A,B,tA,tB]*FC+AREA_COEFF[A,B,tB,tC]*FD)
414
- Bmat[ei+4,7] += Li*Lt2*COEFF*(AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[B,B,tC,tA]*FE-AREA_COEFF[A,B,tB,tC]*FD+AREA_COEFF[A,B,tC,tA]*FF)
415
- Bmat[3,ei+4] += Lt1*Li*COEFF*(AREA_COEFF[tA,tB,B,B]*FA-AREA_COEFF[tA,tB,A,B]*FC-AREA_COEFF[tB,tC,B,B]*FB+AREA_COEFF[tB,tC,A,B]*FD)
416
- Bmat[7,ei+4] += Lt2*Li*COEFF*(AREA_COEFF[tB,tC,B,B]*FB-AREA_COEFF[tB,tC,A,B]*FD-AREA_COEFF[tC,tA,B,B]*FE+AREA_COEFF[tC,tA,A,B]*FF)
417
-
408
+ Bmat[ei,3] += Li*Lt1*(AREA_COEFF[A,B,tA,tB]*FA-AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,A,tA,tB]*FC+AREA_COEFF[A,A,tB,tC]*FD)
409
+ Bmat[ei,7] += Li*Lt2*(AREA_COEFF[A,B,tB,tC]*FB-AREA_COEFF[A,B,tC,tA]*FE-AREA_COEFF[A,A,tB,tC]*FD+AREA_COEFF[A,A,tC,tA]*FF)
410
+ Bmat[3,ei] += Lt1*Li*(AREA_COEFF[tA,tB,A,B]*FA-AREA_COEFF[tA,tB,A,A]*FC-AREA_COEFF[tB,tC,A,B]*FB+AREA_COEFF[tB,tC,A,A]*FD)
411
+ Bmat[7,ei] += Lt2*Li*(AREA_COEFF[tB,tC,A,B]*FB-AREA_COEFF[tB,tC,A,A]*FD-AREA_COEFF[tC,tA,A,B]*FE+AREA_COEFF[tC,tA,A,A]*FF)
412
+ Bmat[ei+4,3] += Li*Lt1*(AREA_COEFF[B,B,tA,tB]*FA-AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[A,B,tA,tB]*FC+AREA_COEFF[A,B,tB,tC]*FD)
413
+ Bmat[ei+4,7] += Li*Lt2*(AREA_COEFF[B,B,tB,tC]*FB-AREA_COEFF[B,B,tC,tA]*FE-AREA_COEFF[A,B,tB,tC]*FD+AREA_COEFF[A,B,tC,tA]*FF)
414
+ Bmat[3,ei+4] += Lt1*Li*(AREA_COEFF[tA,tB,B,B]*FA-AREA_COEFF[tA,tB,A,B]*FC-AREA_COEFF[tB,tC,B,B]*FB+AREA_COEFF[tB,tC,A,B]*FD)
415
+ Bmat[7,ei+4] += Lt2*Li*(AREA_COEFF[tB,tC,B,B]*FB-AREA_COEFF[tB,tC,A,B]*FD-AREA_COEFF[tC,tA,B,B]*FE+AREA_COEFF[tC,tA,A,B]*FF)
416
+
418
417
  H1 = dot(GtA,GtC)
419
418
  H2 = dot(GtA,GtA)
420
419
  H3 = dot(GtA,GtB)
@@ -423,8 +422,8 @@ def ned2_tri_stiff(vertices, edge_lengths, gamma):
423
422
  Bmat[3,7] += Lt1*Lt2*(AREA_COEFF[tA,tB,tB,tC]*H1-AREA_COEFF[tA,tB,tC,tA]*dot(GtB,GtC)-AREA_COEFF[tB,tC,tB,tC]*H2+AREA_COEFF[tB,tC,tC,tA]*H3)
424
423
  Bmat[7,3] += Lt2*Lt1*(AREA_COEFF[tB,tC,tA,tB]*H1-AREA_COEFF[tB,tC,tB,tC]*H2-AREA_COEFF[tC,tA,tA,tB]*dot(GtB,GtC)+AREA_COEFF[tC,tA,tB,tC]*H3)
425
424
  Bmat[7,7] += Lt2*Lt2*(AREA_COEFF[tB,tC,tB,tC]*H2-AREA_COEFF[tB,tC,tC,tA]*H3-AREA_COEFF[tC,tA,tB,tC]*H3+AREA_COEFF[tC,tA,tC,tA]*dot(GtB,GtB))
426
-
427
425
 
426
+ Bmat = Bmat * COEFF
428
427
  return Bmat
429
428
 
430
429
  @njit(c16[:](f8[:,:], i8[:,:], c16[:], f8[:,:], i8[:], c16), cache=True, nogil=True, parallel=False)
@@ -25,7 +25,6 @@ from ...elements.nedelec2 import Nedelec2
25
25
  from ...solver import DEFAULT_ROUTINE, SolveRoutine
26
26
  from ...system import called_from_main_function
27
27
  from ...selection import FaceSelection
28
- from ...mth.optimized import compute_distances
29
28
  from ...settings import Settings
30
29
  from .microwave_bc import MWBoundaryConditionSet, PEC, ModalPort, LumpedPort, PortBC
31
30
  from .microwave_data import MWData
@@ -101,21 +100,6 @@ def shortest_path(xyz1: np.ndarray, xyz2: np.ndarray, Npts: int) -> np.ndarray:
101
100
  path = (1 - t) * p1[:, np.newaxis] + t * p2[:, np.newaxis]
102
101
 
103
102
  return path
104
-
105
- def _pick_central(vertices: np.ndarray) -> np.ndarray:
106
- """Computes the coordinate in the vertex set that has the shortest square distance to all other points.
107
-
108
-
109
- Args:
110
- vertices (np.ndarray): The set of coordinates [3,:]
111
-
112
- Returns:
113
- np.ndarray: The most central point
114
- """
115
- Ds = compute_distances(vertices[0,:], vertices[1,:], vertices[2,:])
116
- sumDs = np.sum(Ds**2, axis=1)
117
- id_central = np.argwhere(sumDs==np.min(sumDs)).flatten()[0]
118
- return vertices[:, id_central].squeeze()
119
103
 
120
104
  class Microwave3D:
121
105
  """The Electrodynamics time harmonic physics class.
@@ -248,6 +232,14 @@ class Microwave3D:
248
232
  self.set_frequency(np.linspace(fmin, fmax, Npoints))
249
233
 
250
234
  def fdense(self, Npoints: int) -> np.ndarray:
235
+ """Return a resampled version of the current frequency range
236
+
237
+ Args:
238
+ Npoints (int): The new number of points
239
+
240
+ Returns:
241
+ np.ndarray: The new frequency axis
242
+ """
251
243
  if len(self.frequencies) == 1:
252
244
  raise ValueError('Only 1 frequency point known. At least two need to be defined.')
253
245
  fmin = min(self.frequencies)
@@ -497,7 +489,7 @@ class Microwave3D:
497
489
  if freq is None:
498
490
  freq = self.frequencies[0]
499
491
 
500
- materials = self.mesh.retreive(self.mesher.volumes)
492
+ materials = self.mesh._get_material_assignment(self.mesher.volumes)
501
493
 
502
494
  ertet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
503
495
  tandtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
@@ -655,7 +647,7 @@ class Microwave3D:
655
647
  if self.basis is None:
656
648
  raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
657
649
 
658
- materials = self.mesh.retreive(self.mesher.volumes)
650
+ materials = self.mesh._get_material_assignment(self.mesher.volumes)
659
651
 
660
652
  ### Does this move
661
653
  logger.debug('Initializing frequency domain sweep.')
@@ -853,7 +845,7 @@ class Microwave3D:
853
845
  if self.basis is None:
854
846
  raise SimulationError('Cannot proceed. The simulation basis class is undefined.')
855
847
 
856
- materials = self.mesh.retreive(self.mesher.volumes)
848
+ materials = self.mesh._get_material_assignment(self.mesher.volumes)
857
849
 
858
850
  # er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
859
851
  # ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
@@ -19,19 +19,17 @@ from __future__ import annotations
19
19
  import numpy as np
20
20
  from loguru import logger
21
21
  from typing import Callable, Literal
22
+ from dataclasses import dataclass
23
+ from collections import defaultdict
22
24
  from ...selection import Selection, FaceSelection
23
25
  from ...cs import CoordinateSystem, Axis, GCS, _parse_axis
24
26
  from ...coord import Line
25
27
  from ...geometry import GeoSurface, GeoObject
26
- from dataclasses import dataclass
27
- from collections import defaultdict
28
28
  from ...bc import BoundaryCondition, BoundaryConditionSet, Periodic
29
29
  from ...periodic import PeriodicCell, HexCell, RectCell
30
30
  from ...material import Material
31
31
  from ...const import Z0, C0, EPS0, MU0
32
32
 
33
-
34
-
35
33
  ############################################################
36
34
  # UTILITY FUNCTIONS #
37
35
  ############################################################
@@ -98,6 +96,15 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
98
96
  self._cell._ports.append(port)
99
97
  return port
100
98
 
99
+ # Checks
100
+ def _is_excited(self) -> bool:
101
+ for bc in self.boundary_conditions:
102
+ if not isinstance(bc, RobinBC):
103
+ continue
104
+ if bc._include_force:
105
+ return True
106
+
107
+ return False
101
108
 
102
109
  ############################################################
103
110
  # BOUNDARY CONDITIONS #
@@ -125,6 +132,7 @@ class RobinBC(BoundaryCondition):
125
132
  _include_stiff: bool = False
126
133
  _include_mass: bool = False
127
134
  _include_force: bool = False
135
+ _isabc: bool = False
128
136
 
129
137
  def __init__(self, selection: GeoSurface | Selection):
130
138
  """A Generalization of any boundary condition of the third kind (Robin).
@@ -264,11 +272,13 @@ class AbsorbingBoundary(RobinBC):
264
272
  _include_stiff: bool = True
265
273
  _include_mass: bool = True
266
274
  _include_force: bool = False
267
-
275
+ _isabc: bool = True
276
+
268
277
  def __init__(self,
269
278
  face: FaceSelection | GeoSurface,
270
- order: int = 1,
271
- origin: tuple | None = None):
279
+ order: int = 2,
280
+ origin: tuple | None = None,
281
+ abctype: Literal['A','B','C','D','E'] = 'B'):
272
282
  """Creates an AbsorbingBoundary condition.
273
283
 
274
284
  Currently only a first order boundary condition is possible. Second order will be supported later.
@@ -286,7 +296,15 @@ class AbsorbingBoundary(RobinBC):
286
296
  self.order: int = order
287
297
  self.origin: tuple = origin
288
298
  self.cs: CoordinateSystem = GCS
289
-
299
+
300
+ self.abctype: Literal['A','B','C','D','E'] = abctype
301
+ self.o2coeffs: tuple[float, float] = {'A': (1.0, -0.5),
302
+ 'B': (1.00023, -0.51555),
303
+ 'C': (1.03084, -0.73631),
304
+ 'D': (1.06103, -0.84883),
305
+ 'E': (1.12500, -1.00000)
306
+ }
307
+
290
308
  def get_basis(self) -> np.ndarray:
291
309
  return np.eye(3)
292
310
 
@@ -306,15 +324,10 @@ class AbsorbingBoundary(RobinBC):
306
324
  Returns:
307
325
  complex: The γ-constant
308
326
  """
309
- if self.order == 2:
310
-
311
- p0 = 1.06103
312
- p2 = -0.84883
313
- ky = k0*0.5
314
- return 1j*k0*p0 - 1j*p2*ky**2/k0
315
- else:
316
- Factor = 1
317
- return 1j*self.get_beta(k0)*Factor
327
+ if self.order==1:
328
+ return 1j*k0
329
+
330
+ return 1j*k0*self.o2coeffs[self.abctype][0]
318
331
 
319
332
 
320
333
  @dataclass