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.
- emerge/__init__.py +54 -0
- emerge/__main__.py +5 -0
- 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 +57 -0
- emerge/plot.py +1 -0
- emerge/pyvista.py +1 -0
- {emerge-0.4.6.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.6.dist-info/RECORD +0 -4
- emerge-0.4.6.dist-info/entry_points.txt +0 -2
- {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
# EMerge is an open source Python based FEM EM simulation module.
|
|
2
|
+
# Copyright (C) 2025 Robert Fennis.
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU General Public License
|
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
|
7
|
+
# of the License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, see
|
|
16
|
+
# <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
from ..microwave_bc import PEC, BoundaryCondition, RectangularWaveguide, RobinBC, PortBC, Periodic
|
|
20
|
+
from ....elements.nedelec2 import Nedelec2
|
|
21
|
+
from ....elements.nedleg2 import NedelecLegrange2
|
|
22
|
+
from ....mth.optimized import gaus_quad_tri
|
|
23
|
+
from scipy.sparse import csr_matrix, eye
|
|
24
|
+
from loguru import logger
|
|
25
|
+
from ..simjob import SimJob
|
|
26
|
+
from collections import defaultdict
|
|
27
|
+
|
|
28
|
+
C0 = 299792458
|
|
29
|
+
EPS0 = 8.854187818814e-12
|
|
30
|
+
|
|
31
|
+
def matprint(mat: np.ndarray) -> None:
|
|
32
|
+
factor = np.max(np.abs(mat.flatten()))
|
|
33
|
+
if factor == 0:
|
|
34
|
+
factor = 1
|
|
35
|
+
print(mat.real/factor)
|
|
36
|
+
|
|
37
|
+
def select_bc(bcs: list[BoundaryCondition], bctype):
|
|
38
|
+
return [bc for bc in bcs if isinstance(bc,bctype)]
|
|
39
|
+
|
|
40
|
+
def diagnose_matrix(mat: np.ndarray) -> None:
|
|
41
|
+
|
|
42
|
+
if not isinstance(mat, np.ndarray):
|
|
43
|
+
logger.debug('Converting sparse array to flattened array')
|
|
44
|
+
mat = mat[mat.nonzero()].A1
|
|
45
|
+
#mat = np.array(nonzero_mat)
|
|
46
|
+
|
|
47
|
+
''' Prints all indices of Nan's and infinities in a matrix '''
|
|
48
|
+
ids = np.where(np.isnan(mat))
|
|
49
|
+
if len(ids[0]) > 0:
|
|
50
|
+
logger.error(f'Found NaN at {ids}')
|
|
51
|
+
ids = np.where(np.isinf(mat))
|
|
52
|
+
if len(ids[0]) > 0:
|
|
53
|
+
logger.error(f'Found Inf at {ids}')
|
|
54
|
+
ids = np.where(np.abs(mat) > 1e10)
|
|
55
|
+
if len(ids[0]) > 0:
|
|
56
|
+
logger.error(f'Found large values at {ids}')
|
|
57
|
+
logger.info('Diagnostics finished')
|
|
58
|
+
|
|
59
|
+
#############
|
|
60
|
+
def gen_key(coord, mult):
|
|
61
|
+
return tuple([int(round(c*mult)) for c in coord])
|
|
62
|
+
|
|
63
|
+
def plane_basis_from_points(points: np.ndarray) -> np.ndarray:
|
|
64
|
+
"""
|
|
65
|
+
Compute an orthonormal basis from a cloud of 3D points dominantly
|
|
66
|
+
lying on one plane.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
points : ndarray, shape (3, N)
|
|
71
|
+
3D coordinates of the point cloud.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
basis : ndarray, shape (3, 3)
|
|
76
|
+
Matrix whose columns are:
|
|
77
|
+
- first principal direction (plane X axis)
|
|
78
|
+
- second principal direction (plane Y axis)
|
|
79
|
+
- plane normal vector (Z axis)
|
|
80
|
+
"""
|
|
81
|
+
if points.shape[0] != 3:
|
|
82
|
+
raise ValueError("Input must have shape (3, N)")
|
|
83
|
+
|
|
84
|
+
# Compute centroid
|
|
85
|
+
centroid = points.mean(axis=1, keepdims=True)
|
|
86
|
+
|
|
87
|
+
# Center the data
|
|
88
|
+
points_centered = points - centroid
|
|
89
|
+
|
|
90
|
+
# Compute covariance matrix (3x3)
|
|
91
|
+
C = (points_centered @ points_centered.T) / points.shape[1]
|
|
92
|
+
|
|
93
|
+
# Eigen decomposition
|
|
94
|
+
eigvals, eigvecs = np.linalg.eigh(C)
|
|
95
|
+
|
|
96
|
+
# Sort eigenvectors by descending eigenvalue
|
|
97
|
+
idx = np.argsort(eigvals)[::-1]
|
|
98
|
+
eigvecs = eigvecs[:, idx]
|
|
99
|
+
|
|
100
|
+
# Columns of eigvecs = principal axes
|
|
101
|
+
return eigvecs
|
|
102
|
+
|
|
103
|
+
class Assembler:
|
|
104
|
+
|
|
105
|
+
def __init__(self):
|
|
106
|
+
self.cached_matrices = None
|
|
107
|
+
self.conductivity_limit = 1e7
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def assemble_bma_matrices(self,
|
|
111
|
+
field: Nedelec2,
|
|
112
|
+
er: np.ndarray,
|
|
113
|
+
ur: np.ndarray,
|
|
114
|
+
sig: np.ndarray,
|
|
115
|
+
k0: float,
|
|
116
|
+
port: PortBC,
|
|
117
|
+
bcs: list[BoundaryCondition]) -> tuple[np.ndarray, np.ndarray, np.ndarray, NedelecLegrange2]:
|
|
118
|
+
|
|
119
|
+
from .generalized_eigen import generelized_eigenvalue_matrix
|
|
120
|
+
|
|
121
|
+
mesh = field.mesh
|
|
122
|
+
tri_ids = mesh.get_triangles(port.tags)
|
|
123
|
+
|
|
124
|
+
origin = tuple([c-n for c,n in zip(port.cs.origin, port.cs.gzhat)])
|
|
125
|
+
|
|
126
|
+
boundary_surface = mesh.boundary_surface(port.tags, origin)
|
|
127
|
+
nedlegfield = NedelecLegrange2(boundary_surface, port.cs)
|
|
128
|
+
|
|
129
|
+
ermesh = er[:,:,tri_ids]
|
|
130
|
+
urmesh = ur[:,:,tri_ids]
|
|
131
|
+
sigmesh = sig[tri_ids]
|
|
132
|
+
|
|
133
|
+
ermesh = ermesh - 1j * sigmesh/(k0*C0*EPS0)
|
|
134
|
+
|
|
135
|
+
E, B = generelized_eigenvalue_matrix(nedlegfield, ermesh, urmesh, port.cs._basis, k0)
|
|
136
|
+
|
|
137
|
+
pecs: list[PEC] = [bc for bc in bcs if isinstance(bc,PEC)]
|
|
138
|
+
if len(pecs) > 0:
|
|
139
|
+
logger.debug('Implementing PEC BCs')
|
|
140
|
+
|
|
141
|
+
pec_ids = []
|
|
142
|
+
|
|
143
|
+
# Process all concutors. Everything above the conductivity limit is considered pec.
|
|
144
|
+
for it in range(boundary_surface.n_tris):
|
|
145
|
+
if sigmesh[it] > self.conductivity_limit:
|
|
146
|
+
pec_ids.extend(list(nedlegfield.tri_to_field[:,it]))
|
|
147
|
+
|
|
148
|
+
# Process all PEC Boundary Conditions
|
|
149
|
+
pec_edges = []
|
|
150
|
+
pec_vertices = []
|
|
151
|
+
for pec in pecs:
|
|
152
|
+
face_tags = pec.tags
|
|
153
|
+
|
|
154
|
+
tri_ids = mesh.get_triangles(face_tags)
|
|
155
|
+
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
156
|
+
for ii in edge_ids:
|
|
157
|
+
i2 = nedlegfield.mesh.from_source_edge(ii)
|
|
158
|
+
if i2 is None:
|
|
159
|
+
continue
|
|
160
|
+
eids = nedlegfield.edge_to_field[:, i2]
|
|
161
|
+
pec_ids.extend(list(eids))
|
|
162
|
+
pec_edges.append(eids[0])
|
|
163
|
+
pec_vertices.append(eids[3]-nedlegfield.n_xy)
|
|
164
|
+
pec_vertices.append(eids[4]-nedlegfield.n_xy)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
for ii in tri_ids:
|
|
168
|
+
i2 = nedlegfield.mesh.from_source_tri(ii)
|
|
169
|
+
if i2 is None:
|
|
170
|
+
continue
|
|
171
|
+
tids = nedlegfield.tri_to_field[:, i2]
|
|
172
|
+
pec_ids.extend(list(tids))
|
|
173
|
+
|
|
174
|
+
port._field = nedlegfield
|
|
175
|
+
port._pece = pec_edges
|
|
176
|
+
port._pecv = pec_vertices
|
|
177
|
+
# Process all port boundary Conditions
|
|
178
|
+
pec_ids = set(pec_ids)
|
|
179
|
+
solve_ids = [i for i in range(nedlegfield.n_field) if i not in pec_ids]
|
|
180
|
+
|
|
181
|
+
return E, B, np.array(solve_ids), nedlegfield
|
|
182
|
+
|
|
183
|
+
def assemble_freq_matrix(self, field: Nedelec2,
|
|
184
|
+
er: np.ndarray,
|
|
185
|
+
ur: np.ndarray,
|
|
186
|
+
sig: np.ndarray,
|
|
187
|
+
bcs: list[BoundaryCondition],
|
|
188
|
+
frequency: float,
|
|
189
|
+
cache_matrices: bool = False) -> SimJob:
|
|
190
|
+
|
|
191
|
+
from .curlcurl import tet_mass_stiffness_matrices
|
|
192
|
+
from .robinbc import assemble_robin_bc, assemble_robin_bc_excited
|
|
193
|
+
|
|
194
|
+
mesh = field.mesh
|
|
195
|
+
w0 = 2*np.pi*frequency
|
|
196
|
+
k0 = w0/C0
|
|
197
|
+
|
|
198
|
+
er = er - 1j*sig/(w0*EPS0)*np.repeat(np.eye(3)[:, :, np.newaxis], er.shape[2], axis=2)
|
|
199
|
+
|
|
200
|
+
f_dependent_properties = np.any((sig > 0) & (sig < self.conductivity_limit))
|
|
201
|
+
|
|
202
|
+
if cache_matrices and not f_dependent_properties and self.cached_matrices is not None:
|
|
203
|
+
logger.debug(' Retreiving cached matricies')
|
|
204
|
+
E, B = self.cached_matrices
|
|
205
|
+
else:
|
|
206
|
+
logger.debug(' Assembling matrices')
|
|
207
|
+
E, B = tet_mass_stiffness_matrices(field, er, ur)
|
|
208
|
+
self.cached_matrices = (E, B)
|
|
209
|
+
|
|
210
|
+
K: csr_matrix = (E - B*(k0**2)).tocsr()
|
|
211
|
+
|
|
212
|
+
NF = E.shape[0]
|
|
213
|
+
|
|
214
|
+
pecs: list[PEC] = [bc for bc in bcs if isinstance(bc,PEC)]
|
|
215
|
+
robin_bcs: list[RectangularWaveguide] = [bc for bc in bcs if isinstance(bc,RobinBC)]
|
|
216
|
+
ports: list[PortBC] = [bc for bc in bcs if isinstance(bc, PortBC)]
|
|
217
|
+
periodic: list[Periodic] = [bc for bc in bcs if isinstance(bc, Periodic)]
|
|
218
|
+
|
|
219
|
+
b = np.zeros((E.shape[0],)).astype(np.complex128)
|
|
220
|
+
port_vectors = {port.port_number: np.zeros((E.shape[0],)).astype(np.complex128) for port in ports}
|
|
221
|
+
# Process all PEC Boundary Conditions
|
|
222
|
+
pec_ids = []
|
|
223
|
+
|
|
224
|
+
logger.debug(' Implementing PEC Boundary Conditions.')
|
|
225
|
+
|
|
226
|
+
# Conductivity above al imit, consider it all PEC
|
|
227
|
+
for itet in range(field.n_tets):
|
|
228
|
+
if sig[itet] > self.conductivity_limit:
|
|
229
|
+
pec_ids.extend(field.tet_to_field[:,itet])
|
|
230
|
+
|
|
231
|
+
# PEC Boundary conditions
|
|
232
|
+
for pec in pecs:
|
|
233
|
+
if len(pec.tags)==0:
|
|
234
|
+
continue
|
|
235
|
+
face_tags = pec.tags
|
|
236
|
+
tri_ids = mesh.get_triangles(face_tags)
|
|
237
|
+
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
238
|
+
|
|
239
|
+
for ii in edge_ids:
|
|
240
|
+
eids = field.edge_to_field[:, ii]
|
|
241
|
+
pec_ids.extend(list(eids))
|
|
242
|
+
|
|
243
|
+
for ii in tri_ids:
|
|
244
|
+
tids = field.tri_to_field[:, ii]
|
|
245
|
+
pec_ids.extend(list(tids))
|
|
246
|
+
|
|
247
|
+
# Robin BCs
|
|
248
|
+
if len(robin_bcs) > 0:
|
|
249
|
+
logger.debug(' Implementing Robin Boundary Conditions.')
|
|
250
|
+
|
|
251
|
+
gauss_points = gaus_quad_tri(4)
|
|
252
|
+
|
|
253
|
+
Bempty = field.empty_tri_matrix()
|
|
254
|
+
|
|
255
|
+
for bc in robin_bcs:
|
|
256
|
+
|
|
257
|
+
for tag in bc.tags:
|
|
258
|
+
face_tags = [tag,]
|
|
259
|
+
|
|
260
|
+
tri_ids = mesh.get_triangles(face_tags)
|
|
261
|
+
nodes = mesh.get_nodes(face_tags)
|
|
262
|
+
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
263
|
+
|
|
264
|
+
gamma = bc.get_gamma(k0)
|
|
265
|
+
|
|
266
|
+
def Ufunc(x,y):
|
|
267
|
+
return bc.get_Uinc(x,y,k0)
|
|
268
|
+
|
|
269
|
+
ibasis = bc.get_inv_basis()
|
|
270
|
+
if ibasis is None:
|
|
271
|
+
basis = plane_basis_from_points(mesh.nodes[:,nodes]) + 1e-16
|
|
272
|
+
ibasis = np.linalg.pinv(basis)
|
|
273
|
+
if bc._include_force:
|
|
274
|
+
|
|
275
|
+
Bempty, b_p = assemble_robin_bc_excited(field, Bempty, tri_ids, Ufunc, gamma, ibasis, bc.cs.origin, gauss_points)
|
|
276
|
+
|
|
277
|
+
port_vectors[bc.port_number] += b_p
|
|
278
|
+
|
|
279
|
+
else:
|
|
280
|
+
Bempty = assemble_robin_bc(field, Bempty, tri_ids, gamma)
|
|
281
|
+
B_p = field.generate_csr(Bempty)
|
|
282
|
+
K = K + B_p
|
|
283
|
+
|
|
284
|
+
if len(periodic) > 0:
|
|
285
|
+
logger.debug(' Implementing Periodic Boundary Conditions.')
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# Periodic BCs
|
|
289
|
+
Pmats = []
|
|
290
|
+
remove = set()
|
|
291
|
+
has_periodic = False
|
|
292
|
+
|
|
293
|
+
for bc in periodic:
|
|
294
|
+
Pmat = eye(NF, format='lil', dtype=np.complex128)
|
|
295
|
+
has_periodic = True
|
|
296
|
+
tri_ids_1 = mesh.get_triangles(bc.face1.tags)
|
|
297
|
+
tri_ids_2 = mesh.get_triangles(bc.face2.tags)
|
|
298
|
+
dv = np.array(bc.dv)
|
|
299
|
+
trimapdict = defaultdict(lambda: [None, None])
|
|
300
|
+
edgemapdict = defaultdict(lambda: [None, None])
|
|
301
|
+
mult = int(10**(-np.round(np.log10(min(mesh.edge_lengths.flatten())))+3))
|
|
302
|
+
|
|
303
|
+
edge_ids_1 = set()
|
|
304
|
+
edge_ids_2 = set()
|
|
305
|
+
|
|
306
|
+
phi = bc.phi(k0)
|
|
307
|
+
for i1, i2 in zip(tri_ids_1, tri_ids_2):
|
|
308
|
+
trimapdict[gen_key(mesh.tri_centers[:,i1], mult)][0] = i1
|
|
309
|
+
trimapdict[gen_key(mesh.tri_centers[:,i2]-dv, mult)][1] = i2
|
|
310
|
+
|
|
311
|
+
ie1, ie2, ie3 = mesh.tri_to_edge[:,i1]
|
|
312
|
+
edge_ids_1.update({ie1, ie2, ie3})
|
|
313
|
+
ie1, ie2, ie3 = mesh.tri_to_edge[:,i2]
|
|
314
|
+
edge_ids_2.update({ie1, ie2, ie3})
|
|
315
|
+
|
|
316
|
+
for i1, i2 in zip(list(edge_ids_1), list(edge_ids_2)):
|
|
317
|
+
edgemapdict[gen_key(mesh.edge_centers[:,i1], mult)][0] = i1
|
|
318
|
+
edgemapdict[gen_key(mesh.edge_centers[:,i2]-dv, mult)][1] = i2
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
for t1, t2 in trimapdict.values():
|
|
322
|
+
f11, f21 = field.tri_to_field[[3,7],t1]
|
|
323
|
+
f12, f22 = field.tri_to_field[[3,7],t2]
|
|
324
|
+
Pmat[f12,f12] = 0
|
|
325
|
+
Pmat[f22,f22] = 0
|
|
326
|
+
if Pmat[f12,f11] == 0:
|
|
327
|
+
Pmat[f12,f11] += phi
|
|
328
|
+
else:
|
|
329
|
+
Pmat[f12,f11] *= phi
|
|
330
|
+
|
|
331
|
+
if Pmat[f22,f21] == 0:
|
|
332
|
+
Pmat[f22,f21] += phi
|
|
333
|
+
else:
|
|
334
|
+
Pmat[f22,f21] *= phi
|
|
335
|
+
remove.add(f12)
|
|
336
|
+
remove.add(f22)
|
|
337
|
+
|
|
338
|
+
for e1, e2 in edgemapdict.values():
|
|
339
|
+
f11, f21 = field.edge_to_field[:,e1]
|
|
340
|
+
f12, f22 = field.edge_to_field[:,e2]
|
|
341
|
+
Pmat[f12,f12] = 0
|
|
342
|
+
Pmat[f22,f22] = 0
|
|
343
|
+
if Pmat[f12,f11] == 0:
|
|
344
|
+
Pmat[f12,f11] += phi
|
|
345
|
+
else:
|
|
346
|
+
Pmat[f12,f11] *= phi
|
|
347
|
+
|
|
348
|
+
if Pmat[f22,f21] == 0:
|
|
349
|
+
Pmat[f22,f21] += phi
|
|
350
|
+
else:
|
|
351
|
+
Pmat[f22,f21] *= phi
|
|
352
|
+
remove.add(f12)
|
|
353
|
+
remove.add(f22)
|
|
354
|
+
|
|
355
|
+
Pmats.append(Pmat)
|
|
356
|
+
|
|
357
|
+
if Pmats:
|
|
358
|
+
Pmat = Pmats[0]
|
|
359
|
+
for P2 in Pmats[1:]:
|
|
360
|
+
Pmat = Pmat @ P2
|
|
361
|
+
Pmat = Pmat.tocsr()
|
|
362
|
+
remove = np.array(sorted(list(remove)))
|
|
363
|
+
all_indices = np.arange(NF)
|
|
364
|
+
keep_indices = np.setdiff1d(all_indices, remove)
|
|
365
|
+
Pmat = Pmat[:,keep_indices]
|
|
366
|
+
else:
|
|
367
|
+
Pmat = None
|
|
368
|
+
|
|
369
|
+
pec_ids = set(pec_ids)
|
|
370
|
+
solve_ids = np.array([i for i in range(E.shape[0]) if i not in pec_ids])
|
|
371
|
+
|
|
372
|
+
if has_periodic:
|
|
373
|
+
mask = np.zeros((NF,))
|
|
374
|
+
mask[solve_ids] = 1
|
|
375
|
+
mask = mask[keep_indices]
|
|
376
|
+
solve_ids = np.argwhere(mask==1).flatten()
|
|
377
|
+
|
|
378
|
+
logger.debug(f'Number of tets: {mesh.n_tets}')
|
|
379
|
+
logger.debug(f'Number of DoF: {K.shape[0]}')
|
|
380
|
+
simjob = SimJob(K, b, k0*299792458/(2*np.pi), True)
|
|
381
|
+
|
|
382
|
+
simjob.port_vectors = port_vectors
|
|
383
|
+
simjob.solve_ids = solve_ids
|
|
384
|
+
|
|
385
|
+
if has_periodic:
|
|
386
|
+
simjob.P = Pmat
|
|
387
|
+
simjob.Pd = Pmat.getH()
|
|
388
|
+
simjob.has_periodic = has_periodic
|
|
389
|
+
|
|
390
|
+
return simjob
|
|
391
|
+
|
|
392
|
+
def assemble_eig_matrix(self, field: Nedelec2,
|
|
393
|
+
er: np.ndarray,
|
|
394
|
+
ur: np.ndarray,
|
|
395
|
+
sig: np.ndarray,
|
|
396
|
+
bcs: list[BoundaryCondition],
|
|
397
|
+
frequency: float) -> SimJob:
|
|
398
|
+
|
|
399
|
+
from .curlcurl import tet_mass_stiffness_matrices
|
|
400
|
+
from .robinbc import assemble_robin_bc
|
|
401
|
+
|
|
402
|
+
mesh = field.mesh
|
|
403
|
+
w0 = 2*np.pi*frequency
|
|
404
|
+
k0 = w0/C0
|
|
405
|
+
|
|
406
|
+
er = er - 1j*sig/(w0*EPS0)*np.repeat(np.eye(3)[:, :, np.newaxis], er.shape[2], axis=2)
|
|
407
|
+
|
|
408
|
+
logger.debug(' Assembling matrices')
|
|
409
|
+
E, B = tet_mass_stiffness_matrices(field, er, ur)
|
|
410
|
+
self.cached_matrices = (E, B)
|
|
411
|
+
|
|
412
|
+
NF = E.shape[0]
|
|
413
|
+
|
|
414
|
+
pecs: list[PEC] = [bc for bc in bcs if isinstance(bc,PEC)]
|
|
415
|
+
robin_bcs: list[RectangularWaveguide] = [bc for bc in bcs if isinstance(bc,RobinBC)]
|
|
416
|
+
periodic: list[Periodic] = [bc for bc in bcs if isinstance(bc, Periodic)]
|
|
417
|
+
|
|
418
|
+
# Process all PEC Boundary Conditions
|
|
419
|
+
pec_ids = []
|
|
420
|
+
|
|
421
|
+
logger.debug(' Implementing PEC Boundary Conditions.')
|
|
422
|
+
|
|
423
|
+
# Conductivity above a limit, consider it all PEC
|
|
424
|
+
for itet in range(field.n_tets):
|
|
425
|
+
if sig[itet] > self.conductivity_limit:
|
|
426
|
+
pec_ids.extend(field.tet_to_field[:,itet])
|
|
427
|
+
|
|
428
|
+
# PEC Boundary conditions
|
|
429
|
+
for pec in pecs:
|
|
430
|
+
if len(pec.tags)==0:
|
|
431
|
+
continue
|
|
432
|
+
face_tags = pec.tags
|
|
433
|
+
tri_ids = mesh.get_triangles(face_tags)
|
|
434
|
+
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
435
|
+
|
|
436
|
+
for ii in edge_ids:
|
|
437
|
+
eids = field.edge_to_field[:, ii]
|
|
438
|
+
pec_ids.extend(list(eids))
|
|
439
|
+
|
|
440
|
+
for ii in tri_ids:
|
|
441
|
+
tids = field.tri_to_field[:, ii]
|
|
442
|
+
pec_ids.extend(list(tids))
|
|
443
|
+
|
|
444
|
+
# Robin BCs
|
|
445
|
+
if len(robin_bcs) > 0:
|
|
446
|
+
logger.debug(' Implementing Robin Boundary Conditions.')
|
|
447
|
+
|
|
448
|
+
for bc in robin_bcs:
|
|
449
|
+
for tag in bc.tags:
|
|
450
|
+
face_tags = [tag,]#bc.tags
|
|
451
|
+
|
|
452
|
+
tri_ids = mesh.get_triangles(face_tags)
|
|
453
|
+
nodes = mesh.get_nodes(face_tags)
|
|
454
|
+
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
455
|
+
|
|
456
|
+
gamma = bc.get_gamma(k0)
|
|
457
|
+
|
|
458
|
+
ibasis = bc.get_inv_basis()
|
|
459
|
+
if ibasis is None:
|
|
460
|
+
basis = plane_basis_from_points(mesh.nodes[:,nodes]) + 1e-16
|
|
461
|
+
ibasis = np.linalg.pinv(basis)
|
|
462
|
+
B_p = assemble_robin_bc(field, tri_ids, gamma)
|
|
463
|
+
if bc._include_stiff:
|
|
464
|
+
B = B + B_p
|
|
465
|
+
|
|
466
|
+
if len(periodic) > 0:
|
|
467
|
+
logger.debug(' Implementing Periodic Boundary Conditions.')
|
|
468
|
+
|
|
469
|
+
# Periodic BCs
|
|
470
|
+
Pmats = []
|
|
471
|
+
remove = set()
|
|
472
|
+
has_periodic = False
|
|
473
|
+
|
|
474
|
+
for bc in periodic:
|
|
475
|
+
Pmat = eye(NF, format='lil', dtype=np.complex128)
|
|
476
|
+
has_periodic = True
|
|
477
|
+
tri_ids_1 = mesh.get_triangles(bc.face1.tags)
|
|
478
|
+
tri_ids_2 = mesh.get_triangles(bc.face2.tags)
|
|
479
|
+
dv = np.array(bc.dv)
|
|
480
|
+
trimapdict = defaultdict(lambda: [None, None])
|
|
481
|
+
edgemapdict = defaultdict(lambda: [None, None])
|
|
482
|
+
mult = int(10**(-np.round(np.ceil(min(mesh.edge_lengths.flatten())))+2))
|
|
483
|
+
edge_ids_1 = set()
|
|
484
|
+
edge_ids_2 = set()
|
|
485
|
+
|
|
486
|
+
phi = bc.phi(k0)
|
|
487
|
+
for i1, i2 in zip(tri_ids_1, tri_ids_2):
|
|
488
|
+
trimapdict[gen_key(mesh.tri_centers[:,i1], mult)][0] = i1
|
|
489
|
+
trimapdict[gen_key(mesh.tri_centers[:,i2]-dv, mult)][1] = i2
|
|
490
|
+
|
|
491
|
+
ie1, ie2, ie3 = mesh.tri_to_edge[:,i1]
|
|
492
|
+
edge_ids_1.update({ie1, ie2, ie3})
|
|
493
|
+
ie1, ie2, ie3 = mesh.tri_to_edge[:,i2]
|
|
494
|
+
edge_ids_2.update({ie1, ie2, ie3})
|
|
495
|
+
|
|
496
|
+
for i1, i2 in zip(list(edge_ids_1), list(edge_ids_2)):
|
|
497
|
+
edgemapdict[gen_key(mesh.edge_centers[:,i1], mult)][0] = i1
|
|
498
|
+
edgemapdict[gen_key(mesh.edge_centers[:,i2]-dv, mult)][1] = i2
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
for t1, t2 in trimapdict.values():
|
|
502
|
+
f11, f21 = field.tri_to_field[[3,7],t1]
|
|
503
|
+
f12, f22 = field.tri_to_field[[3,7],t2]
|
|
504
|
+
Pmat[f12,f12] = 0
|
|
505
|
+
Pmat[f22,f22] = 0
|
|
506
|
+
if Pmat[f12,f11] == 0:
|
|
507
|
+
Pmat[f12,f11] += phi
|
|
508
|
+
else:
|
|
509
|
+
Pmat[f12,f11] *= phi
|
|
510
|
+
|
|
511
|
+
if Pmat[f22,f21] == 0:
|
|
512
|
+
Pmat[f22,f21] += phi
|
|
513
|
+
else:
|
|
514
|
+
Pmat[f22,f21] *= phi
|
|
515
|
+
remove.add(f12)
|
|
516
|
+
remove.add(f22)
|
|
517
|
+
|
|
518
|
+
for e1, e2 in edgemapdict.values():
|
|
519
|
+
f11, f21 = field.edge_to_field[:,e1]
|
|
520
|
+
f12, f22 = field.edge_to_field[:,e2]
|
|
521
|
+
Pmat[f12,f12] = 0
|
|
522
|
+
Pmat[f22,f22] = 0
|
|
523
|
+
if Pmat[f12,f11] == 0:
|
|
524
|
+
Pmat[f12,f11] += phi
|
|
525
|
+
else:
|
|
526
|
+
Pmat[f12,f11] *= phi
|
|
527
|
+
|
|
528
|
+
if Pmat[f22,f21] == 0:
|
|
529
|
+
Pmat[f22,f21] += phi
|
|
530
|
+
else:
|
|
531
|
+
Pmat[f22,f21] *= phi
|
|
532
|
+
remove.add(f12)
|
|
533
|
+
remove.add(f22)
|
|
534
|
+
|
|
535
|
+
Pmats.append(Pmat)
|
|
536
|
+
|
|
537
|
+
if Pmats:
|
|
538
|
+
Pmat = Pmats[0]
|
|
539
|
+
for P2 in Pmats[1:]:
|
|
540
|
+
Pmat = Pmat @ P2
|
|
541
|
+
Pmat = Pmat.tocsr()
|
|
542
|
+
remove = np.array(sorted(list(remove)))
|
|
543
|
+
all_indices = np.arange(NF)
|
|
544
|
+
keep_indices = np.setdiff1d(all_indices, remove)
|
|
545
|
+
Pmat = Pmat[:,keep_indices]
|
|
546
|
+
else:
|
|
547
|
+
Pmat = None
|
|
548
|
+
|
|
549
|
+
pec_ids = set(pec_ids)
|
|
550
|
+
solve_ids = np.array([i for i in range(E.shape[0]) if i not in pec_ids])
|
|
551
|
+
|
|
552
|
+
if has_periodic:
|
|
553
|
+
mask = np.zeros((NF,))
|
|
554
|
+
mask[solve_ids] = 1
|
|
555
|
+
mask = mask[keep_indices]
|
|
556
|
+
solve_ids = np.argwhere(mask==1).flatten()
|
|
557
|
+
|
|
558
|
+
logger.debug(f'Number of tets: {mesh.n_tets}')
|
|
559
|
+
logger.debug(f'Number of DoF: {E.shape[0]}')
|
|
560
|
+
simjob = SimJob(E, None, frequency, False, B=B)
|
|
561
|
+
|
|
562
|
+
simjob.solve_ids = solve_ids
|
|
563
|
+
|
|
564
|
+
if has_periodic:
|
|
565
|
+
simjob.P = Pmat
|
|
566
|
+
simjob.Pd = Pmat.getH()
|
|
567
|
+
simjob.has_periodic = has_periodic
|
|
568
|
+
|
|
569
|
+
return simjob
|