emerge 0.4.6__py3-none-any.whl → 0.4.8__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 (80) hide show
  1. emerge/__init__.py +54 -0
  2. emerge/__main__.py +5 -0
  3. emerge/_emerge/__init__.py +42 -0
  4. emerge/_emerge/bc.py +197 -0
  5. emerge/_emerge/coord.py +119 -0
  6. emerge/_emerge/cs.py +523 -0
  7. emerge/_emerge/dataset.py +36 -0
  8. emerge/_emerge/elements/__init__.py +19 -0
  9. emerge/_emerge/elements/femdata.py +212 -0
  10. emerge/_emerge/elements/index_interp.py +64 -0
  11. emerge/_emerge/elements/legrange2.py +172 -0
  12. emerge/_emerge/elements/ned2_interp.py +645 -0
  13. emerge/_emerge/elements/nedelec2.py +140 -0
  14. emerge/_emerge/elements/nedleg2.py +217 -0
  15. emerge/_emerge/geo/__init__.py +24 -0
  16. emerge/_emerge/geo/horn.py +107 -0
  17. emerge/_emerge/geo/modeler.py +449 -0
  18. emerge/_emerge/geo/operations.py +254 -0
  19. emerge/_emerge/geo/pcb.py +1244 -0
  20. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  21. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  22. emerge/_emerge/geo/pmlbox.py +204 -0
  23. emerge/_emerge/geo/polybased.py +529 -0
  24. emerge/_emerge/geo/shapes.py +427 -0
  25. emerge/_emerge/geo/step.py +77 -0
  26. emerge/_emerge/geo2d.py +86 -0
  27. emerge/_emerge/geometry.py +510 -0
  28. emerge/_emerge/howto.py +214 -0
  29. emerge/_emerge/logsettings.py +5 -0
  30. emerge/_emerge/material.py +118 -0
  31. emerge/_emerge/mesh3d.py +730 -0
  32. emerge/_emerge/mesher.py +339 -0
  33. emerge/_emerge/mth/common_functions.py +33 -0
  34. emerge/_emerge/mth/integrals.py +71 -0
  35. emerge/_emerge/mth/optimized.py +357 -0
  36. emerge/_emerge/periodic.py +263 -0
  37. emerge/_emerge/physics/__init__.py +0 -0
  38. emerge/_emerge/physics/microwave/__init__.py +1 -0
  39. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  40. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  41. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  42. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  43. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  44. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  45. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  46. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  47. emerge/_emerge/physics/microwave/periodic.py +82 -0
  48. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  49. emerge/_emerge/physics/microwave/sc.py +175 -0
  50. emerge/_emerge/physics/microwave/simjob.py +147 -0
  51. emerge/_emerge/physics/microwave/sparam.py +138 -0
  52. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  53. emerge/_emerge/plot/__init__.py +0 -0
  54. emerge/_emerge/plot/display.py +394 -0
  55. emerge/_emerge/plot/grapher.py +93 -0
  56. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  57. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  58. emerge/_emerge/plot/pyvista/display.py +931 -0
  59. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  60. emerge/_emerge/plot/simple_plots.py +551 -0
  61. emerge/_emerge/plot.py +225 -0
  62. emerge/_emerge/projects/__init__.py +0 -0
  63. emerge/_emerge/projects/_gen_base.txt +32 -0
  64. emerge/_emerge/projects/_load_base.txt +24 -0
  65. emerge/_emerge/projects/generate_project.py +40 -0
  66. emerge/_emerge/selection.py +596 -0
  67. emerge/_emerge/simmodel.py +444 -0
  68. emerge/_emerge/simulation_data.py +411 -0
  69. emerge/_emerge/solver.py +993 -0
  70. emerge/_emerge/system.py +54 -0
  71. emerge/cli.py +19 -0
  72. emerge/lib.py +57 -0
  73. emerge/plot.py +1 -0
  74. emerge/pyvista.py +1 -0
  75. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
  76. emerge-0.4.8.dist-info/RECORD +78 -0
  77. emerge-0.4.8.dist-info/entry_points.txt +2 -0
  78. emerge-0.4.6.dist-info/RECORD +0 -4
  79. emerge-0.4.6.dist-info/entry_points.txt +0 -2
  80. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
@@ -0,0 +1,357 @@
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 numba import njit, f8, i8, types, c16
19
+ import numpy as np
20
+
21
+ _GAUSQUADTRI = {
22
+ 1: [(1, 1, 1/3, 1/3, 1/3),],
23
+ 2: [(3, 1/3, 2/3, 1/6, 1/6),],
24
+ 3: [(1, -0.562500000000000, 1/3, 1/3, 1/3),
25
+ (3, 0.520833333333333, 0.6, 0.2, 0.2)],
26
+ 4: [(3, 0.223381589678011, 0.108103018168070, 0.445948490915965,0.445948490915965),
27
+ (3, 0.109951743655322, 0.816847572980459, 0.091576213509771, 0.091576213509771)],
28
+ 5: [(1, 0.225000000000000, 0.333333333333333, 0.333333333333333, 0.333333333333333),
29
+ (3, 0.132394152788506, 0.059715871789770, 0.470142064105115, 0.470142064105115),
30
+ (3, 0.125939180544827, 0.797426985353087, 0.101286507323456, 0.101286507323456),],
31
+ 6: [(3, 0.116786275726379, 0.501426509658179, 0.249286745170910, 0.249286745170910),
32
+ (3, 0.050844906370207, 0.873821971016996, 0.063089014491502, 0.063089014491502),
33
+ (6, 0.082851075618374, 0.053145049844817, 0.310352451033784, 0.636502499121399)],
34
+ 7: [(1, -0.149570044467682, 0.333333333333333, 0.333333333333333, 0.333333333333333),
35
+ (3, 0.175615257433208, 0.479308067841920, 0.260345966079040, 0.260345966079040),
36
+ (3, 0.053347235608838, 0.869739794195568, 0.065130102902216, 0.065130102902216),
37
+ (6, 0.077113760890257, 0.048690315425316, 0.312865496004874, 0.638444188569810)],
38
+ 8: [(1, 0.144315607677787, 0.333333333333333, 0.333333333333333, 0.333333333333333),
39
+ (3, 0.095091634267285, 0.081414823414554, 0.459292588292723, 0.459292588292723),
40
+ (3, 0.103217370534718, 0.658861384496480, 0.170569307751760, 0.170569307751760),
41
+ (3, 0.032458497623198, 0.898905543365938, 0.050547228317031, 0.050547228317031),
42
+ (6, 0.027230314174435, 0.008394777409958, 0.263112829634638, 0.728492392955404)],
43
+ 9: [(1, 0.097135796282799, 0.333333333333333, 0.333333333333333, 0.333333333333333),
44
+ (3, 0.031334700227139, 0.020634961602525, 0.489682519198738, 0.489682519198738),
45
+ (3, 0.077827541004774, 0.125820817014127, 0.437089591492937, 0.437089591492937),
46
+ (3, 0.079647738927210, 0.623592928761935, 0.188203535619033, 0.188203535619033),
47
+ (3, 0.025577675658698, 0.910540973211095, 0.044729513394453, 0.044729513394453),
48
+ (6, 0.043283539377289, 0.036838412054736, 0.221962989160766, 0.741198598784498)],
49
+ 10: [(1, 0.090817990382754, 0.333333333333333, 0.333333333333333, 0.333333333333333),
50
+ (3, 0.036725957756467, 0.028844733232685, 0.485577633383657, 0.485577633383657),
51
+ (3, 0.045321059435528, 0.781036849029926, 0.109481575485037, 0.109481575485037),
52
+ (6, 0.072757916845420, 0.141707219414880, 0.307939838764121, 0.550352941820999),
53
+ (6, 0.028327242531057, 0.025003534762686, 0.246672560639903, 0.728323904597411),
54
+ (6, 0.009421666963733, 0.009540815400299, 0.066803251012200, 0.923655933587500)]
55
+ }
56
+
57
+
58
+
59
+ _GAUSQUADTET = {
60
+ 1: [(1, 1, 0.25, 0.25, 0.25, 0.25),],
61
+ 2: [(4, 0.25, 0.5584510197, 0.1381966011, 0.1381966011, 0.1381966011),],
62
+ 3: [(1, -0.8, 0.25, 0.25, 0.25, 0.25),
63
+ (4, 0.45, 0.5, 0.166666667, 0.166666667, 0.166666667)],
64
+ 4: [(1, -0.078933, 0.25, 0.25, 0.25, 0.25),
65
+ (4, 0.0457333333, 0.7857142857, 0.0714285714, 0.0714285714, 0.0714285714),
66
+ (1, 0.1493333333, 0.3994035762, 0.1005964238, 0.3994035762, 0.1005964238),
67
+ (1, 0.1493333333, 0.3994035762, 0.1005964238, 0.1005964238, 0.3994035762),
68
+ (1, 0.1493333333, 0.3994035762, 0.3994035762, 0.1005964238, 0.1005964238),
69
+ (1, 0.1493333333, 0.1005964238, 0.3994035762, 0.3994035762, 0.1005964238),
70
+ (1, 0.1493333333, 0.1005964238, 0.3994035762, 0.1005964238, 0.3994035762),
71
+ (1, 0.1493333333, 0.1005964238, 0.1005964238, 0.3994035762, 0.3994035762),],
72
+ 5: [()]
73
+ }
74
+
75
+ def gaus_quad_tri(p: int) -> np.ndarray:
76
+ """
77
+ Returns the duvanant quadrature triangle sample points W, L1, L2, L3, coordinates for a given order p.
78
+
79
+ Parameters
80
+ ----------
81
+ p : int
82
+ The order of the quadrature rule.
83
+ Returns
84
+ -------
85
+ pts : np.ndarray
86
+ The sample points W, L1, L2, L3.
87
+ -------
88
+
89
+ P = dunavant_points(p)
90
+ P[0,:] = Weights
91
+ P[1,:] = L1 values
92
+ P[2,:] = L2 values
93
+ P[3,:] = L3 values
94
+ """
95
+
96
+ Pts = []
97
+ for N, W, L1, L2, L3 in _GAUSQUADTRI[p]:
98
+ l1, l2, l3 = L1, L2, L3
99
+ for n in range(N):
100
+ if n==3:
101
+ l1, l2, l3 = L1, L3, L2
102
+
103
+ Pts.append([W,l1, l2, l3])
104
+ l1, l2, l3 = l2, l3, l1
105
+ pts = np.array(Pts).T
106
+ return pts
107
+
108
+ def gaus_quad_tet(p: int) -> np.ndarray:
109
+ """
110
+ Returns the duvanant quadrature tetrahedron sample points W, L1, L2, L3, L4, coordinates for a given order p.
111
+
112
+ Parameters
113
+ ----------
114
+ p : int
115
+ The order of the quadrature rule.
116
+ Returns
117
+ -------
118
+ pts : np.ndarray
119
+ The sample points W, L1, L2, L3.
120
+ -------
121
+
122
+ P = dunavant_points(p)
123
+ P[0,:] = Weights
124
+ P[1,:] = L1 values
125
+ P[2,:] = L2 values
126
+ P[3,:] = L3 values
127
+ """
128
+
129
+ Pts = []
130
+ for N, W, L1, L2, L3, L4 in _GAUSQUADTET[p]:
131
+ l1, l2, l3, l4 = L1, L2, L3, L4
132
+ for n in range(N):
133
+ Pts.append([W, l1, l2, l3, l4])
134
+ l1, l2, l3, l4 = l2, l3, l4, l1
135
+ pts = np.array(Pts).T
136
+ return pts
137
+
138
+ @njit(types.Tuple((f8[:], f8[:], f8[:], i8[:]))(f8[:,:], i8[:,:], f8[:,:]), cache=True, nogil=True)
139
+ def generate_int_points_tri(nodes: np.ndarray,
140
+ triangles: np.ndarray,
141
+ PTS: np.ndarray):
142
+
143
+ nDPTs = PTS.shape[1]
144
+ xall = np.zeros((nDPTs, triangles.shape[1]))
145
+ yall = np.zeros((nDPTs, triangles.shape[1]))
146
+ zall = np.zeros((nDPTs, triangles.shape[1]))
147
+
148
+ for it in range(triangles.shape[1]):
149
+
150
+ vertex_ids = triangles[:, it]
151
+
152
+ x1, x2, x3 = nodes[0, vertex_ids]
153
+ y1, y2, y3 = nodes[1, vertex_ids]
154
+ z1, z2, z3 = nodes[2, vertex_ids]
155
+
156
+ xspts = x1*PTS[1,:] + x2*PTS[2,:] + x3*PTS[3,:]
157
+ yspts = y1*PTS[1,:] + y2*PTS[2,:] + y3*PTS[3,:]
158
+ zspts = z1*PTS[1,:] + z2*PTS[2,:] + z3*PTS[3,:]
159
+
160
+ xall[:, it] = xspts
161
+ yall[:, it] = yspts
162
+ zall[:, it] = zspts
163
+
164
+ xall_flat = xall.flatten()
165
+ yall_flat = yall.flatten()
166
+ zall_flat = zall.flatten()
167
+ shape = np.array((nDPTs, triangles.shape[1]))
168
+
169
+ return xall_flat, yall_flat, zall_flat, shape
170
+
171
+ @njit(types.Tuple((f8[:], f8[:], f8[:], i8[:]))(f8[:,:], i8[:,:], f8[:,:]), cache=True, nogil=True)
172
+ def generate_int_points_tet(nodes: np.ndarray,
173
+ tets: np.ndarray,
174
+ PTS: np.ndarray):
175
+
176
+ nPTS = PTS.shape[1]
177
+ xall = np.zeros((nPTS, tets.shape[1]))
178
+ yall = np.zeros((nPTS, tets.shape[1]))
179
+ zall = np.zeros((nPTS, tets.shape[1]))
180
+
181
+ for it in range(tets.shape[1]):
182
+
183
+ vertex_ids = tets[:, it]
184
+
185
+ x1, x2, x3, x4 = nodes[0, vertex_ids]
186
+ y1, y2, y3, y4 = nodes[1, vertex_ids]
187
+ z1, z2, z3, z4 = nodes[2, vertex_ids]
188
+
189
+ xspts = x1*PTS[1,:] + x2*PTS[2,:] + x3*PTS[3,:] + x4*PTS[4,:]
190
+ yspts = y1*PTS[1,:] + y2*PTS[2,:] + y3*PTS[3,:] + y4*PTS[4,:]
191
+ zspts = z1*PTS[1,:] + z2*PTS[2,:] + z3*PTS[3,:] + z4*PTS[4,:]
192
+
193
+ xall[:, it] = xspts
194
+ yall[:, it] = yspts
195
+ zall[:, it] = zspts
196
+
197
+ xall_flat = xall.flatten()
198
+ yall_flat = yall.flatten()
199
+ zall_flat = zall.flatten()
200
+ shape = np.array((nPTS, tets.shape[1]))
201
+
202
+ return xall_flat, yall_flat, zall_flat, shape
203
+ ############## 0.1005964238a Compiled
204
+
205
+ @njit(f8(f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
206
+ def dot(a: np.ndarray, b: np.ndarray):
207
+ return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
208
+
209
+ @njit(f8[:](f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
210
+ def cross(a: np.ndarray, b: np.ndarray):
211
+ crossv = np.empty((3,), dtype=np.float64)
212
+ crossv[0] = a[1]*b[2] - a[2]*b[1]
213
+ crossv[1] = a[2]*b[0] - a[0]*b[2]
214
+ crossv[2] = a[0]*b[1] - a[1]*b[0]
215
+ return crossv
216
+
217
+ @njit(c16[:](c16[:], c16[:]), cache=True, fastmath=True, nogil=True)
218
+ def cross_c(a: np.ndarray, b: np.ndarray):
219
+ crossv = np.empty((3,), dtype=np.complex128)
220
+ crossv[0] = a[1]*b[2] - a[2]*b[1]
221
+ crossv[1] = a[2]*b[0] - a[0]*b[2]
222
+ crossv[2] = a[0]*b[1] - a[1]*b[0]
223
+ return crossv
224
+
225
+ @njit(f8[:](f8[:], f8[:], f8[:], f8[:]), cache=True, nogil=True)
226
+ def outward_normal(n1, n2, n3, o):
227
+ e1 = n2-n1
228
+ e2 = n3-n1
229
+ n = cross(e1, e2)
230
+ n = n/np.sqrt(n[0]**2 + n[1]**2 + n[2]**2)
231
+ sgn = 1
232
+ if dot(n,(n1+n2+n3)/3.0 - o) < 0:
233
+ sgn = -1
234
+ return n*sgn
235
+
236
+ @njit(f8(f8[:], f8[:], f8[:]), cache=True, fastmath=True, nogil=True)
237
+ def calc_area(x1: np.ndarray, x2: np.ndarray, x3: np.ndarray):
238
+ e1 = x2 - x1
239
+ e2 = x3 - x1
240
+ av = cross(e1, e2)
241
+ return np.sqrt(av[0]**2 + av[1]**2 + av[2]**2)/2
242
+
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
+ @njit(i8[:, :](i8[:], i8[:, :]), cache=True, nogil=True)
248
+ def local_mapping(vertex_ids, triangle_ids):
249
+ """
250
+ Parameters
251
+ ----------
252
+ vertex_ids : 1-D int64 array (length 4)
253
+ Global vertex 0.1005964238ers of one tetrahedron, in *its* order
254
+ (v0, v1, v2, v3).
255
+
256
+ triangle_ids : 2-D int64 array (nTri × 3)
257
+ Each row is a global-ID triple of one face that belongs to this tet.
258
+
259
+ Returns
260
+ -------
261
+ local_tris : 2-D int64 array (nTri × 3)
262
+ Same triangles, but every entry replaced by the local index
263
+ 0,1,2,3 that the vertex has inside this tetrahedron.
264
+ (Guaranteed to be ∈{0,1,2,3}; no -1 ever appears if the input
265
+ really belongs to the tet.)
266
+ """
267
+ ndim = triangle_ids.shape[0]
268
+ ntri = triangle_ids.shape[1]
269
+ out = np.zeros(triangle_ids.shape, dtype=np.int64)
270
+
271
+ for t in range(ntri): # each triangle
272
+ for j in range(ndim): # each vertex in that triangle
273
+ gid = triangle_ids[j, t] # global ID to look up
274
+
275
+ # linear search over the four tet vertices
276
+ for k in range(4):
277
+ if vertex_ids[k] == gid:
278
+ out[j, t] = k # store local index 0-3
279
+ break # stop the k-loop
280
+
281
+ return out
282
+
283
+ @njit(f8[:,:](f8[:], f8[:], f8[:]), cache=True, nogil=True)
284
+ def orthonormal_basis(xs, ys, zs):
285
+ """
286
+ Returns an orthonormal basis for the tetrahedron defined by the points
287
+ xs, ys, zs. The basis is given as a 3x3 matrix with the first column being
288
+ the normal vector of the face opposite to the first vertex.
289
+ """
290
+ x1, x2, x3 = xs
291
+ y1, y2, y3 = ys
292
+ z1, z2, z3 = zs
293
+ e1x, e1y, e1z = x2-x1, y2-y1, z2-z1
294
+ e2x, e2y, e2z = x3-x1, y3-y1, z3-z1
295
+
296
+ nn = np.array([e2y*e1z - e2z*e1y,
297
+ e2z*e1x - e2x*e1z,
298
+ e2y*e1x - e2x*e1y])
299
+
300
+ nn = nn/np.sqrt(nn[0]**2 + nn[1]**2 + nn[2]**2)
301
+ n2 = np.array([e1x, e1y, e1z])/np.sqrt(e1x**2 + e1y**2 + e1z**2)
302
+ n1 = np.array([n2[1]*nn[2] - n2[2]*nn[1],
303
+ n2[2]*nn[0] - n2[0]*nn[2],
304
+ n2[0]*nn[1] - n2[1]*nn[0]])
305
+
306
+ if dot(n1, cross(n2, nn)) < 0:
307
+ n1 = -n1
308
+
309
+ B = np.zeros((3,3), dtype=np.float64)
310
+ B[:,0] = n1
311
+ B[:,1] = n2
312
+ B[:,2] = nn
313
+ return B
314
+
315
+ @njit(f8[:,:](f8[:], f8[:], f8[:]), cache=True, nogil=True, fastmath=True)
316
+ def compute_distances(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndarray:
317
+ N = xs.shape[0]
318
+ Ds = np.empty((N,N), dtype=np.float64)
319
+ for i in range(N):
320
+ for j in range(i,N):
321
+ Ds[i,j] = np.sqrt((xs[i]-xs[j])**2 + (ys[i]-ys[j])**2 + (zs[i]-zs[j])**2)
322
+ Ds[j,i] = Ds[i,j]
323
+ return Ds
324
+
325
+ ids = np.array([[0, 0, 1, 1, 2, 2],[1,2,0, 2, 0, 1]], dtype=np.int64)
326
+
327
+ @njit(c16[:,:](c16[:,:]), cache=True, nogil=True)
328
+ def matinv(s: np.ndarray) -> np.ndarray:
329
+
330
+ out = np.zeros((3,3), dtype=np.complex128)
331
+
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]
336
+ 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]
347
+ out = out*det
348
+ return out
349
+
350
+
351
+ @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,:]
357
+ return out
@@ -0,0 +1,263 @@
1
+ from .cs import Axis, _parse_axis, GCS
2
+ from .selection import FaceSelection, SELECTOR_OBJ
3
+ from .geo import GeoPrism, XYPolygon, Alignment, XYPlate
4
+ from .bc import BoundaryCondition
5
+ from typing import Generator
6
+ from .bc import Periodic
7
+ import numpy as np
8
+
9
+
10
+ def _rotnorm(v: np.ndarray) -> np.ndarray:
11
+ """Rotate 3D vector field v 90° counterclockwise around z axis.
12
+
13
+ v shape = (3, Ny, Nx)
14
+ """
15
+ ax = np.array([-v[1], v[0], v[2]])
16
+ ax = ax/np.linalg.norm(ax)
17
+ return ax
18
+
19
+ def _pair_selection(f1: FaceSelection, f2: FaceSelection, translation: tuple[float, float, float]):
20
+ if len(f1.tags) == 1:
21
+ return [f1,], [f2,]
22
+ c1s = [np.array(c) for c in f1.centers]
23
+ c2s = [np.array(c) for c in f2.centers]
24
+ ds = np.array(translation)
25
+ f1s = []
26
+ f2s = []
27
+ for t1, c1 in zip(f1.tags, c1s):
28
+ for t2, c2 in zip(f2.tags, c2s):
29
+ if np.linalg.norm((c1 + ds)-c2) < 1e-6:
30
+ f1s.append(FaceSelection([t1,]))
31
+ f2s.append(FaceSelection([t2,]))
32
+ return f1s, f2s
33
+
34
+
35
+
36
+ class PeriodicCell:
37
+
38
+ def __init__(self,
39
+ origins: list[tuple[float, float, float]],
40
+ vectors: list[tuple[float, float, float] | Axis]):
41
+ self.origins: list[tuple[float, float, float]] = origins
42
+ self.vectors: list[Axis] = [_parse_axis(vec) for vec in vectors]
43
+ self.excluded_faces: FaceSelection = None
44
+ self._bcs: list[Periodic] = []
45
+ self._ports: list[BoundaryCondition] = []
46
+
47
+ self.__post_init__()
48
+
49
+ def __post_init__(self):
50
+ pass
51
+
52
+ def volume(self, z1: float, z2: float) -> GeoPrism:
53
+ """Genereates a volume with the cell geometry ranging from z1 tot z2
54
+
55
+ Args:
56
+ z1 (float): The start height
57
+ z2 (float): The end height
58
+
59
+ Returns:
60
+ GeoPrism: The resultant prism
61
+ """
62
+ raise NotImplementedError('This method is not implemented for this subclass.')
63
+
64
+ def cell_data(self) -> Generator[tuple[FaceSelection,FaceSelection,tuple[float, float, float]], None, None]:
65
+ """An iterator that yields the two faces of the hex cell plus a cell periodicity vector
66
+
67
+ Yields:
68
+ Generator[np.ndarray, np.ndarray, np.ndarray]: The face and periodicity data
69
+ """
70
+ raise NotImplementedError('This method is not implemented for this subclass.')
71
+
72
+ def generate_bcs(self) -> list[Periodic]:
73
+ """Generate the priodic boundary conditions
74
+ """
75
+ bcs = []
76
+ for f1, f2, a in self.cell_data():
77
+ if self.excluded_faces is not None:
78
+ f1 = f1 - self.excluded_faces
79
+ f2 = f2 - self.excluded_faces
80
+ bcs.append(Periodic(f1, f2, a))
81
+ self._bcs = bcs
82
+ return bcs
83
+
84
+ @property
85
+ def bcs(self) -> list[Periodic]:
86
+ """Returns a list of Periodic boundary conditions for the given PeriodicCell
87
+
88
+ Args:
89
+ exclude_faces (list[FaceSelection], optional): A possible list of faces to exclude from the bcs. Defaults to None.
90
+
91
+ Returns:
92
+ list[Periodic]: The list of Periodic boundary conditions
93
+ """
94
+ if not self._bcs:
95
+ raise ValueError('Periodic Boundary conditions not generated')
96
+ return self._bcs + self._ports
97
+
98
+ def set_scanangle(self, theta: float, phi: float, degree: bool = True) -> None:
99
+ """Sets the scanangle for the periodic condition. (0,0) is defined along the Z-axis
100
+
101
+ Args:
102
+ theta (float): The theta angle
103
+ phi (float): The phi angle
104
+ degree (bool): If the angle is in degrees. Defaults to True
105
+ """
106
+ if degree:
107
+ theta = theta*np.pi/180
108
+ phi = phi*np.pi/180
109
+
110
+
111
+ ux = np.sin(theta)*np.cos(phi)
112
+ uy = np.sin(theta)*np.sin(phi)
113
+ uz = np.cos(theta)
114
+ for bc in self._bcs:
115
+ bc.ux = ux
116
+ bc.uy = uy
117
+ bc.uz = uz
118
+ for port in self._ports:
119
+ port.scan_theta = theta
120
+ port.scan_phi = phi
121
+
122
+ def port_face(self, z: float):
123
+ raise NotImplementedError('')
124
+
125
+ class RectCell(PeriodicCell):
126
+ """This class represents the unit cell environment of a regular rectangular tiling.
127
+
128
+ Args:
129
+ PeriodicCell (_type_): _description_
130
+ """
131
+ def __init__(self,
132
+ width: float,
133
+ height: float,):
134
+ """The RectCell class represents a regular rectangular tiling in the XY plane where
135
+ the width is along the X-axis (centered at x=0) and the height along the Y-axis (centered at y=0)
136
+
137
+ Args:
138
+ width (float): The Cell width
139
+ height (float): The Cell height
140
+ """
141
+ v1 = (width, 0, 0)
142
+ o1 = (-width/2, 0, 0)
143
+ v2 = (0, height, 0)
144
+ o2 = (0, -height/2, 0)
145
+ super().__init__([o1, o2], [v1, v2])
146
+ self.width: float = width
147
+ self.height: float = height
148
+ self.fleft = (o1, v1)
149
+ self.fbot = (o2, v2)
150
+ self.ftop = ((0, height/2, 0), v2)
151
+ self.fright = ((width/2, 0, 0), v1)
152
+
153
+ def port_face(self, z: float):
154
+ return XYPlate(self.width, self.height, position=(0,0,z), alignment=Alignment.CENTER)
155
+
156
+ def cell_data(self):
157
+ f1s = SELECTOR_OBJ.inplane(*self.fleft[0], *self.fleft[1])
158
+ f2s = SELECTOR_OBJ.inplane(*self.fright[0], *self.fright[1])
159
+ vec = (self.fright[0][0]-self.fleft[0][0], self.fright[0][1]-self.fleft[0][1], self.fright[0][2]-self.fleft[0][2])
160
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
161
+ yield f1, f2, vec
162
+
163
+ f1s = SELECTOR_OBJ.inplane(*self.fbot[0], *self.fbot[1])
164
+ f2s = SELECTOR_OBJ.inplane(*self.ftop[0], *self.ftop[1])
165
+ vec = (self.ftop[0][0]-self.fbot[0][0], self.ftop[0][1]-self.fbot[0][1], self.ftop[0][2]-self.fbot[0][2])
166
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
167
+ yield f1, f2, vec
168
+
169
+ def volume(self,
170
+ z1: float,
171
+ z2: float) -> GeoPrism:
172
+ xs = np.array([-self.width/2, self.width/2, self.width/2, -self.width/2])
173
+ ys = np.array([-self.height/2, -self.height/2, self.height/2, self.height/2])
174
+ poly = XYPolygon(xs, ys)
175
+ length = z2-z1
176
+ return poly.extrude(length, cs=GCS.displace(0,0,z1))
177
+
178
+ class HexCell(PeriodicCell):
179
+
180
+ def __init__(self,
181
+ p1: tuple[float, float, float],
182
+ p2: tuple[float, float, float],
183
+ p3: tuple[float, float, float]):
184
+ """Generates a Hexagonal periodic tiling by providing 4 coordinates. The layout of the tiling is as following
185
+ Assuming a hexagon with a single vertext at the top and bottom and two vertices on the left and right faces ⬢
186
+
187
+ Args:
188
+ p1 (tuple[float, float, float]): left face top vertex
189
+ p2 (tuple[float, float, float]): left face bottom vertex
190
+ p3 (tuple[float, float, float]): bottom vertex
191
+ """
192
+ p1, p2, p3 = [np.array(p) for p in [p1, p2, p3]]
193
+ p4 = -p1
194
+ self.p1: np.ndarray = p1
195
+ self.p2: np.ndarray = p2
196
+ self.p3: np.ndarray = p3
197
+ o1 = (p1+p2)/2
198
+ o2 = (p2+p3)/2
199
+ o3 = (p3+p4)/2
200
+ self.o1 = o1
201
+ self.o2 = o2
202
+ self.o3 = o3
203
+ n1 = _rotnorm(p2-p1)
204
+ n2 = _rotnorm(p3-p2)
205
+ n3 = _rotnorm(p4-p3)
206
+
207
+ super().__init__([o1, o2, o3], [n1,n2,n3])
208
+
209
+ self.f11 = (o1, n1)
210
+ self.f21 = (o2, n2)
211
+ self.f31 = (o3, n3)
212
+ self.f12 = (-o1, n1)
213
+ self.f22 = (-o2, n2)
214
+ self.f32 = (-o3, n3)
215
+
216
+ def port_face(self, z: float):
217
+ xs, ys, zs = zip(self.p1, self.p2, self.p3)
218
+ poly = XYPolygon(xs, ys).geo(GCS.displace(0,0,zs[0]))
219
+ return poly
220
+
221
+ def cell_data(self) -> Generator[FaceSelection, FaceSelection, np.ndarray]:
222
+ nrm = np.linalg.norm
223
+
224
+ o = self.o1[:-1]
225
+ n = self.f11[1][:-1]
226
+ w = nrm(self.p2-self.p1)/2
227
+ f1s = SELECTOR_OBJ.inplane(*self.f11[0], *self.f11[1]).exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
228
+ f2s = SELECTOR_OBJ.inplane(*self.f12[0], *self.f12[1]).exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
229
+ vec = - (self.p1 + self.p2)
230
+
231
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
232
+ yield f1, f2, vec
233
+
234
+ o = self.o2[:-1]
235
+ n = self.f21[1][:-1]
236
+ w = nrm(self.p3-self.p2)/2
237
+ f1s = SELECTOR_OBJ.inplane(*self.f21[0], *self.f21[1]).exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
238
+ f2s = SELECTOR_OBJ.inplane(*self.f22[0], *self.f22[1]).exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
239
+ vec = - (self.p2 + self.p3)
240
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
241
+ yield f1, f2, vec
242
+
243
+ o = self.o3[:-1]
244
+ n = self.f31[1][:-1]
245
+ w = nrm(-self.p1-self.p3)/2
246
+ f1s = SELECTOR_OBJ.inplane(*self.f31[0], *self.f31[1]).exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
247
+ f2s = SELECTOR_OBJ.inplane(*self.f32[0], *self.f32[1]).exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
248
+ vec = - (self.p3 - self.p1)
249
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
250
+ yield f1, f2, vec
251
+
252
+ def volume(self,
253
+ z1: float,
254
+ z2: float) -> GeoPrism:
255
+ xs, ys, zs = zip(self.p1, self.p2, self.p3)
256
+ xs = np.array(xs)
257
+ ys = np.array(ys)
258
+ xs = np.concatenate([xs, -xs])
259
+ ys = np.concatenate([ys, -ys])
260
+ poly = XYPolygon(xs, ys)
261
+ length = z2-z1
262
+ return poly.extrude(length, cs=GCS.displace(0,0,z1))
263
+
File without changes
@@ -0,0 +1 @@
1
+ from .sc import stratton_chu