emerge 0.4.11__py3-none-any.whl → 0.5.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.

@@ -18,6 +18,12 @@
18
18
  from numba import njit, f8, i8, types, c16
19
19
  import numpy as np
20
20
 
21
+
22
+
23
+ ############################################################
24
+ # TRIANGLE GAUSS QUADRATURE POINTS #
25
+ ############################################################
26
+
21
27
  _GAUSQUADTRI = {
22
28
  1: [(1, 1, 1/3, 1/3, 1/3),],
23
29
  2: [(3, 1/3, 2/3, 1/6, 1/6),],
@@ -54,7 +60,9 @@ _GAUSQUADTRI = {
54
60
  (6, 0.009421666963733, 0.009540815400299, 0.066803251012200, 0.923655933587500)]
55
61
  }
56
62
 
57
-
63
+ ############################################################
64
+ # TETRAHEDRON GAUSS QUADRATURE POINTS #
65
+ ############################################################
58
66
 
59
67
  _GAUSQUADTET = {
60
68
  1: [(1, 1, 0.25, 0.25, 0.25, 0.25),],
@@ -69,9 +77,14 @@ _GAUSQUADTET = {
69
77
  (1, 0.1493333333, 0.1005964238, 0.3994035762, 0.3994035762, 0.1005964238),
70
78
  (1, 0.1493333333, 0.1005964238, 0.3994035762, 0.1005964238, 0.3994035762),
71
79
  (1, 0.1493333333, 0.1005964238, 0.1005964238, 0.3994035762, 0.3994035762),],
72
- 5: [()]
80
+ 5: None
73
81
  }
74
82
 
83
+
84
+ ############################################################
85
+ # FUNCTIONS #
86
+ ############################################################
87
+
75
88
  def gaus_quad_tri(p: int) -> np.ndarray:
76
89
  """
77
90
  Returns the duvanant quadrature triangle sample points W, L1, L2, L3, coordinates for a given order p.
@@ -135,6 +148,12 @@ def gaus_quad_tet(p: int) -> np.ndarray:
135
148
  pts = np.array(Pts).T
136
149
  return pts
137
150
 
151
+
152
+
153
+ ############################################################
154
+ # NUMBA COMPILED #
155
+ ############################################################
156
+
138
157
  @njit(types.Tuple((f8[:], f8[:], f8[:], i8[:]))(f8[:,:], i8[:,:], f8[:,:]), cache=True, nogil=True)
139
158
  def generate_int_points_tri(nodes: np.ndarray,
140
159
  triangles: np.ndarray,
@@ -200,14 +219,44 @@ def generate_int_points_tet(nodes: np.ndarray,
200
219
  shape = np.array((nPTS, tets.shape[1]))
201
220
 
202
221
  return xall_flat, yall_flat, zall_flat, shape
203
- ############## 0.1005964238a Compiled
204
222
 
205
223
  @njit(f8(f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
206
- def dot(a: np.ndarray, b: np.ndarray):
224
+ def dot(a: np.ndarray, b: np.ndarray) -> float:
225
+ """Computes a dot product of two 3D vectors
226
+
227
+ Args:
228
+ a (np.ndarray): (3,) array
229
+ b (np.ndarray): (3,) array
230
+
231
+ Returns:
232
+ float: a · b
233
+ """
234
+ return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
235
+
236
+ @njit(c16(c16[:], c16[:]), cache=True, fastmath=True, nogil=True)
237
+ def dot_c(a: np.ndarray, b: np.ndarray) -> complex:
238
+ """Computes the complex dot product of two 3D vectors
239
+
240
+ Args:
241
+ a (np.ndarray): (3,) array
242
+ b (np.ndarray): (3,) array
243
+
244
+ Returns:
245
+ complex: a · b
246
+ """
207
247
  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
208
248
 
209
249
  @njit(f8[:](f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
210
- def cross(a: np.ndarray, b: np.ndarray):
250
+ def cross(a: np.ndarray, b: np.ndarray) -> np.ndarray:
251
+ """Optimized single vector cross product
252
+
253
+ Args:
254
+ a (np.ndarray): (3,) vector a
255
+ b (np.ndarray): (3,) vector b
256
+
257
+ Returns:
258
+ np.ndarray: a ⨉ b
259
+ """
211
260
  crossv = np.empty((3,), dtype=np.float64)
212
261
  crossv[0] = a[1]*b[2] - a[2]*b[1]
213
262
  crossv[1] = a[2]*b[0] - a[0]*b[2]
@@ -216,6 +265,15 @@ def cross(a: np.ndarray, b: np.ndarray):
216
265
 
217
266
  @njit(c16[:](c16[:], c16[:]), cache=True, fastmath=True, nogil=True)
218
267
  def cross_c(a: np.ndarray, b: np.ndarray):
268
+ """Optimized complex single vector cross product
269
+
270
+ Args:
271
+ a (np.ndarray): (3,) vector a
272
+ b (np.ndarray): (3,) vector b
273
+
274
+ Returns:
275
+ np.ndarray: a ⨉ b
276
+ """
219
277
  crossv = np.empty((3,), dtype=np.complex128)
220
278
  crossv[0] = a[1]*b[2] - a[2]*b[1]
221
279
  crossv[1] = a[2]*b[0] - a[0]*b[2]
@@ -223,7 +281,21 @@ def cross_c(a: np.ndarray, b: np.ndarray):
223
281
  return crossv
224
282
 
225
283
  @njit(f8[:](f8[:], f8[:], f8[:], f8[:]), cache=True, nogil=True)
226
- def outward_normal(n1, n2, n3, o):
284
+ def outward_normal(n1: np.ndarray, n2: np.ndarray, n3: np.ndarray, o: np.ndarray) -> np.ndarray:
285
+ """Copmutes an outward normal vector of a triangle spanned by 3 points.
286
+
287
+ Computes the triangle surface (n1, n2, n3) which have normal n.
288
+ The normal is aligned with respect to an origin.
289
+
290
+ Args:
291
+ n1 (np.ndarray): Node 1 (3,) array
292
+ n2 (np.ndarray): Node 2 (3,) array
293
+ n3 (np.ndarray): Node 3 (3,) array
294
+ o (np.ndarray): The alignment origin
295
+
296
+ Returns:
297
+ Node 1 (3,) array: The outward pointing normal
298
+ """
227
299
  e1 = n2-n1
228
300
  e2 = n3-n1
229
301
  n = cross(e1, e2)
@@ -234,16 +306,22 @@ def outward_normal(n1, n2, n3, o):
234
306
  return n*sgn
235
307
 
236
308
  @njit(f8(f8[:], f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
237
- def calc_area(x1: np.ndarray, x2: np.ndarray, x3: np.ndarray):
309
+ def calc_area(x1: np.ndarray, x2: np.ndarray, x3: np.ndarray) -> float:
310
+ """Computes the tirangle surface area of the traingle spun by three nodes.
311
+
312
+ Args:
313
+ x1 (np.ndarray): Node 1 (3,) array
314
+ x2 (np.ndarray): Node 2 (3,) array
315
+ x3 (np.ndarray): Node 3 (3,) array
316
+
317
+ Returns:
318
+ float: The surface area
319
+ """
238
320
  e1 = x2 - x1
239
321
  e2 = x3 - x1
240
322
  av = cross(e1, e2)
241
323
  return np.sqrt(av[0]**2 + av[1]**2 + av[2]**2)/2
242
324
 
243
- @njit(c16(c16[:], c16[:]), cache=True, fastmath=True, nogil=True)
244
- def dot_c(a: np.ndarray, b: np.ndarray):
245
- return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
246
-
247
325
  @njit(i8[:, :](i8[:], i8[:, :]), cache=True, nogil=True)
248
326
  def local_mapping(vertex_ids, triangle_ids):
249
327
  """
@@ -281,7 +359,7 @@ def local_mapping(vertex_ids, triangle_ids):
281
359
  return out
282
360
 
283
361
  @njit(f8[:,:](f8[:], f8[:], f8[:]), cache=True, nogil=True)
284
- def orthonormal_basis(xs, ys, zs):
362
+ def orthonormal_basis(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray):
285
363
  """
286
364
  Returns an orthonormal basis for the tetrahedron defined by the points
287
365
  xs, ys, zs. The basis is given as a 3x3 matrix with the first column being
@@ -325,33 +403,48 @@ def compute_distances(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndar
325
403
  ids = np.array([[0, 0, 1, 1, 2, 2],[1,2,0, 2, 0, 1]], dtype=np.int64)
326
404
 
327
405
  @njit(c16[:,:](c16[:,:]), cache=True, nogil=True)
328
- def matinv(s: np.ndarray) -> np.ndarray:
406
+ def matinv(M: np.ndarray) -> np.ndarray:
407
+ """Optimized matrix inverse of 3x3 matrix
408
+
409
+ Args:
410
+ M (np.ndarray): Input matrix M of shape (3,3)
329
411
 
412
+ Returns:
413
+ np.ndarray: The matrix inverse inv(M)
414
+ """
330
415
  out = np.zeros((3,3), dtype=np.complex128)
331
416
 
332
- if s[0,1]==0 and s[0,2]==0 and s[1,0]==0 and s[1,2]==0 and s[2,0]==0 and s[2,1]==0:
333
- out[0,0] = 1/s[0,0]
334
- out[1,1] = 1/s[1,1]
335
- out[2,2] = 1/s[2,2]
417
+ if M[0,1]==0 and M[0,2]==0 and M[1,0]==0 and M[1,2]==0 and M[2,0]==0 and M[2,1]==0:
418
+ out[0,0] = 1/M[0,0]
419
+ out[1,1] = 1/M[1,1]
420
+ out[2,2] = 1/M[2,2]
336
421
  else:
337
- det = s[0,0]*s[1,1]*s[2,2] - s[0,0]*s[1,2]*s[2,1] - s[0,1]*s[1,0]*s[2,2] + s[0,1]*s[1,2]*s[2,0] + s[0,2]*s[1,0]*s[2,1] - s[0,2]*s[1,1]*s[2,0]
338
- out[0,0] = s[1,1]*s[2,2] - s[1,2]*s[2,1]
339
- out[0,1] = -s[0,1]*s[2,2] + s[0,2]*s[2,1]
340
- out[0,2] = s[0,1]*s[1,2] - s[0,2]*s[1,1]
341
- out[1,0] = -s[1,0]*s[2,2] + s[1,2]*s[2,0]
342
- out[1,1] = s[0,0]*s[2,2] - s[0,2]*s[2,0]
343
- out[1,2] = -s[0,0]*s[1,2] + s[0,2]*s[1,0]
344
- out[2,0] = s[1,0]*s[2,1] - s[1,1]*s[2,0]
345
- out[2,1] = -s[0,0]*s[2,1] + s[0,1]*s[2,0]
346
- out[2,2] = s[0,0]*s[1,1] - s[0,1]*s[1,0]
422
+ det = M[0,0]*M[1,1]*M[2,2] - M[0,0]*M[1,2]*M[2,1] - M[0,1]*M[1,0]*M[2,2] + M[0,1]*M[1,2]*M[2,0] + M[0,2]*M[1,0]*M[2,1] - M[0,2]*M[1,1]*M[2,0]
423
+ out[0,0] = M[1,1]*M[2,2] - M[1,2]*M[2,1]
424
+ out[0,1] = -M[0,1]*M[2,2] + M[0,2]*M[2,1]
425
+ out[0,2] = M[0,1]*M[1,2] - M[0,2]*M[1,1]
426
+ out[1,0] = -M[1,0]*M[2,2] + M[1,2]*M[2,0]
427
+ out[1,1] = M[0,0]*M[2,2] - M[0,2]*M[2,0]
428
+ out[1,2] = -M[0,0]*M[1,2] + M[0,2]*M[1,0]
429
+ out[2,0] = M[1,0]*M[2,1] - M[1,1]*M[2,0]
430
+ out[2,1] = -M[0,0]*M[2,1] + M[0,1]*M[2,0]
431
+ out[2,2] = M[0,0]*M[1,1] - M[0,1]*M[1,0]
347
432
  out = out*det
348
433
  return out
349
434
 
350
-
351
435
  @njit(cache=True, nogil=True)
352
- def matmul(a: np.ndarray, b: np.ndarray):
353
- out = np.empty((3,b.shape[1]), dtype=b.dtype)
354
- out[0,:] = a[0,0]*b[0,:] + a[0,1]*b[1,:] + a[0,2]*b[2,:]
355
- out[1,:] = a[1,0]*b[0,:] + a[1,1]*b[1,:] + a[1,2]*b[2,:]
356
- out[2,:] = a[2,0]*b[0,:] + a[2,1]*b[1,:] + a[2,2]*b[2,:]
436
+ def matmul(M: np.ndarray, vecs: np.ndarray):
437
+ """Executes a basis transformation of vectors (3,N) with a basis matrix M
438
+
439
+ Args:
440
+ M (np.ndarray): A (3,3) basis matrix
441
+ vec (np.ndarray): A (3,N) set of coordinates
442
+
443
+ Returns:
444
+ np.ndarray: The transformed (3,N) set of vectors
445
+ """
446
+ out = np.empty((3,vecs.shape[1]), dtype=vecs.dtype)
447
+ out[0,:] = M[0,0]*vecs[0,:] + M[0,1]*vecs[1,:] + M[0,2]*vecs[2,:]
448
+ out[1,:] = M[1,0]*vecs[0,:] + M[1,1]*vecs[1,:] + M[1,2]*vecs[2,:]
449
+ out[2,:] = M[2,0]*vecs[0,:] + M[2,1]*vecs[1,:] + M[2,2]*vecs[2,:]
357
450
  return out
@@ -0,0 +1,97 @@
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
+ """
20
+ THESE FUNCTIONS ARE MEANT TO QUICKLY COUPLE COORDINATES ON TWO PERIODIC
21
+ BOUNDARIES IN THE SMALLEST AMOUNT OF TIME WITHOUT RISKING FLOAT ROUNDING ERRORS.
22
+
23
+ THE MAPPING IS DONE BY BY SIMPLY FINDING THE CLOSEST COORDINATE AFTER PROJECTION
24
+ BY SEARCHING A LIST SORTED DICTIONARY WISE ON 3D COORDINATES.
25
+
26
+ ALL PAIRED NODES ARE NO LONGER VISITED
27
+ """
28
+
29
+ import numpy as np
30
+ from numba import njit, f8, i8
31
+
32
+
33
+ ############################################################
34
+ # NUMBA COMPILED FUNCTION #
35
+ ############################################################
36
+
37
+
38
+ @njit(i8[:,:](f8[:,:], i8[:], i8[:], f8[:], f8), cache=True, nogil=True)
39
+ def link_coords(coords: np.ndarray, ids1: np.ndarray, ids2: np.ndarray, disp: np.ndarray, dsmax: float) -> np.ndarray:
40
+ D = dsmax**2
41
+ N = ids1.shape[0]
42
+ ids2_mapped = np.zeros_like(ids2)
43
+
44
+ available = np.ones_like(ids2)
45
+ id_start = 0
46
+
47
+ for i1 in range(N):
48
+ ictr = 0
49
+ c1 = coords[:,ids1[i1]]
50
+
51
+ for i2 in range(id_start, N):
52
+ if available[i2] == 0:
53
+ continue
54
+ c2 = coords[:,ids2[i2]]-disp
55
+ dist = (c2[0]-c1[0])**2 + (c2[1]-c1[1])**2 + (c2[2]-c1[2])**2
56
+ if dist > D:
57
+ ictr += 1
58
+ continue
59
+ if ictr==0:
60
+ id_start += 1
61
+ ids2_mapped[i1] = ids2[i2]
62
+ available[i2] = 0
63
+ break
64
+
65
+ out = np.zeros((2, N), dtype=np.int64)
66
+ out[0,:] = ids1
67
+ out[1,:] = ids2_mapped
68
+ return out
69
+
70
+
71
+ ############################################################
72
+ # MAIN PYTHON INTERFACE #
73
+ ############################################################
74
+
75
+ def pair_coordinates(coords: np.ndarray, ids1: list[int], ids2: list[int], disp: np.ndarray, dsmax: float) -> dict[int, int]:
76
+ """ This function finds the mapping between a total coordinate set and two lits of indices.
77
+
78
+ The indices correspond to two faces that are identical but displaced (mesh centroids of periodic boundaries).
79
+
80
+ Args:
81
+ coords (np.ndarray): A total set of coordinates
82
+ ids1 (list[int]): The indices of the source set
83
+ ids2 (list[int]): The indices of the to-be-matched set
84
+ disp (np.ndarray): The displacement vector of shape (3,)
85
+ dsmax (float): The maximum allowed displacement in matchiing
86
+
87
+ Returns:
88
+ dict[int, int]: An int,int mapping of the indices.
89
+ """
90
+ ids1_c_sorted = sorted(ids1, key= lambda x: tuple(coords[:,x]))
91
+ ids2_c_sorted = sorted(ids2, key= lambda x: tuple(coords[:,x]-disp))
92
+
93
+ mapping = link_coords(coords, np.array(ids1_c_sorted), np.array(ids2_c_sorted), disp, dsmax)
94
+
95
+ mapping = {i: j for i,j in zip(mapping[0,:], mapping[1,:])}
96
+
97
+ return mapping
@@ -7,6 +7,11 @@ from .bc import Periodic
7
7
  import numpy as np
8
8
 
9
9
 
10
+
11
+ ############################################################
12
+ # FUNCTIONS #
13
+ ############################################################
14
+
10
15
  def _rotnorm(v: np.ndarray) -> np.ndarray:
11
16
  """Rotate 3D vector field v 90° counterclockwise around z axis.
12
17
 
@@ -33,6 +38,11 @@ def _pair_selection(f1: FaceSelection, f2: FaceSelection, translation: tuple[flo
33
38
 
34
39
 
35
40
 
41
+ ############################################################
42
+ # BASE PERIODIC CELL CLASS #
43
+ ############################################################
44
+
45
+
36
46
  class PeriodicCell:
37
47
 
38
48
  def __init__(self,
@@ -122,6 +132,12 @@ class PeriodicCell:
122
132
  def port_face(self, z: float):
123
133
  raise NotImplementedError('')
124
134
 
135
+
136
+
137
+ ############################################################
138
+ # RECTANGULAR TILING #
139
+ ############################################################
140
+
125
141
  class RectCell(PeriodicCell):
126
142
  """This class represents the unit cell environment of a regular rectangular tiling.
127
143
 
@@ -175,6 +191,12 @@ class RectCell(PeriodicCell):
175
191
  length = z2-z1
176
192
  return poly.extrude(length, cs=GCS.displace(0,0,z1))
177
193
 
194
+
195
+
196
+ ############################################################
197
+ # HEXAGONAL TILING #
198
+ ############################################################
199
+
178
200
  class HexCell(PeriodicCell):
179
201
 
180
202
  def __init__(self,