emerge 0.5.5__py3-none-any.whl → 0.6.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.
- emerge/__init__.py +4 -1
- emerge/_emerge/cs.py +2 -2
- emerge/_emerge/elements/ned2_interp.py +21 -26
- emerge/_emerge/elements/nedleg2.py +27 -45
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/modeler.py +2 -2
- emerge/_emerge/geo/pcb.py +4 -4
- emerge/_emerge/geo/shapes.py +37 -14
- emerge/_emerge/geometry.py +27 -1
- emerge/_emerge/howto.py +9 -9
- emerge/_emerge/material.py +1 -0
- emerge/_emerge/mesh3d.py +63 -14
- emerge/_emerge/mesher.py +7 -4
- emerge/_emerge/mth/optimized.py +30 -0
- emerge/_emerge/periodic.py +46 -16
- emerge/_emerge/physics/microwave/assembly/assembler.py +4 -21
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +23 -19
- emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +465 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +59 -18
- emerge/_emerge/physics/microwave/microwave_3d.py +38 -186
- emerge/_emerge/physics/microwave/microwave_bc.py +101 -35
- emerge/_emerge/physics/microwave/microwave_data.py +1 -1
- emerge/_emerge/plot/pyvista/display.py +40 -7
- emerge/_emerge/plot/pyvista/display_settings.py +1 -0
- emerge/_emerge/plot/simple_plots.py +159 -27
- emerge/_emerge/projects/_gen_base.txt +2 -2
- emerge/_emerge/projects/_load_base.txt +1 -1
- emerge/_emerge/simmodel.py +22 -7
- emerge/_emerge/solve_interfaces/cudss_interface.py +44 -2
- emerge/_emerge/solve_interfaces/pardiso_interface.py +1 -0
- emerge/_emerge/solver.py +26 -19
- emerge/ext.py +4 -0
- emerge/lib.py +1 -1
- {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/METADATA +6 -4
- {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/RECORD +38 -37
- emerge/_emerge/elements/legrange2.py +0 -172
- {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/WHEEL +0 -0
- {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/mesh3d.py
CHANGED
|
@@ -146,6 +146,7 @@ class Mesh3D(Mesh):
|
|
|
146
146
|
self.ftag_to_node: dict[int, list[int]] = dict()
|
|
147
147
|
self.ftag_to_edge: dict[int, list[int]] = dict()
|
|
148
148
|
self.vtag_to_tet: dict[int, list[int]] = dict()
|
|
149
|
+
self.etag_to_edge: dict[int, list[int]] = dict()
|
|
149
150
|
|
|
150
151
|
self.exterior_face_tags: list[int] = []
|
|
151
152
|
|
|
@@ -176,7 +177,7 @@ class Mesh3D(Mesh):
|
|
|
176
177
|
search = (min(int(i1),int(i2)), max(int(i1),int(i2)))
|
|
177
178
|
result = self.inv_edges.get(search, -10)
|
|
178
179
|
if result == -10:
|
|
179
|
-
ValueError(f'There is no edge with indices {i1}, {i2}')
|
|
180
|
+
raise ValueError(f'There is no edge with indices {i1}, {i2}')
|
|
180
181
|
return result
|
|
181
182
|
|
|
182
183
|
def get_edge_sign(self, i1: int, i2: int) -> int:
|
|
@@ -240,6 +241,31 @@ class Mesh3D(Mesh):
|
|
|
240
241
|
|
|
241
242
|
return np.array(indices)
|
|
242
243
|
|
|
244
|
+
def domain_edges(self, dimtags: list[tuple[int,int]]) -> np.ndarray:
|
|
245
|
+
"""Returns a np.ndarray of all edge indices corresponding to a set of dimension tags.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
dimtags (list[tuple[int,int]]): A list of dimtags.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
np.ndarray: The list of mesh edge element indices.
|
|
252
|
+
"""
|
|
253
|
+
dimtags_edge = []
|
|
254
|
+
for (d,t) in dimtags:
|
|
255
|
+
if d==1:
|
|
256
|
+
dimtags_edge.append(t)
|
|
257
|
+
if d==2:
|
|
258
|
+
dimtags_edge.extend(gmsh.model.getBoundary([(d,t),], False, False))
|
|
259
|
+
if d==3:
|
|
260
|
+
dts = gmsh.model.getBoundary([(d,t),], False, False)
|
|
261
|
+
dimtags_edge.extend(gmsh.model.getBoundary(dts, False, False))
|
|
262
|
+
|
|
263
|
+
edge_ids = []
|
|
264
|
+
for tag in dimtags_edge:
|
|
265
|
+
edge_ids.extend(self.etag_to_edge[tag[1]])
|
|
266
|
+
edge_ids = np.array(edge_ids)
|
|
267
|
+
return edge_ids
|
|
268
|
+
|
|
243
269
|
def get_face_tets(self, *taglist: list[int]) -> np.ndarray:
|
|
244
270
|
''' Return a list of a tetrahedrons that share a node with any of the nodes in the provided face.'''
|
|
245
271
|
nodes: set = set()
|
|
@@ -271,6 +297,8 @@ class Mesh3D(Mesh):
|
|
|
271
297
|
|
|
272
298
|
|
|
273
299
|
def update(self, periodic_bcs: list[Periodic] | None = None):
|
|
300
|
+
|
|
301
|
+
logger.trace('Generating mesh data.')
|
|
274
302
|
if periodic_bcs is None:
|
|
275
303
|
periodic_bcs = []
|
|
276
304
|
|
|
@@ -282,7 +310,7 @@ class Mesh3D(Mesh):
|
|
|
282
310
|
self.nodes = coords
|
|
283
311
|
self.n_i2t = {i: int(t) for i, t in enumerate(nodes)}
|
|
284
312
|
self.n_t2i = {t: i for i, t in self.n_i2t.items()}
|
|
285
|
-
|
|
313
|
+
logger.trace(f'Total of {self.nodes.shape[1]} nodes imported.')
|
|
286
314
|
## Tetrahedras
|
|
287
315
|
|
|
288
316
|
_, tet_tags, tet_node_tags = gmsh.model.mesh.get_elements(3)
|
|
@@ -297,8 +325,8 @@ class Mesh3D(Mesh):
|
|
|
297
325
|
self.tets = np.array(tet_node_tags).reshape(-1,4).T
|
|
298
326
|
self.tet_i2t = {i: int(t) for i, t in enumerate(tet_tags)}
|
|
299
327
|
self.tet_t2i = {t: i for i, t in self.tet_i2t.items()}
|
|
300
|
-
|
|
301
328
|
self.centers = (self.nodes[:,self.tets[0,:]] + self.nodes[:,self.tets[1,:]] + self.nodes[:,self.tets[2,:]] + self.nodes[:,self.tets[3,:]]) / 4
|
|
329
|
+
logger.trace(f'Total of {self.tets.shape[1]} tetrahedra imported.')
|
|
302
330
|
|
|
303
331
|
# Resort node indices to be sorted on all periodic conditions
|
|
304
332
|
# This sorting makes sure that each edge and triangle on a source face is
|
|
@@ -307,6 +335,7 @@ class Mesh3D(Mesh):
|
|
|
307
335
|
# Then this ensures that if i1>i2>i3 then j1>j2>j3
|
|
308
336
|
|
|
309
337
|
for bc in periodic_bcs:
|
|
338
|
+
logger.trace(f'reassigning ordered node numbers for periodic boundary {bc}')
|
|
310
339
|
nodemap, ids1, ids2 = self._derive_node_map(bc)
|
|
311
340
|
nodemap = {int(a): int(b) for a,b in nodemap.items()}
|
|
312
341
|
self.nodes[:,ids2] = self.nodes[:,ids1]
|
|
@@ -330,25 +359,26 @@ class Mesh3D(Mesh):
|
|
|
330
359
|
triset.add((i1,i2,i4))
|
|
331
360
|
triset.add((i1,i3,i4))
|
|
332
361
|
triset.add((i2,i3,i4))
|
|
333
|
-
|
|
362
|
+
logger.trace(f'Total of {len(edgeset)} unique edges and {len(triset)} unique triangles.')
|
|
363
|
+
|
|
334
364
|
# Edges are effectively Randomly sorted
|
|
335
365
|
# It contains index pairs of vertices edge 1 = (ev1, ev2) etc.
|
|
336
|
-
# Same for
|
|
366
|
+
# Same for triangles
|
|
337
367
|
self.edges = np.array(sorted(list(edgeset))).T
|
|
338
368
|
self.tris = np.array(sorted(list(triset))).T
|
|
339
|
-
|
|
340
369
|
self.tri_centers = (self.nodes[:,self.tris[0,:]] + self.nodes[:,self.tris[1,:]] + self.nodes[:,self.tris[2,:]]) / 3
|
|
370
|
+
|
|
341
371
|
def _hash(ints):
|
|
342
372
|
return tuple(sorted([int(x) for x in ints]))
|
|
343
373
|
|
|
344
374
|
# Map edge index tuples to edge indices
|
|
345
375
|
# This mapping tells which characteristic index pair (4,3) maps to which edge
|
|
376
|
+
logger.trace('Constructing tet/tri/edge and node mappings.')
|
|
346
377
|
self.inv_edges = {(int(self.edges[0,i]), int(self.edges[1,i])): i for i in range(self.edges.shape[1])}
|
|
347
378
|
self.inv_tris = {_hash((self.tris[0,i], self.tris[1,i], self.tris[2,i])): i for i in range(self.tris.shape[1])}
|
|
348
379
|
self.inv_tets = {_hash((self.tets[0,i], self.tets[1,i], self.tets[2,i], self.tets[3,i])): i for i in range(self.tets.shape[1])}
|
|
349
380
|
|
|
350
381
|
# Tet links
|
|
351
|
-
|
|
352
382
|
self.tet_to_edge = np.zeros((6, self.tets.shape[1]), dtype=int)-99999
|
|
353
383
|
self.tet_to_edge_sign = np.zeros((6, self.tets.shape[1]), dtype=int)-999999
|
|
354
384
|
self.tet_to_tri = np.zeros((4, self.tets.shape[1]), dtype=int)-99999
|
|
@@ -414,14 +444,32 @@ class Mesh3D(Mesh):
|
|
|
414
444
|
self.node_to_edge = {key: sorted(list(set(val))) for key, val in self.node_to_edge.items()}
|
|
415
445
|
|
|
416
446
|
## Quantities
|
|
417
|
-
|
|
447
|
+
logger.trace('Computing derived quantaties (centres, areas and lengths).')
|
|
418
448
|
self.edge_centers = (self.nodes[:,self.edges[0,:]] + self.nodes[:,self.edges[1,:]]) / 2
|
|
419
449
|
self.edge_lengths = np.sqrt(np.sum((self.nodes[:,self.edges[0,:]] - self.nodes[:,self.edges[1,:]])**2, axis=0))
|
|
420
450
|
self.areas = np.array([area(self.nodes[:,self.tris[0,i]], self.nodes[:,self.tris[1,i]], self.nodes[:,self.tris[2,i]]) for i in range(self.tris.shape[1])])
|
|
421
451
|
|
|
452
|
+
## Edge tag pairings
|
|
453
|
+
_, edge_tags, edge_node_tags = gmsh.model.mesh.get_elements(1)
|
|
454
|
+
edge_tags = np.array(edge_tags).flatten()
|
|
455
|
+
ent = np.array(edge_node_tags).reshape(-1,2).T
|
|
456
|
+
nET = ent.shape[1]
|
|
457
|
+
self.edge_t2i = {int(edge_tags[i]): self.get_edge(self.n_t2i[ent[0,i]], self.n_t2i[ent[1,i]]) for i in range(nET)}
|
|
458
|
+
self.edge_i2t = {i: t for t, i in self.edge_t2i.items()}
|
|
459
|
+
|
|
460
|
+
edge_dimtags = gmsh.model.get_entities(1)
|
|
461
|
+
for _d, t in edge_dimtags:
|
|
462
|
+
_, edge_tags, node_tags = gmsh.model.mesh.get_elements(1, t)
|
|
463
|
+
if not edge_tags:
|
|
464
|
+
self.etag_to_edge[t] = []
|
|
465
|
+
continue
|
|
466
|
+
self.etag_to_edge[t] = [int(self.edge_t2i[tag]) for tag in edge_tags[0]]
|
|
467
|
+
|
|
468
|
+
|
|
422
469
|
## Tag bindings
|
|
470
|
+
logger.trace('Constructing geometry to mesh mappings.')
|
|
423
471
|
face_dimtags = gmsh.model.get_entities(2)
|
|
424
|
-
for
|
|
472
|
+
for _d,t in face_dimtags:
|
|
425
473
|
domain_tag, f_tags, node_tags = gmsh.model.mesh.get_elements(2, t)
|
|
426
474
|
node_tags = [self.n_t2i[int(t)] for t in node_tags[0]]
|
|
427
475
|
self.ftag_to_node[t] = node_tags
|
|
@@ -430,13 +478,14 @@ class Mesh3D(Mesh):
|
|
|
430
478
|
self.ftag_to_edge[t] = sorted(list(np.unique(self.tri_to_edge[:,self.ftag_to_tri[t]].flatten())))
|
|
431
479
|
|
|
432
480
|
vol_dimtags = gmsh.model.get_entities(3)
|
|
433
|
-
for
|
|
481
|
+
for _d,t in vol_dimtags:
|
|
434
482
|
domain_tag, v_tags, node_tags = gmsh.model.mesh.get_elements(3, t)
|
|
435
483
|
node_tags = [self.n_t2i[int(t)] for t in node_tags[0]]
|
|
436
484
|
node_tags = np.squeeze(np.array(node_tags)).reshape(-1,4).T
|
|
437
485
|
self.vtag_to_tet[t] = [self.get_tet(node_tags[0,i], node_tags[1,i], node_tags[2,i], node_tags[3,i]) for i in range(node_tags.shape[1])]
|
|
438
486
|
|
|
439
487
|
self.defined = True
|
|
488
|
+
logger.trace('Done analyzing mesh.')
|
|
440
489
|
|
|
441
490
|
|
|
442
491
|
## Higher order functions
|
|
@@ -455,7 +504,7 @@ class Mesh3D(Mesh):
|
|
|
455
504
|
Returns:
|
|
456
505
|
tuple[dict[int, int], np.ndarray, np.ndarray]: The node index mapping and the node index arrays
|
|
457
506
|
"""
|
|
458
|
-
|
|
507
|
+
|
|
459
508
|
node_ids_1 = []
|
|
460
509
|
node_ids_2 = []
|
|
461
510
|
|
|
@@ -597,9 +646,9 @@ class Mesh3D(Mesh):
|
|
|
597
646
|
tri_ids = self.get_triangles(face_tags)
|
|
598
647
|
if origin is None:
|
|
599
648
|
nodes = self.nodes[:,self.get_nodes(face_tags)]
|
|
600
|
-
x0 = np.mean(nodes[0,:])
|
|
601
|
-
y0 = np.mean(nodes[1,:])
|
|
602
|
-
z0 = np.mean(nodes[2,:])
|
|
649
|
+
x0 = float(np.mean(nodes[0,:]))
|
|
650
|
+
y0 = float(np.mean(nodes[1,:]))
|
|
651
|
+
z0 = float(np.mean(nodes[2,:]))
|
|
603
652
|
origin = (x0, y0, z0)
|
|
604
653
|
|
|
605
654
|
return SurfaceMesh(self, tri_ids, origin)
|
emerge/_emerge/mesher.py
CHANGED
|
@@ -79,6 +79,7 @@ class Mesher:
|
|
|
79
79
|
self.max_size: float = None
|
|
80
80
|
self.periodic_cell: PeriodicCell = None
|
|
81
81
|
|
|
82
|
+
|
|
82
83
|
@property
|
|
83
84
|
def edge_tags(self) -> list[int]:
|
|
84
85
|
return [tag[1] for tag in gmsh.model.getEntities(1)]
|
|
@@ -244,7 +245,7 @@ class Mesher:
|
|
|
244
245
|
for dimtag in dimtags:
|
|
245
246
|
gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
|
|
246
247
|
|
|
247
|
-
def set_boundary_size(self, boundary:
|
|
248
|
+
def set_boundary_size(self, boundary: GeoObject | Selection | Iterable,
|
|
248
249
|
size:float,
|
|
249
250
|
growth_rate: float = 1.4,
|
|
250
251
|
max_size: float | None = None) -> None:
|
|
@@ -272,8 +273,10 @@ class Mesher:
|
|
|
272
273
|
nodes = gmsh.model.getBoundary(dimtags, combined=False, oriented=False, recursive=False)
|
|
273
274
|
|
|
274
275
|
disttag = gmsh.model.mesh.field.add("Distance")
|
|
275
|
-
|
|
276
|
-
|
|
276
|
+
if boundary.dim==2:
|
|
277
|
+
gmsh.model.mesh.field.setNumbers(disttag, "CurvesList", [n[1] for n in nodes])
|
|
278
|
+
if boundary.dim==3:
|
|
279
|
+
gmsh.model.mesh.field.setNumbers(disttag,'SurfacesList', [n[1] for n in nodes])
|
|
277
280
|
gmsh.model.mesh.field.setNumber(disttag, "Sampling", 100)
|
|
278
281
|
|
|
279
282
|
thtag = gmsh.model.mesh.field.add("Threshold")
|
|
@@ -285,7 +288,7 @@ class Mesher:
|
|
|
285
288
|
|
|
286
289
|
self.mesh_fields.append(thtag)
|
|
287
290
|
|
|
288
|
-
def set_domain_size(self, obj:
|
|
291
|
+
def set_domain_size(self, obj: GeoObject | Selection, size: float):
|
|
289
292
|
"""Manually set the maximum element size inside a domain
|
|
290
293
|
|
|
291
294
|
Args:
|
emerge/_emerge/mth/optimized.py
CHANGED
|
@@ -432,6 +432,36 @@ def matinv(M: np.ndarray) -> np.ndarray:
|
|
|
432
432
|
out = out*det
|
|
433
433
|
return out
|
|
434
434
|
|
|
435
|
+
@njit(f8[:,:](f8[:,:]), cache=True, nogil=True)
|
|
436
|
+
def matinv_r(M: np.ndarray) -> np.ndarray:
|
|
437
|
+
"""Optimized matrix inverse of 3x3 matrix
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
M (np.ndarray): Input matrix M of shape (3,3)
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
np.ndarray: The matrix inverse inv(M)
|
|
444
|
+
"""
|
|
445
|
+
out = np.zeros((3,3), dtype=np.float64)
|
|
446
|
+
|
|
447
|
+
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:
|
|
448
|
+
out[0,0] = 1/M[0,0]
|
|
449
|
+
out[1,1] = 1/M[1,1]
|
|
450
|
+
out[2,2] = 1/M[2,2]
|
|
451
|
+
else:
|
|
452
|
+
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]
|
|
453
|
+
out[0,0] = M[1,1]*M[2,2] - M[1,2]*M[2,1]
|
|
454
|
+
out[0,1] = -M[0,1]*M[2,2] + M[0,2]*M[2,1]
|
|
455
|
+
out[0,2] = M[0,1]*M[1,2] - M[0,2]*M[1,1]
|
|
456
|
+
out[1,0] = -M[1,0]*M[2,2] + M[1,2]*M[2,0]
|
|
457
|
+
out[1,1] = M[0,0]*M[2,2] - M[0,2]*M[2,0]
|
|
458
|
+
out[1,2] = -M[0,0]*M[1,2] + M[0,2]*M[1,0]
|
|
459
|
+
out[2,0] = M[1,0]*M[2,1] - M[1,1]*M[2,0]
|
|
460
|
+
out[2,1] = -M[0,0]*M[2,1] + M[0,1]*M[2,0]
|
|
461
|
+
out[2,2] = M[0,0]*M[1,1] - M[0,1]*M[1,0]
|
|
462
|
+
out = out*det
|
|
463
|
+
return out
|
|
464
|
+
|
|
435
465
|
@njit(cache=True, nogil=True)
|
|
436
466
|
def matmul(M: np.ndarray, vecs: np.ndarray):
|
|
437
467
|
"""Executes a basis transformation of vectors (3,N) with a basis matrix M
|
emerge/_emerge/periodic.py
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
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
|
+
|
|
1
18
|
from .cs import Axis, _parse_axis, GCS, _parse_vector
|
|
2
|
-
from .selection import
|
|
19
|
+
from .selection import SELECTOR_OBJ, Selection
|
|
3
20
|
from .geo import GeoPrism, XYPolygon, Alignment, XYPlate
|
|
4
21
|
from .bc import BoundaryCondition
|
|
5
22
|
from typing import Generator
|
|
@@ -7,7 +24,6 @@ from .bc import Periodic
|
|
|
7
24
|
import numpy as np
|
|
8
25
|
|
|
9
26
|
|
|
10
|
-
|
|
11
27
|
############################################################
|
|
12
28
|
# FUNCTIONS #
|
|
13
29
|
############################################################
|
|
@@ -21,7 +37,9 @@ def _rotnorm(v: np.ndarray) -> np.ndarray:
|
|
|
21
37
|
ax = ax/np.linalg.norm(ax)
|
|
22
38
|
return ax
|
|
23
39
|
|
|
24
|
-
def _pair_selection(f1: Selection,
|
|
40
|
+
def _pair_selection(f1: Selection,
|
|
41
|
+
f2: Selection,
|
|
42
|
+
translation: tuple[float, float, float]):
|
|
25
43
|
if len(f1.tags) == 1:
|
|
26
44
|
return [f1,], [f2,]
|
|
27
45
|
c1s = [np.array(c) for c in f1.centers]
|
|
@@ -175,13 +193,17 @@ class RectCell(PeriodicCell):
|
|
|
175
193
|
def cell_data(self):
|
|
176
194
|
f1s = SELECTOR_OBJ.inplane(*self.fleft[0], *self.fleft[1])
|
|
177
195
|
f2s = SELECTOR_OBJ.inplane(*self.fright[0], *self.fright[1])
|
|
178
|
-
vec = (self.fright[0][0]-self.fleft[0][0],
|
|
196
|
+
vec = (self.fright[0][0]-self.fleft[0][0],
|
|
197
|
+
self.fright[0][1]-self.fleft[0][1],
|
|
198
|
+
self.fright[0][2]-self.fleft[0][2])
|
|
179
199
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
|
|
180
200
|
yield f1, f2, vec
|
|
181
201
|
|
|
182
202
|
f1s = SELECTOR_OBJ.inplane(*self.fbot[0], *self.fbot[1])
|
|
183
203
|
f2s = SELECTOR_OBJ.inplane(*self.ftop[0], *self.ftop[1])
|
|
184
|
-
vec = (self.ftop[0][0]-self.fbot[0][0],
|
|
204
|
+
vec = (self.ftop[0][0]-self.fbot[0][0],
|
|
205
|
+
self.ftop[0][1]-self.fbot[0][1],
|
|
206
|
+
self.ftop[0][2]-self.fbot[0][2])
|
|
185
207
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
|
|
186
208
|
yield f1, f2, vec
|
|
187
209
|
|
|
@@ -206,13 +228,15 @@ class HexCell(PeriodicCell):
|
|
|
206
228
|
point1: tuple[float, float, float],
|
|
207
229
|
point2: tuple[float, float, float],
|
|
208
230
|
point3: tuple[float, float, float]):
|
|
209
|
-
"""
|
|
210
|
-
|
|
231
|
+
"""
|
|
232
|
+
Generates a Hexagonal periodic tiling by providing three coordinates.
|
|
233
|
+
The layout of the tiling assumes a hexagon with a single vertex at the top and bottom,
|
|
234
|
+
and one vertex on the bottom and right faces (⬢).
|
|
211
235
|
|
|
212
236
|
Args:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
237
|
+
point1 (tuple[float, float, float]): left face top vertex
|
|
238
|
+
point2 (tuple[float, float, float]): left face bottom vertex
|
|
239
|
+
point3 (tuple[float, float, float]): bottom vertex
|
|
216
240
|
"""
|
|
217
241
|
p1, p2, p3 = np.array(point1), np.array(point2), np.array(point3)
|
|
218
242
|
p4 = -p1
|
|
@@ -249,8 +273,10 @@ class HexCell(PeriodicCell):
|
|
|
249
273
|
o = self.o1[:-1]
|
|
250
274
|
n = self.f11[1][:-1]
|
|
251
275
|
w = nrm(self.p2-self.p1)/2
|
|
252
|
-
f1s = SELECTOR_OBJ.inplane(*self.f11[0], *self.f11[1])
|
|
253
|
-
|
|
276
|
+
f1s = SELECTOR_OBJ.inplane(*self.f11[0], *self.f11[1])\
|
|
277
|
+
.exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
|
|
278
|
+
f2s = SELECTOR_OBJ.inplane(*self.f12[0], *self.f12[1])\
|
|
279
|
+
.exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
|
|
254
280
|
vec = - (self.p1 + self.p2)
|
|
255
281
|
|
|
256
282
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
|
|
@@ -259,8 +285,10 @@ class HexCell(PeriodicCell):
|
|
|
259
285
|
o = self.o2[:-1]
|
|
260
286
|
n = self.f21[1][:-1]
|
|
261
287
|
w = nrm(self.p3-self.p2)/2
|
|
262
|
-
f1s = SELECTOR_OBJ.inplane(*self.f21[0], *self.f21[1])
|
|
263
|
-
|
|
288
|
+
f1s = SELECTOR_OBJ.inplane(*self.f21[0], *self.f21[1])\
|
|
289
|
+
.exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
|
|
290
|
+
f2s = SELECTOR_OBJ.inplane(*self.f22[0], *self.f22[1])\
|
|
291
|
+
.exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
|
|
264
292
|
vec = - (self.p2 + self.p3)
|
|
265
293
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
|
|
266
294
|
yield f1, f2, vec
|
|
@@ -268,8 +296,10 @@ class HexCell(PeriodicCell):
|
|
|
268
296
|
o = self.o3[:-1]
|
|
269
297
|
n = self.f31[1][:-1]
|
|
270
298
|
w = nrm(-self.p1-self.p3)/2
|
|
271
|
-
f1s = SELECTOR_OBJ.inplane(*self.f31[0], *self.f31[1])
|
|
272
|
-
|
|
299
|
+
f1s = SELECTOR_OBJ.inplane(*self.f31[0], *self.f31[1])\
|
|
300
|
+
.exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
|
|
301
|
+
f2s = SELECTOR_OBJ.inplane(*self.f32[0], *self.f32[1])\
|
|
302
|
+
.exclude(lambda x, y, z: (nrm(np.array([x,y])+o)>w) or (abs((np.array([x,y])+o) @ n ) > 1e-6))
|
|
273
303
|
vec = - (self.p3 - self.p1)
|
|
274
304
|
for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
|
|
275
305
|
yield f1, f2, vec
|
|
@@ -132,7 +132,7 @@ class Assembler:
|
|
|
132
132
|
Returns:
|
|
133
133
|
tuple[np.ndarray, np.ndarray, np.ndarray, NedelecLegrange2]: The E, B, solve ids and Mixed order field object.
|
|
134
134
|
"""
|
|
135
|
-
from .
|
|
135
|
+
from .generalized_eigen_hb import generelized_eigenvalue_matrix
|
|
136
136
|
logger.debug('Assembling Boundary Mode Matrices')
|
|
137
137
|
|
|
138
138
|
bcs = bc_set.boundary_conditions
|
|
@@ -167,8 +167,6 @@ class Assembler:
|
|
|
167
167
|
pec_ids.extend(list(nedlegfield.tri_to_field[:,it]))
|
|
168
168
|
|
|
169
169
|
# Process all PEC Boundary Conditions
|
|
170
|
-
pec_edges = []
|
|
171
|
-
pec_vertices = []
|
|
172
170
|
for pec in pecs:
|
|
173
171
|
logger.trace(f'.implementing {pec}')
|
|
174
172
|
face_tags = pec.tags
|
|
@@ -180,16 +178,6 @@ class Assembler:
|
|
|
180
178
|
continue
|
|
181
179
|
eids = nedlegfield.edge_to_field[:, i2]
|
|
182
180
|
pec_ids.extend(list(eids))
|
|
183
|
-
pec_edges.append(eids[0])
|
|
184
|
-
pec_vertices.append(eids[3]-nedlegfield.n_xy)
|
|
185
|
-
pec_vertices.append(eids[4]-nedlegfield.n_xy)
|
|
186
|
-
|
|
187
|
-
for ii in tri_ids:
|
|
188
|
-
i2 = nedlegfield.mesh.from_source_tri(ii)
|
|
189
|
-
if i2 is None:
|
|
190
|
-
continue
|
|
191
|
-
tids = nedlegfield.tri_to_field[:, i2]
|
|
192
|
-
pec_ids.extend(list(tids))
|
|
193
181
|
|
|
194
182
|
# Process all port boundary Conditions
|
|
195
183
|
pec_ids_set: set[int] = set(pec_ids)
|
|
@@ -312,16 +300,11 @@ class Assembler:
|
|
|
312
300
|
gamma = bc.get_gamma(K0)
|
|
313
301
|
logger.trace(f'..robin bc γ={gamma:.3f}')
|
|
314
302
|
|
|
315
|
-
def Ufunc(x,y):
|
|
316
|
-
return bc.get_Uinc(x,y,K0)
|
|
303
|
+
def Ufunc(x,y,z):
|
|
304
|
+
return bc.get_Uinc(x,y,z,K0)
|
|
317
305
|
|
|
318
|
-
ibasis = bc.get_inv_basis()
|
|
319
|
-
if ibasis is None:
|
|
320
|
-
basis = plane_basis_from_points(mesh.nodes[:,nodes]) + 1e-16
|
|
321
|
-
ibasis = np.linalg.pinv(basis)
|
|
322
|
-
logger.trace(f'..Using computed basis: {ibasis.flatten()}')
|
|
323
306
|
if bc._include_force:
|
|
324
|
-
Bempty, b_p = assemble_robin_bc_excited(field, Bempty, tri_ids, Ufunc, gamma,
|
|
307
|
+
Bempty, b_p = assemble_robin_bc_excited(field, Bempty, tri_ids, Ufunc, gamma, gauss_points) # type: ignore
|
|
325
308
|
port_vectors[bc.port_number] += b_p # type: ignore
|
|
326
309
|
logger.trace(f'..included force vector term with norm {np.linalg.norm(b_p):.3f}')
|
|
327
310
|
else:
|
|
@@ -19,7 +19,7 @@ import numpy as np
|
|
|
19
19
|
from ....elements.nedleg2 import NedelecLegrange2
|
|
20
20
|
from scipy.sparse import csr_matrix
|
|
21
21
|
from numba_progress import ProgressBar, ProgressBarType
|
|
22
|
-
from ....mth.optimized import local_mapping, matinv, compute_distances
|
|
22
|
+
from ....mth.optimized import local_mapping, matinv, compute_distances, gaus_quad_tri
|
|
23
23
|
from numba import c16, types, f8, i8, njit, prange
|
|
24
24
|
|
|
25
25
|
|
|
@@ -52,7 +52,7 @@ def generelized_eigenvalue_matrix(field: NedelecLegrange2,
|
|
|
52
52
|
nT = tris.shape[1]
|
|
53
53
|
tri_to_field = field.tri_to_field
|
|
54
54
|
|
|
55
|
-
nodes =
|
|
55
|
+
nodes = field.local_nodes
|
|
56
56
|
|
|
57
57
|
with ProgressBar(total=nT, ncols=100, dynamic_ncols=False) as pgb:
|
|
58
58
|
dataE, dataB, rows, cols = _matrix_builder(nodes, tris, edges, tri_to_field, ur, er, k0, pgb)
|
|
@@ -179,6 +179,7 @@ def _ne1_curl(coeff, coords):
|
|
|
179
179
|
ys = coords[1,:]
|
|
180
180
|
return -3*a1*b1*c2 + 3*a1*b2*c1 - 3*b1**2*c2*xs + 3*b1*b2*c1*xs - 3*b1*c1*c2*ys + 3*b2*c1**2*ys + 0j
|
|
181
181
|
|
|
182
|
+
|
|
182
183
|
@njit(c16[:](f8[:,:], f8[:,:]), cache=True, nogil=True)
|
|
183
184
|
def _ne2_curl(coeff, coords):
|
|
184
185
|
a1, b1, c1 = coeff[:,0]
|
|
@@ -195,7 +196,7 @@ def _nf1_curl(coeff, coords):
|
|
|
195
196
|
xs = coords[0,:]
|
|
196
197
|
ys = coords[1,:]
|
|
197
198
|
return -b2*(c1*(a3 + b3*xs + c3*ys) - c3*(a1 + b1*xs + c1*ys)) + c2*(b1*(a3 + b3*xs + c3*ys) - b3*(a1 + b1*xs + c1*ys)) + 2*(b1*c3 - b3*c1)*(a2 + b2*xs + c2*ys) + 0*1j
|
|
198
|
-
|
|
199
|
+
|
|
199
200
|
@njit(c16[:](f8[:,:], f8[:,:]), cache=True, nogil=True)
|
|
200
201
|
def _nf2_curl(coeff, coords):
|
|
201
202
|
a1, b1, c1 = coeff[:,0]
|
|
@@ -204,7 +205,7 @@ def _nf2_curl(coeff, coords):
|
|
|
204
205
|
xs = coords[0,:]
|
|
205
206
|
ys = coords[1,:]
|
|
206
207
|
return b3*(c1*(a2 + b2*xs + c2*ys) - c2*(a1 + b1*xs + c1*ys)) - c3*(b1*(a2 + b2*xs + c2*ys) - b2*(a1 + b1*xs + c1*ys)) - 2*(b1*c2 - b2*c1)*(a3 + b3*xs + c3*ys) + 0*1j
|
|
207
|
-
|
|
208
|
+
|
|
208
209
|
|
|
209
210
|
############################################################
|
|
210
211
|
# TRIANGLE BARYCENTRIC COORDINATE LIN. COEFFICIENTS #
|
|
@@ -243,9 +244,9 @@ def tri_coefficients(vxs, vys):
|
|
|
243
244
|
|
|
244
245
|
|
|
245
246
|
DPTS = np.array([[0.22338159, 0.22338159, 0.22338159, 0.10995174, 0.10995174, 0.10995174],
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
[0.10810302, 0.44594849, 0.44594849, 0.81684757, 0.09157621, 0.09157621],
|
|
248
|
+
[0.44594849, 0.44594849, 0.10810302, 0.09157621, 0.09157621, 0.81684757],
|
|
249
|
+
[0.44594849, 0.10810302, 0.44594849, 0.09157621, 0.81684757, 0.09157621]], dtype=np.float64)
|
|
249
250
|
|
|
250
251
|
|
|
251
252
|
############################################################
|
|
@@ -266,7 +267,6 @@ def generalized_matrix_GQ(tri_vertices, local_edge_map, Ms, Mm, k0):
|
|
|
266
267
|
Dzz2 = np.zeros((6,6), dtype=np.complex128)
|
|
267
268
|
|
|
268
269
|
Ls = np.ones((14,14), dtype=np.float64)
|
|
269
|
-
#Ls2 = np.ones((14,14), dtype=np.float64)
|
|
270
270
|
|
|
271
271
|
WEIGHTS = DPTS[0,:]
|
|
272
272
|
DPTS1 = DPTS[1,:]
|
|
@@ -297,13 +297,11 @@ def generalized_matrix_GQ(tri_vertices, local_edge_map, Ms, Mm, k0):
|
|
|
297
297
|
Ms = Ms[:2,:2]
|
|
298
298
|
Mm = Mm[:2,:2]
|
|
299
299
|
|
|
300
|
-
fid = np.array([0,1,2], dtype=np.int64)
|
|
301
|
-
|
|
302
300
|
Ls[3,:] *= Ds[0,2]
|
|
303
301
|
Ls[7,:] *= Ds[0,1]
|
|
304
302
|
Ls[:,3] *= Ds[0,2]
|
|
305
303
|
Ls[:,7] *= Ds[0,1]
|
|
306
|
-
|
|
304
|
+
|
|
307
305
|
for iv1 in range(3):
|
|
308
306
|
ie1 = local_edge_map[:, iv1]
|
|
309
307
|
|
|
@@ -318,9 +316,12 @@ def generalized_matrix_GQ(tri_vertices, local_edge_map, Ms, Mm, k0):
|
|
|
318
316
|
F4 = _ne2(coeff[:,ie1], cs)
|
|
319
317
|
F5 = _lv_grad(coeff[:,iv1],cs)
|
|
320
318
|
F6 = _le_grad(coeff[:,ie1],cs)
|
|
319
|
+
|
|
321
320
|
for iv2 in range(3):
|
|
322
321
|
ei2 = local_edge_map[:, iv2]
|
|
323
322
|
|
|
323
|
+
H1 = matmul(Ms,_ne1(coeff[:,ei2],cs))
|
|
324
|
+
H2 = matmul(Ms,_ne2(coeff[:,ei2],cs))
|
|
324
325
|
|
|
325
326
|
Att[iv1,iv2] = _gqi(F1, Msz * _ne1_curl(coeff[:,ei2],cs), WEIGHTS)
|
|
326
327
|
Att[iv1+4,iv2] = _gqi(F2, Msz * _ne1_curl(coeff[:,ei2],cs), WEIGHTS)
|
|
@@ -332,15 +333,15 @@ def generalized_matrix_GQ(tri_vertices, local_edge_map, Ms, Mm, k0):
|
|
|
332
333
|
Btt[iv1,iv2+4] = _gqi2(F3, matmul(Mm,_ne2(coeff[:,ei2],cs)), WEIGHTS)
|
|
333
334
|
Btt[iv1+4,iv2+4] = _gqi2(F4, matmul(Mm,_ne2(coeff[:,ei2],cs)), WEIGHTS)
|
|
334
335
|
|
|
335
|
-
Dtt[iv1,iv2] = _gqi2(F3,
|
|
336
|
-
Dtt[iv1+4,iv2] = _gqi2(F4,
|
|
337
|
-
Dtt[iv1,iv2+4] = _gqi2(F3,
|
|
338
|
-
Dtt[iv1+4,iv2+4] = _gqi2(F4,
|
|
336
|
+
Dtt[iv1,iv2] = _gqi2(F3, H1, WEIGHTS)
|
|
337
|
+
Dtt[iv1+4,iv2] = _gqi2(F4, H1, WEIGHTS)
|
|
338
|
+
Dtt[iv1,iv2+4] = _gqi2(F3, H2, WEIGHTS)
|
|
339
|
+
Dtt[iv1+4,iv2+4] = _gqi2(F4, H2, WEIGHTS)
|
|
339
340
|
|
|
340
|
-
Dzt[iv1, iv2] = _gqi2(F5,
|
|
341
|
-
Dzt[iv1+3, iv2] = _gqi2(F6,
|
|
342
|
-
Dzt[iv1, iv2+4] = _gqi2(F5,
|
|
343
|
-
Dzt[iv1+3, iv2+4] = _gqi2(F6,
|
|
341
|
+
Dzt[iv1, iv2] = _gqi2(F5, H1, WEIGHTS)
|
|
342
|
+
Dzt[iv1+3, iv2] = _gqi2(F6, H1, WEIGHTS)
|
|
343
|
+
Dzt[iv1, iv2+4] = _gqi2(F5, H2, WEIGHTS)
|
|
344
|
+
Dzt[iv1+3, iv2+4] = _gqi2(F6, H2, WEIGHTS)
|
|
344
345
|
|
|
345
346
|
Dzz1[iv1, iv2] = _gqi2(_lv_grad(coeff[:,iv1], cs), matmul(Ms,_lv_grad(coeff[:,iv2],cs)), WEIGHTS)
|
|
346
347
|
Dzz1[iv1, iv2+3] = _gqi2(_lv_grad(coeff[:,iv1], cs), matmul(Ms,_le_grad(coeff[:,ei2],cs)), WEIGHTS)
|
|
@@ -357,6 +358,7 @@ def generalized_matrix_GQ(tri_vertices, local_edge_map, Ms, Mm, k0):
|
|
|
357
358
|
G2 = matmul(Mm,_nf2(coeff,cs))
|
|
358
359
|
G3 = matmul(Ms,_nf1(coeff,cs))
|
|
359
360
|
G4 = matmul(Ms,_nf2(coeff,cs))
|
|
361
|
+
|
|
360
362
|
Att[iv1,3] = _gqi(F1, Msz * _nf1_curl(coeff,cs), WEIGHTS)
|
|
361
363
|
Att[iv1+4,3] = _gqi(_ne2_curl(coeff[:,ie1], cs), Msz * _nf1_curl(coeff,cs), WEIGHTS)
|
|
362
364
|
Att[iv1,7] = _gqi(F1, Msz * _nf2_curl(coeff,cs), WEIGHTS)
|
|
@@ -412,6 +414,8 @@ def generalized_matrix_GQ(tri_vertices, local_edge_map, Ms, Mm, k0):
|
|
|
412
414
|
B[:8,8:] = Dzt.T
|
|
413
415
|
B[8:,8:] = Dzz1 - k0**2 * Dzz2
|
|
414
416
|
|
|
417
|
+
#Ls = np.ones((14,14), dtype=np.float64)
|
|
418
|
+
|
|
415
419
|
B = Ls*B*np.abs(Area)
|
|
416
420
|
A = Ls*A*np.abs(Area)
|
|
417
421
|
return A, B
|