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.
- emerge/_emerge/bc.py +14 -20
- emerge/_emerge/const.py +5 -0
- emerge/_emerge/cs.py +2 -2
- emerge/_emerge/elements/femdata.py +14 -14
- emerge/_emerge/elements/index_interp.py +1 -1
- emerge/_emerge/elements/ned2_interp.py +1 -1
- emerge/_emerge/elements/nedelec2.py +4 -4
- emerge/_emerge/elements/nedleg2.py +10 -10
- emerge/_emerge/geo/horn.py +1 -1
- emerge/_emerge/geo/modeler.py +18 -19
- emerge/_emerge/geo/operations.py +13 -10
- emerge/_emerge/geo/pcb.py +180 -82
- emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
- emerge/_emerge/geo/pcb_tools/macro.py +14 -13
- emerge/_emerge/geo/pmlbox.py +1 -1
- emerge/_emerge/geometry.py +47 -33
- emerge/_emerge/logsettings.py +15 -16
- emerge/_emerge/material.py +15 -11
- emerge/_emerge/mesh3d.py +81 -59
- emerge/_emerge/mesher.py +26 -21
- emerge/_emerge/mth/integrals.py +1 -1
- emerge/_emerge/mth/pairing.py +2 -2
- emerge/_emerge/periodic.py +34 -31
- emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
- emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
- emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
- emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
- emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
- emerge/_emerge/physics/microwave/microwave_data.py +36 -38
- emerge/_emerge/physics/microwave/sc.py +26 -26
- emerge/_emerge/physics/microwave/simjob.py +20 -15
- emerge/_emerge/physics/microwave/sparam.py +12 -12
- emerge/_emerge/physics/microwave/touchstone.py +1 -1
- emerge/_emerge/plot/display.py +12 -6
- emerge/_emerge/plot/pyvista/display.py +44 -39
- emerge/_emerge/plot/pyvista/display_settings.py +1 -1
- emerge/_emerge/plot/simple_plots.py +15 -15
- emerge/_emerge/selection.py +35 -39
- emerge/_emerge/simmodel.py +41 -47
- emerge/_emerge/simulation_data.py +24 -15
- emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
- emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
- emerge/_emerge/solver.py +314 -136
- emerge/cli.py +1 -1
- emerge/lib.py +245 -248
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
- emerge-0.5.3.dist-info/RECORD +83 -0
- emerge/_emerge/plot/grapher.py +0 -93
- emerge-0.5.1.dist-info/RECORD +0 -82
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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:
|
|
164
|
-
face2:
|
|
165
|
-
lattice:
|
|
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:
|
|
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,
|
|
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 =
|
|
264
|
+
dimtags = boundary.dimtags
|
|
260
265
|
|
|
261
266
|
if max_size is None:
|
|
262
267
|
self._check_ready()
|
emerge/_emerge/mth/integrals.py
CHANGED
|
@@ -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
|
|
emerge/_emerge/mth/pairing.py
CHANGED
|
@@ -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:
|
|
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).
|
emerge/_emerge/periodic.py
CHANGED
|
@@ -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:
|
|
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(
|
|
36
|
-
f2s.append(
|
|
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
|
-
|
|
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:
|
|
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[
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
bcs.append(Periodic(
|
|
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[
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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 =
|
|
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[
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
poly = XYPolygon(
|
|
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
|
|
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
|
-
|
|
170
|
-
|
|
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
|
|
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
|
-
|
|
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
|