emerge 0.5.1__py3-none-any.whl → 0.5.3__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 (54) hide show
  1. emerge/_emerge/bc.py +14 -20
  2. emerge/_emerge/const.py +5 -0
  3. emerge/_emerge/cs.py +2 -2
  4. emerge/_emerge/elements/femdata.py +14 -14
  5. emerge/_emerge/elements/index_interp.py +1 -1
  6. emerge/_emerge/elements/ned2_interp.py +1 -1
  7. emerge/_emerge/elements/nedelec2.py +4 -4
  8. emerge/_emerge/elements/nedleg2.py +10 -10
  9. emerge/_emerge/geo/horn.py +1 -1
  10. emerge/_emerge/geo/modeler.py +18 -19
  11. emerge/_emerge/geo/operations.py +13 -10
  12. emerge/_emerge/geo/pcb.py +180 -82
  13. emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
  14. emerge/_emerge/geo/pcb_tools/macro.py +14 -13
  15. emerge/_emerge/geo/pmlbox.py +1 -1
  16. emerge/_emerge/geometry.py +47 -33
  17. emerge/_emerge/logsettings.py +15 -16
  18. emerge/_emerge/material.py +15 -11
  19. emerge/_emerge/mesh3d.py +81 -59
  20. emerge/_emerge/mesher.py +26 -21
  21. emerge/_emerge/mth/integrals.py +1 -1
  22. emerge/_emerge/mth/pairing.py +2 -2
  23. emerge/_emerge/periodic.py +34 -31
  24. emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
  25. emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
  26. emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
  27. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
  28. emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
  29. emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
  30. emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
  31. emerge/_emerge/physics/microwave/microwave_data.py +36 -38
  32. emerge/_emerge/physics/microwave/sc.py +26 -26
  33. emerge/_emerge/physics/microwave/simjob.py +20 -15
  34. emerge/_emerge/physics/microwave/sparam.py +12 -12
  35. emerge/_emerge/physics/microwave/touchstone.py +1 -1
  36. emerge/_emerge/plot/display.py +12 -6
  37. emerge/_emerge/plot/pyvista/display.py +44 -39
  38. emerge/_emerge/plot/pyvista/display_settings.py +1 -1
  39. emerge/_emerge/plot/simple_plots.py +15 -15
  40. emerge/_emerge/selection.py +35 -39
  41. emerge/_emerge/simmodel.py +41 -47
  42. emerge/_emerge/simulation_data.py +24 -15
  43. emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
  44. emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
  45. emerge/_emerge/solver.py +314 -136
  46. emerge/cli.py +1 -1
  47. emerge/lib.py +245 -248
  48. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
  49. emerge-0.5.3.dist-info/RECORD +83 -0
  50. emerge/_emerge/plot/grapher.py +0 -93
  51. emerge-0.5.1.dist-info/RECORD +0 -82
  52. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
  53. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
  54. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/mesher.py CHANGED
@@ -15,12 +15,12 @@
15
15
  # along with this program; if not, see
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
- import gmsh
18
+ import gmsh # type: ignore
19
19
  from .geometry import GeoVolume, GeoObject, GeoSurface
20
20
  from .selection import Selection, FaceSelection
21
21
  from .periodic import PeriodicCell
22
22
  import numpy as np
23
- from typing import Iterable, Callable
23
+ from typing import Iterable, Callable, Any, TypeVar
24
24
  from loguru import logger
25
25
  from enum import Enum
26
26
  from .bc import Periodic
@@ -55,7 +55,9 @@ _DOM_TO_STR = {
55
55
  2: "face",
56
56
  3: "volume",
57
57
  }
58
- def unpack_lists(_list: list[list], collector: list = None) -> list:
58
+
59
+ T = TypeVar('T')
60
+ def unpack_lists(_list: Any, collector: list | None = None) -> list[Any]:
59
61
  '''Unpack a recursive list of lists'''
60
62
  if collector is None:
61
63
  collector = []
@@ -116,7 +118,7 @@ class Mesher:
116
118
  raise MeshError('Either maximum or minimum mesh size is undefined. Make sure \
117
119
  to set the simulation frequency range before calling mesh instructions.')
118
120
 
119
- def submit_objects(self, objects: list[GeoObject]) -> None:
121
+ def submit_objects(self, objects: GeoObject | list[GeoObject] | list[list[GeoObject]]) -> None:
120
122
  """Takes al ist of GeoObjects and computes the fragment.
121
123
 
122
124
  Args:
@@ -126,16 +128,15 @@ class Mesher:
126
128
  objects = [objects,]
127
129
 
128
130
  objects = unpack_lists(objects)
129
- embeddings = []
130
-
131
+ embeddings: list = []
131
132
  gmsh.model.occ.synchronize()
132
133
 
133
- final_dimtags = unpack_lists([domain.dimtags for domain in objects])
134
+ final_dimtags = unpack_lists([domain.dimtags for domain in objects]) # type: ignore
134
135
 
135
136
  dom_mapping = dict()
136
- for dom in objects:
137
- embeddings.extend(dom._embeddings)
138
- for dt in dom.dimtags:
137
+ for dom in objects: # type: ignore
138
+ embeddings.extend(dom._embeddings) # type: ignore
139
+ for dt in dom.dimtags: # type: ignore
139
140
  dom_mapping[dt] = dom
140
141
 
141
142
 
@@ -145,24 +146,24 @@ class Mesher:
145
146
  1: dict(),
146
147
  2: dict(),
147
148
  3: dict()}
148
- if len(objects) > 0:
149
+ if len(objects) > 0: # type: ignore
149
150
  dimtags, output_mapping = gmsh.model.occ.fragment(final_dimtags, embedding_dimtags)
150
151
 
151
152
  for domain, mapping in zip(final_dimtags + embedding_dimtags, output_mapping):
152
153
  tag_mapping[domain[0]][domain[1]] = [o[1] for o in mapping]
153
- for dom in objects:
154
- dom.update_tags(tag_mapping)
154
+ for dom in objects: # type: ignore
155
+ dom.update_tags(tag_mapping) # type: ignore
155
156
  else:
156
157
  dimtags = final_dimtags
157
158
 
158
- self.objects = objects
159
+ self.objects = objects # type: ignore
159
160
 
160
161
  gmsh.model.occ.synchronize()
161
162
 
162
163
  def _set_mesh_periodicity(self,
163
- face1: FaceSelection,
164
- face2: FaceSelection,
165
- lattice: tuple[float,float,float]):
164
+ face1: Selection,
165
+ face2: Selection,
166
+ lattice: np.ndarray):
166
167
  translation = [1,0,0,lattice[0],
167
168
  0,1,0,lattice[1],
168
169
  0,0,1,lattice[2],
@@ -175,7 +176,7 @@ class Mesher:
175
176
  for tag in obj.tags:
176
177
  gmsh.model.mesh.setAlgorithm(obj.dim, tag, algorithm.value)
177
178
 
178
- def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: list[FaceSelection] = None):
179
+ def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: Selection | None = None):
179
180
  """Sets the periodic cell information based on the PeriodicCell class object"""
180
181
  if excluded_faces is None:
181
182
  for f1, f2, lat in cell.cell_data():
@@ -243,10 +244,10 @@ class Mesher:
243
244
  for dimtag in dimtags:
244
245
  gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
245
246
 
246
- def set_boundary_size(self, object: GeoSurface | FaceSelection,
247
+ def set_boundary_size(self, boundary: GeoSurface | FaceSelection | Iterable,
247
248
  size:float,
248
249
  growth_rate: float = 1.4,
249
- max_size: float = None):
250
+ max_size: float | None = None) -> None:
250
251
  """Refine the mesh size along the boundary of a conducting surface
251
252
 
252
253
  Args:
@@ -255,8 +256,12 @@ class Mesher:
255
256
  growth_rate (float, optional): _description_. Defaults to 1.1.
256
257
  max_size (float, optional): _description_. Defaults to None.
257
258
  """
259
+ if isinstance(boundary, Iterable):
260
+ for bound in boundary:
261
+ self.set_boundary_size(bound, size, growth_rate, max_size)
262
+ return
258
263
 
259
- dimtags = object.dimtags
264
+ dimtags = boundary.dimtags
260
265
 
261
266
  if max_size is None:
262
267
  self._check_ready()
@@ -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
 
@@ -27,7 +27,7 @@ ALL PAIRED NODES ARE NO LONGER VISITED
27
27
  """
28
28
 
29
29
  import numpy as np
30
- from numba import njit, f8, i8
30
+ from numba import njit, f8, i8 # type: ignore
31
31
 
32
32
 
33
33
  ############################################################
@@ -72,7 +72,7 @@ def link_coords(coords: np.ndarray, ids1: np.ndarray, ids2: np.ndarray, disp: np
72
72
  # MAIN PYTHON INTERFACE #
73
73
  ############################################################
74
74
 
75
- def pair_coordinates(coords: np.ndarray, ids1: list[int], ids2: list[int], disp: np.ndarray, dsmax: float) -> dict[int, int]:
75
+ def pair_coordinates(coords: np.ndarray, ids1: np.ndarray, ids2: np.ndarray, disp: np.ndarray, dsmax: float) -> dict[int, int]:
76
76
  """ This function finds the mapping between a total coordinate set and two lits of indices.
77
77
 
78
78
  The indices correspond to two faces that are identical but displaced (mesh centroids of periodic boundaries).
@@ -1,5 +1,5 @@
1
- from .cs import Axis, _parse_axis, GCS
2
- from .selection import FaceSelection, SELECTOR_OBJ
1
+ from .cs import Axis, _parse_axis, GCS, _parse_vector
2
+ from .selection import FaceSelection, SELECTOR_OBJ, Selection
3
3
  from .geo import GeoPrism, XYPolygon, Alignment, XYPlate
4
4
  from .bc import BoundaryCondition
5
5
  from typing import Generator
@@ -21,7 +21,7 @@ def _rotnorm(v: np.ndarray) -> np.ndarray:
21
21
  ax = ax/np.linalg.norm(ax)
22
22
  return ax
23
23
 
24
- def _pair_selection(f1: FaceSelection, f2: FaceSelection, translation: tuple[float, float, float]):
24
+ def _pair_selection(f1: Selection, f2: Selection, translation: tuple[float, float, float]):
25
25
  if len(f1.tags) == 1:
26
26
  return [f1,], [f2,]
27
27
  c1s = [np.array(c) for c in f1.centers]
@@ -32,8 +32,8 @@ def _pair_selection(f1: FaceSelection, f2: FaceSelection, translation: tuple[flo
32
32
  for t1, c1 in zip(f1.tags, c1s):
33
33
  for t2, c2 in zip(f2.tags, c2s):
34
34
  if np.linalg.norm((c1 + ds)-c2) < 1e-6:
35
- f1s.append(FaceSelection([t1,]))
36
- f2s.append(FaceSelection([t2,]))
35
+ f1s.append(Selection([t1,]))
36
+ f2s.append(Selection([t2,]))
37
37
  return f1s, f2s
38
38
 
39
39
 
@@ -42,15 +42,16 @@ def _pair_selection(f1: FaceSelection, f2: FaceSelection, translation: tuple[flo
42
42
  # BASE PERIODIC CELL CLASS #
43
43
  ############################################################
44
44
 
45
-
45
+ # TODO: This must be moved to mw physics if possible
46
46
  class PeriodicCell:
47
47
 
48
48
  def __init__(self,
49
- origins: list[tuple[float, float, float]],
50
- vectors: list[tuple[float, float, float] | Axis]):
51
- self.origins: list[tuple[float, float, float]] = origins
49
+ origins: list[tuple[float, float, float] | list[float] | np.ndarray | Axis],
50
+ vectors: list[tuple[float, float, float] | list[float] | np.ndarray | Axis]):
51
+
52
+ self.origins: list[tuple[float, float, float]] = [_parse_vector(origin) for origin in origins] # type: ignore
52
53
  self.vectors: list[Axis] = [_parse_axis(vec) for vec in vectors]
53
- self.excluded_faces: FaceSelection = None
54
+ self.excluded_faces: Selection | None = None
54
55
  self._bcs: list[Periodic] = []
55
56
  self._ports: list[BoundaryCondition] = []
56
57
 
@@ -71,7 +72,7 @@ class PeriodicCell:
71
72
  """
72
73
  raise NotImplementedError('This method is not implemented for this subclass.')
73
74
 
74
- def cell_data(self) -> Generator[tuple[FaceSelection,FaceSelection,tuple[float, float, float]], None, None]:
75
+ def cell_data(self) -> Generator[tuple[Selection, Selection, np.ndarray], None, None]:
75
76
  """An iterator that yields the two faces of the hex cell plus a cell periodicity vector
76
77
 
77
78
  Yields:
@@ -84,15 +85,17 @@ class PeriodicCell:
84
85
  """
85
86
  bcs = []
86
87
  for f1, f2, a in self.cell_data():
88
+ f1_new = f1
89
+ f2_new = f2
87
90
  if self.excluded_faces is not None:
88
- f1 = f1 - self.excluded_faces
89
- f2 = f2 - self.excluded_faces
90
- bcs.append(Periodic(f1, f2, a))
91
+ f1_new = f1 - self.excluded_faces # type: ignore
92
+ f2_new = f2 - self.excluded_faces # type: ignore
93
+ bcs.append(Periodic(f1_new, f2_new, tuple(a)))
91
94
  self._bcs = bcs
92
95
  return bcs
93
96
 
94
97
  @property
95
- def bcs(self) -> list[Periodic]:
98
+ def bcs(self) -> list[BoundaryCondition]:
96
99
  """Returns a list of Periodic boundary conditions for the given PeriodicCell
97
100
 
98
101
  Args:
@@ -103,7 +106,7 @@ class PeriodicCell:
103
106
  """
104
107
  if not self._bcs:
105
108
  raise ValueError('Periodic Boundary conditions not generated')
106
- return self._bcs + self._ports
109
+ return self._bcs + self._ports # type: ignore
107
110
 
108
111
  def set_scanangle(self, theta: float, phi: float, degree: bool = True) -> None:
109
112
  """Sets the scanangle for the periodic condition. (0,0) is defined along the Z-axis
@@ -126,8 +129,8 @@ class PeriodicCell:
126
129
  bc.uy = uy
127
130
  bc.uz = uz
128
131
  for port in self._ports:
129
- port.scan_theta = theta
130
- port.scan_phi = phi
132
+ port.scan_theta = theta # type: ignore
133
+ port.scan_phi = phi # type: ignore
131
134
 
132
135
  def port_face(self, z: float):
133
136
  raise NotImplementedError('')
@@ -200,9 +203,9 @@ class RectCell(PeriodicCell):
200
203
  class HexCell(PeriodicCell):
201
204
 
202
205
  def __init__(self,
203
- p1: tuple[float, float, float],
204
- p2: tuple[float, float, float],
205
- p3: tuple[float, float, float]):
206
+ point1: tuple[float, float, float],
207
+ point2: tuple[float, float, float],
208
+ point3: tuple[float, float, float]):
206
209
  """Generates a Hexagonal periodic tiling by providing 4 coordinates. The layout of the tiling is as following
207
210
  Assuming a hexagon with a single vertext at the top and bottom and two vertices on the left and right faces ⬢
208
211
 
@@ -211,7 +214,7 @@ class HexCell(PeriodicCell):
211
214
  p2 (tuple[float, float, float]): left face bottom vertex
212
215
  p3 (tuple[float, float, float]): bottom vertex
213
216
  """
214
- p1, p2, p3 = [np.array(p) for p in [p1, p2, p3]]
217
+ p1, p2, p3 = np.array(point1), np.array(point2), np.array(point3)
215
218
  p4 = -p1
216
219
  self.p1: np.ndarray = p1
217
220
  self.p2: np.ndarray = p2
@@ -240,7 +243,7 @@ class HexCell(PeriodicCell):
240
243
  poly = XYPolygon(xs, ys).geo(GCS.displace(0,0,zs[0]))
241
244
  return poly
242
245
 
243
- def cell_data(self) -> Generator[FaceSelection, FaceSelection, np.ndarray]:
246
+ def cell_data(self) -> Generator[tuple[Selection, Selection, np.ndarray], None, None]:
244
247
  nrm = np.linalg.norm
245
248
 
246
249
  o = self.o1[:-1]
@@ -250,7 +253,7 @@ class HexCell(PeriodicCell):
250
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))
251
254
  vec = - (self.p1 + self.p2)
252
255
 
253
- for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
256
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
254
257
  yield f1, f2, vec
255
258
 
256
259
  o = self.o2[:-1]
@@ -259,7 +262,7 @@ class HexCell(PeriodicCell):
259
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))
260
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))
261
264
  vec = - (self.p2 + self.p3)
262
- for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
265
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
263
266
  yield f1, f2, vec
264
267
 
265
268
  o = self.o3[:-1]
@@ -268,18 +271,18 @@ class HexCell(PeriodicCell):
268
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))
269
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))
270
273
  vec = - (self.p3 - self.p1)
271
- for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)):
274
+ for f1, f2 in zip(*_pair_selection(f1s, f2s, vec)): # type: ignore
272
275
  yield f1, f2, vec
273
276
 
274
277
  def volume(self,
275
278
  z1: float,
276
279
  z2: float) -> GeoPrism:
277
280
  xs, ys, zs = zip(self.p1, self.p2, self.p3)
278
- xs = np.array(xs)
279
- ys = np.array(ys)
280
- xs = np.concatenate([xs, -xs])
281
- ys = np.concatenate([ys, -ys])
282
- poly = XYPolygon(xs, ys)
281
+ xs2 = np.array(xs) # type: ignore
282
+ ys2 = np.array(ys) # type: ignore
283
+ xs3 = np.concatenate([xs2, -xs2]) # type: ignore
284
+ ys3 = np.concatenate([ys2, -ys2]) # type: ignore
285
+ poly = XYPolygon(xs3, ys3)
283
286
  length = z2-z1
284
287
  return poly.extrude(length, cs=GCS.displace(0,0,z1))
285
288
 
@@ -49,14 +49,17 @@ Modification history
49
49
 
50
50
  """
51
51
 
52
+ # ty: ignore
53
+
52
54
  import numpy as np
53
55
  from typing import Literal
54
56
  from loguru import logger
57
+
55
58
  def cc(z):
56
59
  return z.conjugate()
57
60
 
58
61
  def model(s, poles, residues, d, h):
59
- return np.sum(r/(s-p) for p, r in zip(poles, residues)) + d + s*h
62
+ return sum([r/(s-p) for p, r in zip(poles, residues)]) + d + s*h
60
63
 
61
64
  def vectfit_step(f: np.ndarray, s: np.ndarray, poles: np.ndarray) -> np.ndarray:
62
65
  """
@@ -105,10 +108,6 @@ def vectfit_step(f: np.ndarray, s: np.ndarray, poles: np.ndarray) -> np.ndarray:
105
108
  b = np.concatenate((b.real, b.imag))
106
109
  x, residuals, rnk, s = np.linalg.lstsq(A, b, rcond=-1)
107
110
 
108
- residues = x[:N]
109
- d = x[N]
110
- h = x[N+1]
111
-
112
111
  # We only want the "tilde" part in (A.4)
113
112
  x = x[-N:]
114
113
 
@@ -166,12 +165,12 @@ def calculate_residues(f: np.ndarray, s: np.ndarray, poles: np.ndarray, rcond=-1
166
165
  b = np.concatenate((b.real, b.imag))
167
166
  cA = np.linalg.cond(A)
168
167
  if cA > 1e13:
169
- print('Warning!: Ill Conditioned Matrix. Consider scaling the problem down')
170
- print('Cond(A)', cA)
168
+ logger.warning('Warning!: Ill Conditioned Matrix. Consider scaling the problem down')
169
+ logger.warning('Cond(A)', cA)
171
170
  x, residuals, rnk, s = np.linalg.lstsq(A, b, rcond=rcond)
172
171
 
173
172
  # Recover complex values
174
- x = np.complex128(x)
173
+ x = x.astype(np.complex128)
175
174
  for i, ci in enumerate(cindex):
176
175
  if ci == 1:
177
176
  r1, r2 = x[i:i+2]
@@ -190,11 +189,11 @@ def vectfit_auto(f: np.ndarray,
190
189
  inc_real: bool = False,
191
190
  loss_ratio: float = 1e-2,
192
191
  rcond: int = -1,
193
- track_poles: bool = False) -> tuple[np.ndarray, np.ndarray, float, float]:
192
+ track_poles: bool = False) -> tuple[np.ndarray, np.ndarray, float, float, np.ndarray]:
194
193
  w = s.imag
195
194
  pole_locs = np.linspace(w[0], w[-1], n_poles+2)[1:-1]
196
195
  lr = loss_ratio
197
- 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])
198
197
 
199
198
  if inc_real:
200
199
  poles = np.concatenate((poles, [1]))
@@ -208,7 +207,7 @@ def vectfit_auto(f: np.ndarray,
208
207
 
209
208
  if track_poles:
210
209
  return poles, residues, d, h, np.array(poles_list)
211
- return poles, residues, d, h
210
+ return poles, residues, d, h, np.array([])
212
211
 
213
212
  def vectfit_auto_rescale(f: np.ndarray, s: np.ndarray,
214
213
  n_poles: int = 10,
@@ -216,10 +215,10 @@ def vectfit_auto_rescale(f: np.ndarray, s: np.ndarray,
216
215
  inc_real: bool = False,
217
216
  loss_ratio: float = 1e-2,
218
217
  rcond: int = -1,
219
- track_poles: bool = False) -> tuple[np.ndarray, np.ndarray, float, float]:
218
+ track_poles: bool = False) -> tuple[np.ndarray, np.ndarray, float, float, np.ndarray]:
220
219
  s_scale = abs(s[-1])
221
220
  f_scale = abs(f[-1])
222
- poles_s, residues_s, d_s, h_s = vectfit_auto(f / f_scale,
221
+ poles_s, residues_s, d_s, h_s, tracked_poles = vectfit_auto(f / f_scale,
223
222
  s / s_scale,
224
223
  n_poles=n_poles,
225
224
  n_iter = n_iter,
@@ -231,7 +230,7 @@ def vectfit_auto_rescale(f: np.ndarray, s: np.ndarray,
231
230
  residues = residues_s * f_scale * s_scale
232
231
  d = d_s * f_scale
233
232
  h = h_s * f_scale / s_scale
234
- return poles, residues, d, h
233
+ return poles, residues, d, h, tracked_poles
235
234
 
236
235
 
237
236
  class SparamModel:
@@ -251,7 +250,7 @@ class SparamModel:
251
250
  fdense = np.linspace(min(self.f), max(self.f), max(201, 10*self.f.shape[0]))
252
251
  success = False
253
252
  for nps in range(1,maxpoles):
254
- poles, residues, d, h = vectfit_auto_rescale(Sparam, s, n_poles=nps, inc_real=inc_real)
253
+ poles, residues, d, h, _ = vectfit_auto_rescale(Sparam, s, n_poles=nps, inc_real=inc_real)
255
254
  self.poles: np.ndarray = poles
256
255
  self.residues: np.ndarray = residues
257
256
  self.d = d
@@ -268,7 +267,7 @@ class SparamModel:
268
267
  logger.warning('Could not model S-parameters. Try a denser grid')
269
268
 
270
269
  else:
271
- poles, residues, d, h = vectfit_auto_rescale(Sparam, s, n_poles=n_poles, inc_real=inc_real)
270
+ poles, residues, d, h, _ = vectfit_auto_rescale(Sparam, s, n_poles=n_poles, inc_real=inc_real)
272
271
  self.poles: np.ndarray = poles
273
272
  self.residues: np.ndarray = residues
274
273
  self.d = d