emerge 0.4.7__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.
- emerge/__init__.py +14 -14
- emerge/_emerge/__init__.py +42 -0
- emerge/_emerge/bc.py +197 -0
- emerge/_emerge/coord.py +119 -0
- emerge/_emerge/cs.py +523 -0
- emerge/_emerge/dataset.py +36 -0
- emerge/_emerge/elements/__init__.py +19 -0
- emerge/_emerge/elements/femdata.py +212 -0
- emerge/_emerge/elements/index_interp.py +64 -0
- emerge/_emerge/elements/legrange2.py +172 -0
- emerge/_emerge/elements/ned2_interp.py +645 -0
- emerge/_emerge/elements/nedelec2.py +140 -0
- emerge/_emerge/elements/nedleg2.py +217 -0
- emerge/_emerge/geo/__init__.py +24 -0
- emerge/_emerge/geo/horn.py +107 -0
- emerge/_emerge/geo/modeler.py +449 -0
- emerge/_emerge/geo/operations.py +254 -0
- emerge/_emerge/geo/pcb.py +1244 -0
- emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
- emerge/_emerge/geo/pcb_tools/macro.py +79 -0
- emerge/_emerge/geo/pmlbox.py +204 -0
- emerge/_emerge/geo/polybased.py +529 -0
- emerge/_emerge/geo/shapes.py +427 -0
- emerge/_emerge/geo/step.py +77 -0
- emerge/_emerge/geo2d.py +86 -0
- emerge/_emerge/geometry.py +510 -0
- emerge/_emerge/howto.py +214 -0
- emerge/_emerge/logsettings.py +5 -0
- emerge/_emerge/material.py +118 -0
- emerge/_emerge/mesh3d.py +730 -0
- emerge/_emerge/mesher.py +339 -0
- emerge/_emerge/mth/common_functions.py +33 -0
- emerge/_emerge/mth/integrals.py +71 -0
- emerge/_emerge/mth/optimized.py +357 -0
- emerge/_emerge/periodic.py +263 -0
- emerge/_emerge/physics/__init__.py +0 -0
- emerge/_emerge/physics/microwave/__init__.py +1 -0
- emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
- emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
- emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
- emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
- emerge/_emerge/physics/microwave/periodic.py +82 -0
- emerge/_emerge/physics/microwave/port_functions.py +53 -0
- emerge/_emerge/physics/microwave/sc.py +175 -0
- emerge/_emerge/physics/microwave/simjob.py +147 -0
- emerge/_emerge/physics/microwave/sparam.py +138 -0
- emerge/_emerge/physics/microwave/touchstone.py +140 -0
- emerge/_emerge/plot/__init__.py +0 -0
- emerge/_emerge/plot/display.py +394 -0
- emerge/_emerge/plot/grapher.py +93 -0
- emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
- emerge/_emerge/plot/pyvista/__init__.py +1 -0
- emerge/_emerge/plot/pyvista/display.py +931 -0
- emerge/_emerge/plot/pyvista/display_settings.py +24 -0
- emerge/_emerge/plot/simple_plots.py +551 -0
- emerge/_emerge/plot.py +225 -0
- emerge/_emerge/projects/__init__.py +0 -0
- emerge/_emerge/projects/_gen_base.txt +32 -0
- emerge/_emerge/projects/_load_base.txt +24 -0
- emerge/_emerge/projects/generate_project.py +40 -0
- emerge/_emerge/selection.py +596 -0
- emerge/_emerge/simmodel.py +444 -0
- emerge/_emerge/simulation_data.py +411 -0
- emerge/_emerge/solver.py +993 -0
- emerge/_emerge/system.py +54 -0
- emerge/cli.py +19 -0
- emerge/lib.py +1 -1
- emerge/plot.py +1 -1
- {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
- emerge-0.4.8.dist-info/RECORD +78 -0
- emerge-0.4.8.dist-info/entry_points.txt +2 -0
- emerge-0.4.7.dist-info/RECORD +0 -9
- emerge-0.4.7.dist-info/entry_points.txt +0 -2
- {emerge-0.4.7.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
|