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.

Files changed (39) hide show
  1. emerge/__init__.py +4 -1
  2. emerge/_emerge/cs.py +2 -2
  3. emerge/_emerge/elements/ned2_interp.py +21 -26
  4. emerge/_emerge/elements/nedleg2.py +27 -45
  5. emerge/_emerge/geo/__init__.py +1 -1
  6. emerge/_emerge/geo/modeler.py +2 -2
  7. emerge/_emerge/geo/pcb.py +4 -4
  8. emerge/_emerge/geo/shapes.py +37 -14
  9. emerge/_emerge/geometry.py +27 -1
  10. emerge/_emerge/howto.py +9 -9
  11. emerge/_emerge/material.py +1 -0
  12. emerge/_emerge/mesh3d.py +63 -14
  13. emerge/_emerge/mesher.py +7 -4
  14. emerge/_emerge/mth/optimized.py +30 -0
  15. emerge/_emerge/periodic.py +46 -16
  16. emerge/_emerge/physics/microwave/assembly/assembler.py +4 -21
  17. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +23 -19
  18. emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +465 -0
  19. emerge/_emerge/physics/microwave/assembly/robinbc.py +59 -18
  20. emerge/_emerge/physics/microwave/microwave_3d.py +38 -186
  21. emerge/_emerge/physics/microwave/microwave_bc.py +101 -35
  22. emerge/_emerge/physics/microwave/microwave_data.py +1 -1
  23. emerge/_emerge/plot/pyvista/display.py +40 -7
  24. emerge/_emerge/plot/pyvista/display_settings.py +1 -0
  25. emerge/_emerge/plot/simple_plots.py +159 -27
  26. emerge/_emerge/projects/_gen_base.txt +2 -2
  27. emerge/_emerge/projects/_load_base.txt +1 -1
  28. emerge/_emerge/simmodel.py +22 -7
  29. emerge/_emerge/solve_interfaces/cudss_interface.py +44 -2
  30. emerge/_emerge/solve_interfaces/pardiso_interface.py +1 -0
  31. emerge/_emerge/solver.py +26 -19
  32. emerge/ext.py +4 -0
  33. emerge/lib.py +1 -1
  34. {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/METADATA +6 -4
  35. {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/RECORD +38 -37
  36. emerge/_emerge/elements/legrange2.py +0 -172
  37. {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/WHEEL +0 -0
  38. {emerge-0.5.5.dist-info → emerge-0.6.0.dist-info}/entry_points.txt +0 -0
  39. {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 traingles
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 d,t in face_dimtags:
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 d,t in vol_dimtags:
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: GeoSurface | FaceSelection | Iterable,
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
- gmsh.model.mesh.field.setNumbers(disttag, "CurvesList", [n[1] for n in nodes])
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: GeoVolume | Selection, size: float):
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:
@@ -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
@@ -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 FaceSelection, SELECTOR_OBJ, Selection
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, f2: Selection, translation: tuple[float, float, float]):
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], self.fright[0][1]-self.fleft[0][1], self.fright[0][2]-self.fleft[0][2])
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], self.ftop[0][1]-self.fbot[0][1], self.ftop[0][2]-self.fbot[0][2])
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
- """Generates a Hexagonal periodic tiling by providing 4 coordinates. The layout of the tiling is as following
210
- Assuming a hexagon with a single vertext at the top and bottom and two vertices on the left and right faces ⬢
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
- p1 (tuple[float, float, float]): left face top vertex
214
- p2 (tuple[float, float, float]): left face bottom vertex
215
- p3 (tuple[float, float, float]): bottom vertex
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]).exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
253
- 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))
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]).exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
263
- 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))
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]).exclude(lambda x, y, z: (nrm(np.array([x,y])-o)>w) or (abs((np.array([x,y])-o) @ n ) > 1e-6))
272
- 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))
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 .generalized_eigen import generelized_eigenvalue_matrix
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, ibasis, bc.cs.origin, gauss_points) # type: ignore
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 = np.linalg.pinv(basis) @ 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
- [0.10810302, 0.44594849, 0.44594849, 0.81684757, 0.09157621, 0.09157621],
247
- [0.44594849, 0.44594849, 0.10810302, 0.09157621, 0.09157621, 0.81684757],
248
- [0.44594849, 0.10810302, 0.44594849, 0.09157621, 0.81684757, 0.09157621]], dtype=np.float64)
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, matmul(Ms,_ne1(coeff[:,ei2],cs)), WEIGHTS)
336
- Dtt[iv1+4,iv2] = _gqi2(F4, matmul(Ms,_ne1(coeff[:,ei2],cs)), WEIGHTS)
337
- Dtt[iv1,iv2+4] = _gqi2(F3, matmul(Ms,_ne2(coeff[:,ei2],cs)), WEIGHTS)
338
- Dtt[iv1+4,iv2+4] = _gqi2(F4, matmul(Ms,_ne2(coeff[:,ei2],cs)), WEIGHTS)
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, matmul(Ms,_ne1(coeff[:,ei2],cs)), WEIGHTS)
341
- Dzt[iv1+3, iv2] = _gqi2(F6, matmul(Ms,_ne1(coeff[:,ei2],cs)), WEIGHTS)
342
- Dzt[iv1, iv2+4] = _gqi2(F5, matmul(Ms,_ne2(coeff[:,ei2],cs)), WEIGHTS)
343
- Dzt[iv1+3, iv2+4] = _gqi2(F6, matmul(Ms,_ne2(coeff[:,ei2],cs)), WEIGHTS)
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