emerge 0.5.2__py3-none-any.whl → 0.5.4__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 (35) hide show
  1. emerge/__init__.py +2 -2
  2. emerge/_emerge/__init__.py +1 -26
  3. emerge/_emerge/_cache_check.py +46 -0
  4. emerge/_emerge/bc.py +3 -12
  5. emerge/_emerge/const.py +5 -0
  6. emerge/_emerge/elements/nedleg2.py +2 -2
  7. emerge/_emerge/geo/pcb.py +110 -13
  8. emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
  9. emerge/_emerge/geometry.py +1 -1
  10. emerge/_emerge/logsettings.py +29 -13
  11. emerge/_emerge/material.py +4 -0
  12. emerge/_emerge/mesh3d.py +9 -9
  13. emerge/_emerge/mth/integrals.py +1 -1
  14. emerge/_emerge/mth/pairing.py +1 -2
  15. emerge/_emerge/periodic.py +1 -1
  16. emerge/_emerge/physics/microwave/adaptive_freq.py +1 -5
  17. emerge/_emerge/physics/microwave/assembly/assembler.py +62 -39
  18. emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
  19. emerge/_emerge/physics/microwave/microwave_3d.py +33 -26
  20. emerge/_emerge/physics/microwave/microwave_bc.py +97 -27
  21. emerge/_emerge/physics/microwave/microwave_data.py +3 -5
  22. emerge/_emerge/physics/microwave/sc.py +26 -26
  23. emerge/_emerge/physics/microwave/simjob.py +8 -3
  24. emerge/_emerge/selection.py +1 -1
  25. emerge/_emerge/simmodel.py +12 -9
  26. emerge/_emerge/simulation_data.py +5 -1
  27. emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
  28. emerge/_emerge/solver.py +285 -107
  29. emerge/cli.py +1 -1
  30. emerge/lib.py +54 -40
  31. {emerge-0.5.2.dist-info → emerge-0.5.4.dist-info}/METADATA +15 -8
  32. {emerge-0.5.2.dist-info → emerge-0.5.4.dist-info}/RECORD +35 -32
  33. {emerge-0.5.2.dist-info → emerge-0.5.4.dist-info}/licenses/LICENSE +39 -0
  34. {emerge-0.5.2.dist-info → emerge-0.5.4.dist-info}/WHEEL +0 -0
  35. {emerge-0.5.2.dist-info → emerge-0.5.4.dist-info}/entry_points.txt +0 -0
emerge/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """A Python based FEM solver.
2
- Copyright (C) 2025 name of Robert Fennis
2
+ Copyright (C) 2025 Robert Fennis
3
3
 
4
4
  This program is free software; you can redistribute it and/or
5
5
  modify it under the terms of the GNU General Public License
@@ -36,7 +36,7 @@ os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
36
36
  ############################################################
37
37
  # IMPORT MODULES #
38
38
  ############################################################
39
-
39
+ from ._emerge import _cache_check
40
40
  from ._emerge.logsettings import LOG_CONTROLLER
41
41
  from loguru import logger
42
42
 
@@ -1,5 +1,5 @@
1
1
  # """A Python based FEM solver.
2
- # Copyright (C) 2025 name of Robert Fennis
2
+ # Copyright (C) 2025 Robert Fennis
3
3
 
4
4
  # This program is free software; you can redistribute it and/or
5
5
  # modify it under the terms of the GNU General Public License
@@ -15,28 +15,3 @@
15
15
  # along with this program; if not, see
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
- # """
19
- # import os
20
-
21
- # os.environ["OMP_NUM_THREADS"] = "1"
22
- # os.environ["MKL_NUM_THREADS"] = "1"
23
- # os.environ["OPENBLAS_NUM_THREADS"] = "1"
24
-
25
- # from loguru import logger
26
- # from .logsettings import logger_format
27
- # import sys
28
-
29
- # logger.remove()
30
- # logger.add(sys.stderr, format=logger_format)
31
-
32
- # logger.debug('Importing modules')
33
- # from _emerge.simmodel import Simulation3D
34
- # from _emerge.material import Material, FR4, AIR, VACUUM, COPPER
35
- # import bc
36
- # from _emerge.solver import superlu_info, SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK
37
- # from _emerge.cs import CoordinateSystem, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
38
- # from _emerge.coord import Line
39
- # import geo
40
- # from _emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
41
- # from _emerge.mth.common_functions import norm
42
- # logger.debug('Importing complete!')
@@ -0,0 +1,46 @@
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.core import event, types
19
+ from numba import njit
20
+
21
+ _COMPILE_MESSAGE = """
22
+ [ EMERGE ]
23
+ ⚠ Numba is compiling optimized code; this may take a few minutes.
24
+ • Additional functions may be compiled on-the-fly.
25
+ • Compilation happens only once—subsequent runs load from cache.
26
+ Please wait…"""
27
+
28
+ @njit(cache=True)
29
+ def _donothing(a):
30
+ return a
31
+
32
+ class Notify(event.Listener):
33
+ def on_start(self, ev):
34
+ f = ev.data['dispatcher']
35
+ sig = ev.data['args']
36
+ if f is _donothing: # limit to the function you care about
37
+ sig = ev.data['args']
38
+ print(_COMPILE_MESSAGE)
39
+
40
+ def on_end(self, ev): # unused here
41
+ pass
42
+
43
+
44
+ # install listener only for this block:
45
+ with event.install_listener("numba:compile", Notify()):
46
+ _donothing(0)
emerge/_emerge/bc.py CHANGED
@@ -64,17 +64,8 @@ class BoundaryCondition:
64
64
  return self.dimension.value
65
65
 
66
66
  def __repr__(self) -> str:
67
- if self.dimension is BCDimension.ANY:
68
- return f'{type(self).__name__}{self.tags}'
69
- elif self.dimension is BCDimension.EDGE:
70
- return f'{type(self).__name__}{self.tags}'
71
- elif self.dimension is BCDimension.NODE:
72
- return f'{type(self).__name__}{self.tags}'
73
- elif self.dimension is BCDimension.FACE:
74
- return f'{type(self).__name__}{self.tags}'
75
- elif self.dimension is BCDimension.DOMAIN:
76
- return f'{type(self).__name__}{self.tags}'
77
-
67
+ return f'{type(self).__name__}{self.tags}'
68
+
78
69
  def __str__(self) -> str:
79
70
  return self.__repr__()
80
71
 
@@ -183,7 +174,7 @@ class BoundaryConditionSet:
183
174
  Returns:
184
175
  list[BoundaryCondition]: The list of boundary conditions
185
176
  """
186
- return [item for item in self. boundary_conditions if isinstance(item, bctype)]
177
+ return [item for item in self.boundary_conditions if isinstance(item, bctype)]
187
178
 
188
179
  def reset(self) -> None:
189
180
  """Resets the boundary conditions that are defined
@@ -0,0 +1,5 @@
1
+ C0 = 299792458
2
+ Z0 = 376.73031366857
3
+ PI = 3.14159265358979323846
4
+ EPS0 = 8.854187818814e-12
5
+ MU0 = 1/(C0*C0*EPS0)#1.2566370612720e-6
@@ -22,7 +22,7 @@ from .femdata import FEMBasis
22
22
  from .ned2_interp import ned2_tri_interp_full, ned2_tri_interp_curl
23
23
  from ..mth.optimized import matinv
24
24
  from ..cs import CoordinateSystem
25
-
25
+ from ..const import MU0, C0
26
26
 
27
27
  ## TODO: TEMPORARY SOLUTION FIX THIS
28
28
 
@@ -158,7 +158,7 @@ class NedelecLegrange2(FEMBasis):
158
158
 
159
159
  def interpolate_Hf(self, field: np.ndarray, k0: float, ur: np.ndarray, beta: float) -> FieldFunctionClass:
160
160
  '''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
161
- constant = 1j / ((k0*299792458)*(4*np.pi*1e-7))
161
+ constant = 1j / ((k0*C0)*MU0)
162
162
  urinv = np.zeros_like(ur)
163
163
 
164
164
  for i in range(ur.shape[2]):
emerge/_emerge/geo/pcb.py CHANGED
@@ -17,9 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- import numpy as np
21
- import gmsh
22
-
23
20
  from ..cs import CoordinateSystem, GCS, Axis
24
21
  from ..geometry import GeoPolygon, GeoVolume, GeoSurface
25
22
  from ..material import Material, AIR, COPPER
@@ -28,11 +25,14 @@ from .polybased import XYPolygon
28
25
  from .operations import change_coordinate_system
29
26
  from .pcb_tools.macro import parse_macro
30
27
  from .pcb_tools.calculator import PCBCalculator
28
+
29
+ import numpy as np
31
30
  from loguru import logger
32
31
  from typing import Literal, Callable, overload
33
32
  from dataclasses import dataclass
34
- import math
35
33
 
34
+ import math
35
+ import gmsh
36
36
 
37
37
  ############################################################
38
38
  # EXCEPTIONS #
@@ -63,7 +63,15 @@ def normalize(vector: np.ndarray) -> np.ndarray:
63
63
  return vector
64
64
  return vector / norm
65
65
 
66
- def _rot_mat(angle):
66
+ def _rot_mat(angle: float) -> np.ndarray:
67
+ """Returns a 2D rotation matrix given an angle in degrees
68
+
69
+ Args:
70
+ angle (float): The angle in degrees
71
+
72
+ Returns:
73
+ np.ndarray: The rotation matrix
74
+ """
67
75
  ang = -angle * np.pi/180
68
76
  return np.array([[np.cos(ang), -np.sin(ang)], [np.sin(ang), np.cos(ang)]])
69
77
 
@@ -265,6 +273,66 @@ class StripTurn(RouteElement):
265
273
  else:
266
274
  raise RouteException(f'Trying to route a StripTurn with an unknown corner type: {self.corner_type}')
267
275
 
276
+ class StripCurve(StripTurn):
277
+ def __init__(self,
278
+ x: float,
279
+ y: float,
280
+ width: float,
281
+ direction: tuple[float, float],
282
+ angle: float,
283
+ radius: float,
284
+ dang: float = 10.0):
285
+ self.xold: float = x
286
+ self.yold: float = y
287
+ self.width: float = width
288
+ self.old_direction: np.ndarray = normalize(np.array(direction))
289
+ self.direction: np.ndarray = _rot_mat(angle) @ self.old_direction
290
+ self.angle: float = angle
291
+ self.radius: float = radius
292
+ self.dirright: np.ndarray = np.array([self.old_direction[1], -self.old_direction[0]])
293
+ self.dang: float = dang
294
+
295
+ angd = abs(angle*np.pi/180)
296
+ self.start = np.array([x,y])
297
+ self.circ_origin = self.start + radius * np.sign(angle) * self.dirright
298
+
299
+ self._xhat = -self.dirright * np.sign(angle)
300
+ self._yhat = self.old_direction
301
+
302
+ self.end = self.circ_origin + radius*(self._xhat*np.cos(angd)+self._yhat*np.sin(angd))
303
+ self.x, self.y = self.end
304
+
305
+ def __str__(self) -> str:
306
+ return f'StripCurve[{self.x},{self.y},w={self.width},d=({self.direction})]'
307
+
308
+ @property
309
+ def right(self) -> list[tuple[float, float]]:
310
+ points: list[tuple[float, float]] = []
311
+ Npts = int(np.ceil(abs(self.angle/self.dang)))
312
+ R = self.radius-np.sign(self.angle)*self.width/2
313
+ print(R, self.circ_origin, self._xhat, self._yhat)
314
+ for i in range(Npts):
315
+ ang = abs((i+1)/Npts * self.angle * np.pi/180)
316
+ pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
317
+ points.append((pnew[0], pnew[1]))
318
+
319
+ return points
320
+
321
+ @property
322
+ def left(self) -> list[tuple[float, float]]:
323
+ points: list[tuple[float, float]] = []
324
+
325
+ Npts = int(np.ceil(abs(self.angle/self.dang)))
326
+ R = self.radius+np.sign(self.angle)*self.width/2
327
+ print(R, self.circ_origin, self._xhat, self._yhat)
328
+ for i in range(Npts):
329
+ ang = abs((i+1)/Npts * self.angle * np.pi/180)
330
+ pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
331
+ points.append((pnew[0], pnew[1]))
332
+
333
+ return points[::-1]
334
+
335
+
268
336
  ############################################################
269
337
  # THE STRIP PATH CLASS #
270
338
  ############################################################
@@ -405,7 +473,9 @@ class StripPath:
405
473
  """
406
474
  x, y = self.end.x, self.end.y
407
475
  dx, dy = self.end.direction
408
-
476
+ if abs(angle) <= 20:
477
+ corner_type = 'square'
478
+ logger.warning('Small turn detected, defaulting to rectangular corners because chamfers would add to much detail.')
409
479
  if width is not None:
410
480
  if width != self.end.width:
411
481
  self._add_element(StripLine(x, y, width, (dx, dy)))
@@ -413,6 +483,37 @@ class StripPath:
413
483
  width=self.end.width
414
484
  self._add_element(StripTurn(x, y, width, (dx, dy), angle, corner_type))
415
485
  return self
486
+
487
+ def curve(self, angle: float, radius: float,
488
+ width: float | None = None,
489
+ corner_type: Literal['champher','square'] = 'champher',
490
+ dang: float = 10) -> StripPath:
491
+ """Adds a bend to the strip path.
492
+
493
+ The angle is specified in degrees. The width of the turn will be the same as the last segment.
494
+ optionally, a different width may be provided.
495
+ By default, all corners will be cut using the "champher" type. Other options are not yet provided.
496
+
497
+ Args:
498
+ angle (float): The turning angle
499
+ width (float, optional): The stripline width. Defaults to None.
500
+ corner_type (str, optional): The corner type. Defaults to 'champher'.
501
+
502
+ Returns:
503
+ StripPath: The current StripPath object
504
+ """
505
+ if angle == 0:
506
+ logger.trace('Zero angle turn, passing action')
507
+ return self
508
+ x, y = self.end.x, self.end.y
509
+ dx, dy = self.end.direction
510
+ if width is not None:
511
+ if width != self.end.width:
512
+ self._add_element(StripLine(x, y, width, (dx, dy)))
513
+ else:
514
+ width=self.end.width
515
+ self._add_element(StripCurve(x, y, width, (dx, dy), angle, radius, dang=dang))
516
+ return self
416
517
 
417
518
  def store(self, name: str) -> StripPath:
418
519
  """ Store the current x,y coordinate labeled in the PCB object.
@@ -495,7 +596,6 @@ class StripPath:
495
596
  self.pcb._lumped_element(poly, impedance_function, width, length)
496
597
  return self.pcb.new(x+dx*length, y+dy*length, self.end.width, self.end.direction, self.z)
497
598
 
498
-
499
599
  def cut(self) -> StripPath:
500
600
  """Split the current path in N new paths given by a new departure direction
501
601
 
@@ -637,7 +737,7 @@ class StripPath:
637
737
  def to(self, dest: tuple[float, float],
638
738
  arrival_dir: tuple[float, float] | None = None,
639
739
  arrival_margin: float | None= None,
640
- angle_step: float = 90):
740
+ angle_step: float = 90) -> StripPath:
641
741
  """
642
742
  Extend the path from current end point to dest (x, y).
643
743
  Optionally ensure arrival in arrival_dir after a straight segment of arrival_margin.
@@ -743,9 +843,8 @@ class StripPath:
743
843
  back_ang = math.degrees(math.atan2(cross2, dot2))
744
844
 
745
845
  backoff = math.tan(abs(back_ang)*np.pi/360)*self.end.width/2
746
- self.straight(s - backoff)
747
-
748
846
 
847
+ self.straight(s - backoff)
749
848
  self.turn(-back_ang)
750
849
 
751
850
  x0 = self.end.x
@@ -753,7 +852,6 @@ class StripPath:
753
852
  D = math.hypot(tx-x0, ty-y0)
754
853
  # 4) Final straight into destination by arrival_margin + t
755
854
  self.straight(D)
756
-
757
855
 
758
856
  return self
759
857
 
@@ -801,7 +899,7 @@ class StripPath:
801
899
  return self.path[element_nr]
802
900
 
803
901
  ############################################################
804
- # PCB DESIGN CLASS #
902
+ # PCB DESIGN CLASS #
805
903
  ############################################################
806
904
 
807
905
  class PCB:
@@ -1270,7 +1368,6 @@ class PCB:
1270
1368
  polys.material = COPPER
1271
1369
  return polys
1272
1370
 
1273
-
1274
1371
  ############################################################
1275
1372
  # DEPRICATED #
1276
1373
  ############################################################
@@ -1,9 +1,9 @@
1
1
  import numpy as np
2
2
  from ...material import Material
3
+ from ...const import Z0 as n0
3
4
 
4
5
  PI = np.pi
5
6
  TAU = 2*PI
6
- n0 = 376.73
7
7
 
8
8
  def microstrip_z0(W: float, th: float, er: float):
9
9
  u = W/th
@@ -14,7 +14,7 @@ def microstrip_z0(W: float, th: float, er: float):
14
14
 
15
15
  class PCBCalculator:
16
16
 
17
- def __init__(self, thickness: float, layers: np.array, material: Material, unit: float):
17
+ def __init__(self, thickness: float, layers: np.ndarray, material: Material, unit: float):
18
18
  self.th = thickness
19
19
  self.layers = layers
20
20
  self.mat = material
@@ -299,7 +299,7 @@ class GeoObject:
299
299
  for tag in self.tags:
300
300
  newtags.extend(tagmap.get(tag, [tag,]))
301
301
  self.tags = newtags
302
- logger.debug(f'Replaced {self.old_tags} with {self.tags}')
302
+ logger.debug(f'{self} Replaced {self.old_tags} -> {self.tags}')
303
303
 
304
304
  def update_tags(self, tag_mapping: dict[int,dict]) -> GeoObject:
305
305
  ''' Update the tag definition of a GeoObject after fragementation.'''
@@ -1,34 +1,52 @@
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 loguru import logger
2
19
  import sys
3
20
  from typing import Literal
4
21
  from enum import Enum
5
22
  from pathlib import Path
6
23
  import os
7
-
24
+ import gmsh
8
25
 
9
26
  ############################################################
10
27
  # FORMATS #
11
28
  ############################################################
12
29
 
13
30
  TRACE_FORMAT = (
14
- "{time: YY/MM/DD - (ddd) - HH:mm:ss.SSSS} | <green>{elapsed}</green> [ <level>{level}</level> ] "
15
- " <level>{message}</level>"
31
+ "{time:ddd YY/MM/DD HH:mm:ss.SSSS} {level:<7} {thread.id:<15} {line:>4}: "
32
+ "{message}"
16
33
  )
34
+
17
35
  DEBUG_FORMAT = (
18
- "<green>{elapsed}</green> [<level>{level}</level>] "
19
- " <level>{message}</level>"
36
+ "<green>{elapsed}</green> <level>{level:<7}</level>: "
37
+ "<level>{message}</level>"
20
38
  )
21
39
  INFO_FORMAT = (
22
- "<green>{elapsed}</green> [<level>{level}</level>] "
23
- " <level>{message}</level>"
40
+ "<green>{elapsed}</green> <level>{level:<7}</level>: "
41
+ "<level>{message}</level>"
24
42
  )
25
43
  WARNING_FORMAT = (
26
- "<green>{elapsed}</green> [<level>{level}</level>] "
27
- " <level>{message}</level>"
44
+ "<green>{elapsed}</green> <level>{level:<7}</level>: "
45
+ "<level>{message}</level>"
28
46
  )
29
47
  ERROR_FORMAT = (
30
- "<green>{elapsed}</green> [<level>{level}</level>] "
31
- " <level>{message}</level>"
48
+ "<green>{elapsed}</green> <level>{level:<7}</level>: "
49
+ "<level>{message}</level>"
32
50
  )
33
51
  FORMAT_DICT = {
34
52
  'TRACE': TRACE_FORMAT,
@@ -71,10 +89,8 @@ class LogController:
71
89
  logger.configure(handlers=[handler]) # type: ignore
72
90
  self.level = loglevel
73
91
  os.environ["EMERGE_STD_LOGLEVEL"] = loglevel
74
-
75
92
 
76
93
  def set_write_file(self, path: Path, loglevel: str = 'TRACE'):
77
-
78
94
  handler_id = logger.add(str(path / 'logging.log'), mode='w', level=loglevel, format=FORMAT_DICT.get(loglevel, INFO_FORMAT), colorize=False, backtrace=True, diagnose=True)
79
95
  self.file_handlers.append(handler_id)
80
96
  self.file_level = loglevel
@@ -45,6 +45,10 @@ class Material:
45
45
  hex_str = self.color.lstrip('#')
46
46
  self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
47
47
 
48
+ @property
49
+ def sigma(self) -> float:
50
+ return self.cond
51
+
48
52
  @property
49
53
  def color_rgb(self) -> tuple[float,float,float]:
50
54
  return self._color_rgb
emerge/_emerge/mesh3d.py CHANGED
@@ -455,15 +455,11 @@ class Mesh3D(Mesh):
455
455
  Returns:
456
456
  tuple[dict[int, int], np.ndarray, np.ndarray]: The node index mapping and the node index arrays
457
457
  """
458
-
459
- def gen_key(coord, mult):
460
- return tuple([int(round(c*mult)) for c in coord])
461
458
 
462
- ftag_to_node = dict()
463
- face_dimtags = gmsh.model.get_entities(2)
464
-
465
459
  node_ids_1 = []
466
460
  node_ids_2 = []
461
+
462
+ face_dimtags = gmsh.model.get_entities(2)
467
463
 
468
464
  for d,t in face_dimtags:
469
465
  domain_tag, f_tags, node_tags = gmsh.model.mesh.get_elements(2, t)
@@ -472,19 +468,23 @@ class Mesh3D(Mesh):
472
468
  node_ids_1.extend(node_tags)
473
469
  if t in bc.face2.tags:
474
470
  node_ids_2.extend(node_tags)
475
- ftag_to_node[t] = node_tags
471
+
472
+
473
+ node_ids_1 = sorted(list(set(node_ids_1)))
474
+ node_ids_2 = sorted(list(set(node_ids_2)))
476
475
 
477
476
  all_node_ids = np.unique(np.array(node_ids_1 + node_ids_2))
478
477
  dsmin = shortest_distance(self.nodes[:,all_node_ids])
479
478
 
480
- node_ids_1_arry = np.sort(np.unique(np.array(node_ids_1)))
481
- node_ids_2_arry = np.sort(np.unique(np.array(node_ids_2)))
479
+ node_ids_1_arry = np.array(node_ids_1)
480
+ node_ids_2_arry = np.array(node_ids_2)
482
481
  dv = np.array(bc.dv)
483
482
 
484
483
  nodemap = pair_coordinates(self.nodes, node_ids_1_arry, node_ids_2_arry, dv, dsmin/2)
485
484
  node_ids_2_unsorted = [nodemap[i] for i in sorted(node_ids_1)]
486
485
  node_ids_2_sorted = sorted(node_ids_2_unsorted)
487
486
  conv_map = {i1: i2 for i1, i2 in zip(node_ids_2_unsorted, node_ids_2_sorted)}
487
+
488
488
  return conv_map, np.array(node_ids_2_unsorted), np.array(node_ids_2_sorted)
489
489
 
490
490
 
@@ -60,7 +60,7 @@ def _fast_integral_f(nodes, triangles, constants, DPTs, field_values):
60
60
  def surface_integral(nodes: np.ndarray,
61
61
  triangles: np.ndarray,
62
62
  function: Callable,
63
- constants: np.ndarray = None,
63
+ constants: np.ndarray | None = None,
64
64
  gq_order: int = 4) -> complex:
65
65
  """Computes the surface integral of a scalar-field
66
66
 
@@ -47,7 +47,6 @@ def link_coords(coords: np.ndarray, ids1: np.ndarray, ids2: np.ndarray, disp: np
47
47
  for i1 in range(N):
48
48
  ictr = 0
49
49
  c1 = coords[:,ids1[i1]]
50
-
51
50
  for i2 in range(id_start, N):
52
51
  if available[i2] == 0:
53
52
  continue
@@ -91,7 +90,7 @@ def pair_coordinates(coords: np.ndarray, ids1: np.ndarray, ids2: np.ndarray, dis
91
90
  ids2_c_sorted = sorted(ids2, key= lambda x: tuple(coords[:,x]-disp))
92
91
 
93
92
  mapping = link_coords(coords, np.array(ids1_c_sorted), np.array(ids2_c_sorted), disp, dsmax)
94
-
93
+
95
94
  mapping = {i: j for i,j in zip(mapping[0,:], mapping[1,:])}
96
95
 
97
96
  return mapping
@@ -31,7 +31,7 @@ def _pair_selection(f1: Selection, f2: Selection, translation: tuple[float, floa
31
31
  f2s = []
32
32
  for t1, c1 in zip(f1.tags, c1s):
33
33
  for t2, c2 in zip(f2.tags, c2s):
34
- if np.linalg.norm((c1 + ds)-c2) < 1e-6:
34
+ if np.linalg.norm((c1 + ds)-c2) < 1e-8:
35
35
  f1s.append(Selection([t1,]))
36
36
  f2s.append(Selection([t2,]))
37
37
  return f1s, f2s
@@ -108,10 +108,6 @@ def vectfit_step(f: np.ndarray, s: np.ndarray, poles: np.ndarray) -> np.ndarray:
108
108
  b = np.concatenate((b.real, b.imag))
109
109
  x, residuals, rnk, s = np.linalg.lstsq(A, b, rcond=-1)
110
110
 
111
- residues = x[:N]
112
- d = x[N]
113
- h = x[N+1]
114
-
115
111
  # We only want the "tilde" part in (A.4)
116
112
  x = x[-N:]
117
113
 
@@ -197,7 +193,7 @@ def vectfit_auto(f: np.ndarray,
197
193
  w = s.imag
198
194
  pole_locs = np.linspace(w[0], w[-1], n_poles+2)[1:-1]
199
195
  lr = loss_ratio
200
- init_poles = poles = np.concatenate([[p*(-lr + 1j), p*(-lr - 1j)] for p in pole_locs])
196
+ poles = np.concatenate([[p*(-lr + 1j), p*(-lr - 1j)] for p in pole_locs])
201
197
 
202
198
  if inc_real:
203
199
  poles = np.concatenate((poles, [1]))