emerge 0.5.1__py3-none-any.whl → 0.5.2__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 +11 -8
- 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 +9 -9
- 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 +70 -69
- emerge/_emerge/geo/pcb_tools/macro.py +14 -13
- emerge/_emerge/geo/pmlbox.py +1 -1
- emerge/_emerge/geometry.py +46 -32
- emerge/_emerge/logsettings.py +3 -3
- emerge/_emerge/material.py +11 -11
- emerge/_emerge/mesh3d.py +81 -59
- emerge/_emerge/mesher.py +26 -21
- emerge/_emerge/mth/pairing.py +2 -2
- emerge/_emerge/periodic.py +34 -31
- emerge/_emerge/physics/microwave/adaptive_freq.py +14 -11
- emerge/_emerge/physics/microwave/assembly/assembler.py +61 -57
- 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 +40 -20
- emerge/_emerge/physics/microwave/microwave_bc.py +114 -95
- emerge/_emerge/physics/microwave/microwave_data.py +33 -33
- emerge/_emerge/physics/microwave/simjob.py +12 -12
- 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 +29 -39
- emerge/_emerge/simulation_data.py +19 -14
- emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
- emerge/_emerge/solver.py +52 -52
- emerge/lib.py +243 -243
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/METADATA +1 -1
- emerge-0.5.2.dist-info/RECORD +81 -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.2.dist-info}/WHEEL +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/selection.py
CHANGED
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
|
-
import gmsh
|
|
19
|
+
import gmsh # type: ignore
|
|
20
20
|
import numpy as np
|
|
21
|
-
from scipy.spatial import ConvexHull
|
|
21
|
+
from scipy.spatial import ConvexHull # type: ignore
|
|
22
22
|
from .cs import Axis, CoordinateSystem, _parse_vector, Plane
|
|
23
|
-
from typing import Callable, TypeVar
|
|
23
|
+
from typing import Callable, TypeVar, Iterable, Any
|
|
24
24
|
|
|
25
|
-
def align_rectangle_frame(pts3d: np.ndarray, normal: np.ndarray) -> dict[str,
|
|
25
|
+
def align_rectangle_frame(pts3d: np.ndarray, normal: np.ndarray) -> dict[str, Any]:
|
|
26
26
|
"""Tries to find a rectangle as convex-hull of a set of points with a given normal vector.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
@@ -66,9 +66,9 @@ def align_rectangle_frame(pts3d: np.ndarray, normal: np.ndarray) -> dict[str, np
|
|
|
66
66
|
xmax, ymax = rot.max(axis=0)
|
|
67
67
|
area = (xmax-xmin)*(ymax-ymin)
|
|
68
68
|
if area < best[1]:
|
|
69
|
-
best = (theta, area, (xmin,xmax,ymin,ymax), R)
|
|
69
|
+
best = (theta, area, (xmin,xmax,ymin,ymax), R) # type: ignore
|
|
70
70
|
|
|
71
|
-
theta, _, (xmin,xmax,ymin,ymax), R = best
|
|
71
|
+
theta, _, (xmin,xmax,ymin,ymax), R = best # type: ignore
|
|
72
72
|
|
|
73
73
|
# 6. rectangle axes in 3D
|
|
74
74
|
u = np.cos(-theta)*e_x + np.sin(-theta)*e_y
|
|
@@ -172,9 +172,9 @@ class Selection:
|
|
|
172
172
|
|
|
173
173
|
"""
|
|
174
174
|
dim: int = -1
|
|
175
|
-
def __init__(self, tags: list[int] = None):
|
|
175
|
+
def __init__(self, tags: list[int] | set[int] | tuple[int] | None = None):
|
|
176
176
|
|
|
177
|
-
self._tags: set[int] =
|
|
177
|
+
self._tags: set[int] = set()
|
|
178
178
|
|
|
179
179
|
if tags is not None:
|
|
180
180
|
if not isinstance(tags, (list,set,tuple)):
|
|
@@ -182,7 +182,7 @@ class Selection:
|
|
|
182
182
|
self._tags = set(tags)
|
|
183
183
|
|
|
184
184
|
@staticmethod
|
|
185
|
-
def from_dim_tags(dim: int, tags: list[int]
|
|
185
|
+
def from_dim_tags(dim: int, tags: list[int] | set[int]) -> Selection:
|
|
186
186
|
if dim==0:
|
|
187
187
|
return PointSelection(tags)
|
|
188
188
|
elif dim==1:
|
|
@@ -198,7 +198,7 @@ class Selection:
|
|
|
198
198
|
return list(self._tags)
|
|
199
199
|
|
|
200
200
|
@property
|
|
201
|
-
def color_rgb(self) -> tuple[
|
|
201
|
+
def color_rgb(self) -> tuple[float, float, float]:
|
|
202
202
|
return (0.5,0.5,1.0)
|
|
203
203
|
|
|
204
204
|
@property
|
|
@@ -225,14 +225,14 @@ class Selection:
|
|
|
225
225
|
return [gmsh.model.occ.getCenterOfMass(self.dim, tag) for tag in self.tags]
|
|
226
226
|
|
|
227
227
|
@property
|
|
228
|
-
def points(self) ->
|
|
228
|
+
def points(self) -> list[np.ndarray]:
|
|
229
229
|
'''A list of 3D coordinates of all nodes comprising the selection.'''
|
|
230
230
|
points = gmsh.model.get_boundary(self.dimtags, recursive=True)
|
|
231
231
|
coordinates = [gmsh.model.getValue(*p, []) for p in points]
|
|
232
232
|
return coordinates
|
|
233
233
|
|
|
234
234
|
@property
|
|
235
|
-
def bounding_box(self) -> tuple[
|
|
235
|
+
def bounding_box(self) -> tuple[Iterable, Iterable]:
|
|
236
236
|
if len(self.tags)==1:
|
|
237
237
|
return gmsh.model.occ.getBoundingBox(self.dim, self.tags[0])
|
|
238
238
|
else:
|
|
@@ -248,7 +248,7 @@ class Selection:
|
|
|
248
248
|
maxz = max(maxz, z1)
|
|
249
249
|
return (minx, miny, minz), (maxx, maxy, maxz)
|
|
250
250
|
|
|
251
|
-
def exclude(self, xyz_excl_function: Callable = lambda x,y,z: True, plane: Plane = None, axis: Axis = None) -> Selection:
|
|
251
|
+
def exclude(self, xyz_excl_function: Callable = lambda x,y,z: True, plane: Plane | None = None, axis: Axis | None = None) -> Selection:
|
|
252
252
|
"""Exclude points by evaluating a function(x,y,z)-> bool
|
|
253
253
|
|
|
254
254
|
This modifies the selection such that the selection does not contain elements
|
|
@@ -266,10 +266,10 @@ class Selection:
|
|
|
266
266
|
norm = axis.np
|
|
267
267
|
include2 = [abs(gmsh.model.getNormal(tag, np.array([0,0]))@norm)<0.9 for tag in self.tags]
|
|
268
268
|
include = [i1 for i1, i2 in zip(include, include2) if i1 and i2]
|
|
269
|
-
self._tags = [t for incl, t in zip(include, self._tags) if incl]
|
|
269
|
+
self._tags = set([t for incl, t in zip(include, self._tags) if incl])
|
|
270
270
|
return self
|
|
271
271
|
|
|
272
|
-
def isolate(self, xyz_excl_function: Callable = lambda x,y,z: True, plane: Plane = None, axis: Axis = None) -> Selection:
|
|
272
|
+
def isolate(self, xyz_excl_function: Callable = lambda x,y,z: True, plane: Plane | None = None, axis: Axis | None = None) -> Selection:
|
|
273
273
|
"""Include points by evaluating a function(x,y,z)-> bool
|
|
274
274
|
|
|
275
275
|
This modifies the selection such that the selection does not contain elements
|
|
@@ -287,7 +287,7 @@ class Selection:
|
|
|
287
287
|
norm = axis.np
|
|
288
288
|
include2 = [(gmsh.model.getNormal(tag, np.array([0,0]))@norm)>0.99 for tag in self.tags]
|
|
289
289
|
include1 = [i1 for i1, i2 in zip(include1, include2) if i1 and i2]
|
|
290
|
-
self._tags = [t for incl, t in zip(include1, self._tags) if incl]
|
|
290
|
+
self._tags = set([t for incl, t in zip(include1, self._tags) if incl])
|
|
291
291
|
return self
|
|
292
292
|
|
|
293
293
|
def __operable__(self, other: Selection) -> None:
|
|
@@ -295,30 +295,28 @@ class Selection:
|
|
|
295
295
|
raise ValueError(f'Selection dimensions must be equal. Trying to operate on dim {self.dim} and {other.dim}')
|
|
296
296
|
pass
|
|
297
297
|
|
|
298
|
-
def __add__(self, other:
|
|
298
|
+
def __add__(self, other: Selection) -> Selection:
|
|
299
299
|
self.__operable__(other)
|
|
300
|
-
return Selection.from_dim_tags(self.dim, self._tags
|
|
300
|
+
return Selection.from_dim_tags(self.dim, self._tags.union(other._tags))
|
|
301
301
|
|
|
302
|
-
def __and__(self, other:
|
|
302
|
+
def __and__(self, other: Selection) -> Selection:
|
|
303
303
|
self.__operable__(other)
|
|
304
304
|
return Selection.from_dim_tags(self.dim, self._tags.intersection(other._tags))
|
|
305
305
|
|
|
306
|
-
def __or__(self, other:
|
|
306
|
+
def __or__(self, other: Selection) -> Selection:
|
|
307
307
|
self.__operable__(other)
|
|
308
308
|
return Selection.from_dim_tags(self.dim, self._tags.union(other._tags))
|
|
309
309
|
|
|
310
|
-
def __sub__(self, other:
|
|
310
|
+
def __sub__(self, other: Selection) -> Selection:
|
|
311
311
|
self.__operable__(other)
|
|
312
312
|
return Selection.from_dim_tags(self.dim, self._tags.difference(other.tags))
|
|
313
|
-
|
|
314
|
-
|
|
315
313
|
|
|
316
314
|
class PointSelection(Selection):
|
|
317
315
|
"""A Class representing a selection of points.
|
|
318
316
|
|
|
319
317
|
"""
|
|
320
318
|
dim: int = 0
|
|
321
|
-
def __init__(self, tags: list[int] = None):
|
|
319
|
+
def __init__(self, tags: list[int] | set [int] | None = None):
|
|
322
320
|
super().__init__(tags)
|
|
323
321
|
|
|
324
322
|
class EdgeSelection(Selection):
|
|
@@ -326,7 +324,7 @@ class EdgeSelection(Selection):
|
|
|
326
324
|
|
|
327
325
|
"""
|
|
328
326
|
dim: int = 1
|
|
329
|
-
def __init__(self, tags: list[int] = None):
|
|
327
|
+
def __init__(self, tags: list[int] | set [int] | None = None):
|
|
330
328
|
super().__init__(tags)
|
|
331
329
|
|
|
332
330
|
class FaceSelection(Selection):
|
|
@@ -334,7 +332,7 @@ class FaceSelection(Selection):
|
|
|
334
332
|
|
|
335
333
|
"""
|
|
336
334
|
dim: int = 2
|
|
337
|
-
def __init__(self, tags: list[int] = None):
|
|
335
|
+
def __init__(self, tags: list[int] | set [int] | None = None):
|
|
338
336
|
super().__init__(tags)
|
|
339
337
|
|
|
340
338
|
@property
|
|
@@ -355,14 +353,14 @@ class FaceSelection(Selection):
|
|
|
355
353
|
|
|
356
354
|
pts3d = self.points
|
|
357
355
|
normal = self.normal
|
|
358
|
-
data = align_rectangle_frame(pts3d, normal)
|
|
356
|
+
data = align_rectangle_frame(np.array(pts3d), normal)
|
|
359
357
|
plane = data['axes'][:2]
|
|
360
358
|
origin = data['origin']
|
|
361
359
|
|
|
362
360
|
cs = CoordinateSystem(Axis(plane[0]), Axis(plane[1]), Axis(data['axes'][2]), origin)
|
|
363
361
|
|
|
364
|
-
size1 = np.linalg.norm(data['corners'][1] - data['corners'][0])
|
|
365
|
-
size2 = np.linalg.norm(data['corners'][2] - data['corners'][0])
|
|
362
|
+
size1 = float(np.linalg.norm(data['corners'][1] - data['corners'][0]))
|
|
363
|
+
size2 = float(np.linalg.norm(data['corners'][2] - data['corners'][0]))
|
|
366
364
|
|
|
367
365
|
if size1>size2:
|
|
368
366
|
cs.swapxy()
|
|
@@ -382,7 +380,7 @@ class FaceSelection(Selection):
|
|
|
382
380
|
a NxN numpy array of Y coordinates.
|
|
383
381
|
Z: np.ndarray
|
|
384
382
|
a NxN numpy array of Z coordinates'''
|
|
385
|
-
coordset = []
|
|
383
|
+
coordset: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = []
|
|
386
384
|
for tag in self.tags:
|
|
387
385
|
tags, coord, param = gmsh.model.mesh.getNodes(2, tag, includeBoundary=True)
|
|
388
386
|
|
|
@@ -419,17 +417,15 @@ class FaceSelection(Selection):
|
|
|
419
417
|
Y = [c[1] for c in coordset]
|
|
420
418
|
Z = [c[2] for c in coordset]
|
|
421
419
|
if len(X) == 1:
|
|
422
|
-
X
|
|
423
|
-
|
|
424
|
-
Z = Z[0]
|
|
425
|
-
return X, Y, Z
|
|
420
|
+
return X[0], Y[0], Z[0]
|
|
421
|
+
return np.array(X), np.array(Y), np.array(Z)
|
|
426
422
|
|
|
427
423
|
class DomainSelection(Selection):
|
|
428
424
|
"""A Class representing a selection of domains.
|
|
429
425
|
|
|
430
426
|
"""
|
|
431
427
|
dim: int = 3
|
|
432
|
-
def __init__(self, tags: list[int] = None):
|
|
428
|
+
def __init__(self, tags: list[int] | set[int] | None = None):
|
|
433
429
|
super().__init__(tags)
|
|
434
430
|
|
|
435
431
|
SELECT_CLASS: dict[int, type[Selection]] = {
|
|
@@ -501,7 +497,7 @@ class Selector:
|
|
|
501
497
|
x: float,
|
|
502
498
|
y: float,
|
|
503
499
|
z: float,
|
|
504
|
-
vector: tuple[float, float, float] | np.ndarray | Axis) ->
|
|
500
|
+
vector: tuple[float, float, float] | np.ndarray | Axis) -> Selection:
|
|
505
501
|
'''Returns a list of selections that are in the layer defined by the plane normal vector and the point (x,y,z)
|
|
506
502
|
|
|
507
503
|
The layer is specified by two infinite planes normal to the provided vector. The first plane is originated
|
|
@@ -529,7 +525,7 @@ class Selector:
|
|
|
529
525
|
output = []
|
|
530
526
|
for i, c in enumerate(coords):
|
|
531
527
|
c_local = c - np.array([x,y,z])
|
|
532
|
-
if 0 < np.dot(vector, c_local) < L:
|
|
528
|
+
if 0 < np.dot(vector, c_local) < L: # type: ignore
|
|
533
529
|
output.append(dimtags[i][1])
|
|
534
530
|
return SELECT_CLASS[self._current_dim](output)
|
|
535
531
|
|
|
@@ -565,8 +561,8 @@ class Selector:
|
|
|
565
561
|
normals = [gmsh.model.get_normal(t, (0,0)) for d,t, in dimtags]
|
|
566
562
|
tags = []
|
|
567
563
|
for (d,t), o, n in zip(dimtags, coords, normals):
|
|
568
|
-
normdist = np.abs((o-orig)@norm)
|
|
569
|
-
dotnorm = np.abs(n@norm)
|
|
564
|
+
normdist = np.abs((o-orig) @ norm)
|
|
565
|
+
dotnorm = np.abs(n @ norm)
|
|
570
566
|
if normdist < tolerance and dotnorm > 1-tolerance:
|
|
571
567
|
tags.append(t)
|
|
572
568
|
return FaceSelection(tags)
|
emerge/_emerge/simmodel.py
CHANGED
|
@@ -27,12 +27,11 @@ from .plot.pyvista import PVDisplay
|
|
|
27
27
|
from .dataset import SimulationDataset
|
|
28
28
|
from .periodic import PeriodicCell
|
|
29
29
|
from .bc import BoundaryCondition
|
|
30
|
-
from typing import Literal,
|
|
30
|
+
from typing import Literal, Generator, Any
|
|
31
31
|
from loguru import logger
|
|
32
32
|
import numpy as np
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
import joblib
|
|
33
|
+
import gmsh # type: ignore
|
|
34
|
+
import joblib # type: ignore
|
|
36
35
|
import os
|
|
37
36
|
import inspect
|
|
38
37
|
from pathlib import Path
|
|
@@ -94,15 +93,14 @@ class Simulation3D:
|
|
|
94
93
|
|
|
95
94
|
self.mesh: Mesh3D = Mesh3D(self.mesher)
|
|
96
95
|
self.select: Selector = Selector()
|
|
97
|
-
self.display: PVDisplay = None
|
|
98
96
|
self.set_loglevel(loglevel)
|
|
99
97
|
|
|
100
98
|
## STATES
|
|
101
99
|
self.__active: bool = False
|
|
102
100
|
self._defined_geometries: bool = False
|
|
103
|
-
self._cell: PeriodicCell = None
|
|
101
|
+
self._cell: PeriodicCell | None = None
|
|
104
102
|
|
|
105
|
-
self.display = PVDisplay(self.mesh)
|
|
103
|
+
self.display: PVDisplay = PVDisplay(self.mesh)
|
|
106
104
|
|
|
107
105
|
if logfile:
|
|
108
106
|
self.set_logfile()
|
|
@@ -203,7 +201,9 @@ class Simulation3D:
|
|
|
203
201
|
if self.save_file:
|
|
204
202
|
self.save()
|
|
205
203
|
# Finalize GMSH
|
|
206
|
-
gmsh.
|
|
204
|
+
if gmsh.isInitialized():
|
|
205
|
+
gmsh.finalize()
|
|
206
|
+
|
|
207
207
|
logger.debug('GMSH Shut down successful')
|
|
208
208
|
# set the state to active
|
|
209
209
|
self.__active = False
|
|
@@ -214,28 +214,22 @@ class Simulation3D:
|
|
|
214
214
|
|
|
215
215
|
def all_geometries(self) -> list[GeoObject]:
|
|
216
216
|
"""Returns all geometries stored in the simulation file."""
|
|
217
|
-
return [obj for obj in self.sim.default.values() if isinstance(obj, GeoObject)]
|
|
217
|
+
return [obj for obj in self.data.sim.default.values() if isinstance(obj, GeoObject)]
|
|
218
218
|
|
|
219
219
|
def all_bcs(self) -> list[BoundaryCondition]:
|
|
220
220
|
"""Returns all boundary condition objects stored in the simulation file"""
|
|
221
|
-
return [obj for obj in self.sim.default.values() if isinstance(obj, BoundaryCondition)]
|
|
221
|
+
return [obj for obj in self.data.sim.default.values() if isinstance(obj, BoundaryCondition)]
|
|
222
222
|
|
|
223
223
|
def _set_mesh(self, mesh: Mesh3D) -> None:
|
|
224
224
|
"""Set the current model mesh to a given mesh."""
|
|
225
225
|
self.mesh = mesh
|
|
226
226
|
self.mw.mesh = mesh
|
|
227
|
-
self.mesher.mesh = mesh
|
|
228
227
|
self.display._mesh = mesh
|
|
229
228
|
|
|
230
229
|
############################################################
|
|
231
230
|
# PUBLIC FUNCTIONS #
|
|
232
231
|
############################################################
|
|
233
232
|
|
|
234
|
-
@property
|
|
235
|
-
def passed_geometries(self) -> list[GeoObject]:
|
|
236
|
-
""""""
|
|
237
|
-
return self.data.sim['geometries']
|
|
238
|
-
|
|
239
233
|
def save(self) -> None:
|
|
240
234
|
"""Saves the current model in the provided project directory."""
|
|
241
235
|
# Ensure directory exists
|
|
@@ -298,7 +292,7 @@ class Simulation3D:
|
|
|
298
292
|
LOG_CONTROLLER.set_write_file(self.modelpath)
|
|
299
293
|
|
|
300
294
|
def view(self,
|
|
301
|
-
selections: list[Selection] = None,
|
|
295
|
+
selections: list[Selection] | None = None,
|
|
302
296
|
use_gmsh: bool = False,
|
|
303
297
|
volume_opacity: float = 0.1,
|
|
304
298
|
surface_opacity: float = 1,
|
|
@@ -316,22 +310,16 @@ class Simulation3D:
|
|
|
316
310
|
gmsh.model.occ.synchronize()
|
|
317
311
|
gmsh.fltk.run()
|
|
318
312
|
return
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
[self.display.add_object(sel, color='red', opacity=0.7) for sel in selections]
|
|
328
|
-
self.display.show()
|
|
329
|
-
return
|
|
330
|
-
except NotImplementedError as e:
|
|
331
|
-
logger.warning('The provided BaseDisplay class does not support object display. Please make' \
|
|
332
|
-
'sure that this method is properly implemented.')
|
|
313
|
+
for geo in _GEOMANAGER.all_geometries():
|
|
314
|
+
self.display.add_object(geo)
|
|
315
|
+
if selections:
|
|
316
|
+
[self.display.add_object(sel, color='red', opacity=0.7) for sel in selections]
|
|
317
|
+
self.display.show()
|
|
318
|
+
|
|
319
|
+
return None
|
|
320
|
+
|
|
333
321
|
|
|
334
|
-
def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: list[FaceSelection] = None):
|
|
322
|
+
def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: list[FaceSelection] | None = None):
|
|
335
323
|
"""Set the given periodic cell object as the simulations peridicity.
|
|
336
324
|
|
|
337
325
|
Args:
|
|
@@ -341,18 +329,20 @@ class Simulation3D:
|
|
|
341
329
|
self.mw.bc._cell = cell
|
|
342
330
|
self._cell = cell
|
|
343
331
|
|
|
344
|
-
def commit_geometry(self, *geometries: list[GeoObject]) -> None:
|
|
332
|
+
def commit_geometry(self, *geometries: GeoObject | list[GeoObject]) -> None:
|
|
345
333
|
"""Finalizes and locks the current geometry state of the simulation.
|
|
346
334
|
|
|
347
335
|
The geometries may be provided (legacy behavior) but are automatically managed underwater.
|
|
348
336
|
|
|
349
337
|
"""
|
|
338
|
+
geometries_parsed: Any = None
|
|
350
339
|
if not geometries:
|
|
351
|
-
|
|
340
|
+
geometries_parsed = _GEOMANAGER.all_geometries()
|
|
352
341
|
else:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
self.
|
|
342
|
+
geometries_parsed = unpack_lists(geometries + tuple([item for item in self.data.sim.default.values() if isinstance(item, GeoObject)]))
|
|
343
|
+
|
|
344
|
+
self.data.sim['geometries'] = geometries_parsed
|
|
345
|
+
self.mesher.submit_objects(geometries_parsed)
|
|
356
346
|
self._defined_geometries = True
|
|
357
347
|
self.display._facetags = [dt[1] for dt in gmsh.model.get_entities(2)]
|
|
358
348
|
|
|
@@ -438,9 +428,9 @@ class Simulation3D:
|
|
|
438
428
|
|
|
439
429
|
logger.info(f'Iterating: {params}')
|
|
440
430
|
if len(dims_flat)==1:
|
|
441
|
-
yield dims_flat[0][i_iter]
|
|
431
|
+
yield (dims_flat[0][i_iter],)
|
|
442
432
|
else:
|
|
443
|
-
yield (dim[i_iter] for dim in dims_flat)
|
|
433
|
+
yield (dim[i_iter] for dim in dims_flat) # type: ignore
|
|
444
434
|
self.mw.cache_matrices = True
|
|
445
435
|
|
|
446
436
|
############################################################
|
|
@@ -84,7 +84,7 @@ def generate_ndim(
|
|
|
84
84
|
outer_data: dict[str, list[float]],
|
|
85
85
|
inner_data: list[float],
|
|
86
86
|
outer_labels: tuple[str, ...]
|
|
87
|
-
) -> np.ndarray:
|
|
87
|
+
) -> tuple[np.ndarray,...]:
|
|
88
88
|
"""
|
|
89
89
|
Generates an N-dimensional grid of values from flattened data, and returns each axis array plus the grid.
|
|
90
90
|
|
|
@@ -126,7 +126,7 @@ def generate_ndim(
|
|
|
126
126
|
grid[tuple(idxs)] = values
|
|
127
127
|
|
|
128
128
|
# Return each axis array followed by the grid
|
|
129
|
-
return (
|
|
129
|
+
return tuple(axes) + (grid,)
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
class DataEntry:
|
|
@@ -142,7 +142,7 @@ class DataEntry:
|
|
|
142
142
|
|
|
143
143
|
def values(self) -> list[Any]:
|
|
144
144
|
""" Return all values stored in the DataEntry"""
|
|
145
|
-
return self.data.values()
|
|
145
|
+
return list(self.data.values())
|
|
146
146
|
|
|
147
147
|
def keys(self) -> list[str]:
|
|
148
148
|
""" Return all names of data stored in the DataEntry"""
|
|
@@ -150,12 +150,15 @@ class DataEntry:
|
|
|
150
150
|
|
|
151
151
|
def items(self) -> list[tuple[str, Any]]:
|
|
152
152
|
""" Returns a list of all key: value pairs of the DataEntry."""
|
|
153
|
+
return list(self.data.items())
|
|
153
154
|
|
|
154
|
-
def __eq__(self, other:
|
|
155
|
+
def __eq__(self, other: Any) -> bool:
|
|
156
|
+
if not isinstance(other, dict):
|
|
157
|
+
return False
|
|
155
158
|
allkeys = set(list(self.vars.keys()) + list(other.keys()))
|
|
156
159
|
return all(self.vars[key]==other[key] for key in allkeys)
|
|
157
160
|
|
|
158
|
-
def _dist(self, other: dict[str, float]) ->
|
|
161
|
+
def _dist(self, other: dict[str, float]) -> float:
|
|
159
162
|
return sum([(abs(self.vars.get(key,1e20)-other[key])/other[key]) for key in other.keys()])
|
|
160
163
|
|
|
161
164
|
def __getitem__(self, key) -> Any:
|
|
@@ -218,18 +221,18 @@ class DataContainer:
|
|
|
218
221
|
|
|
219
222
|
|
|
220
223
|
class BaseDataset(Generic[T,M]):
|
|
221
|
-
def __init__(self, datatype: T, matrixtype: M, scalar: bool):
|
|
224
|
+
def __init__(self, datatype: type[T], matrixtype: type[M], scalar: bool):
|
|
222
225
|
self._datatype: type[T] = datatype
|
|
223
226
|
self._matrixtype: type[M] = matrixtype
|
|
224
227
|
self._variables: list[dict[str, float]] = []
|
|
225
228
|
self._data_entries: list[T] = []
|
|
226
229
|
self._scalar: bool = scalar
|
|
227
230
|
|
|
228
|
-
self._gritted: bool = None
|
|
229
|
-
self._axes: dict[str, np.ndarray] = None
|
|
230
|
-
self._ax_ids: dict[str, int] = None
|
|
231
|
-
self._ids: np.ndarray = None
|
|
232
|
-
self._gridobj: M = None
|
|
231
|
+
self._gritted: bool | None = None
|
|
232
|
+
self._axes: dict[str, np.ndarray]| None = None
|
|
233
|
+
self._ax_ids: dict[str, int]| None = None
|
|
234
|
+
self._ids: np.ndarray| None = None
|
|
235
|
+
self._gridobj: M | None = None
|
|
233
236
|
|
|
234
237
|
self._data: dict[str, Any] = dict()
|
|
235
238
|
|
|
@@ -238,11 +241,11 @@ class BaseDataset(Generic[T,M]):
|
|
|
238
241
|
|
|
239
242
|
@property
|
|
240
243
|
def _fields(self) -> list[str]:
|
|
241
|
-
return self._datatype._fields
|
|
244
|
+
return self._datatype._fields # type: ignore
|
|
242
245
|
|
|
243
246
|
@property
|
|
244
247
|
def _copy(self) -> list[str]:
|
|
245
|
-
return self._datatype._copy
|
|
248
|
+
return self._datatype._copy # type: ignore
|
|
246
249
|
|
|
247
250
|
def store(self, key: str, value: Any) -> None:
|
|
248
251
|
"""Stores a variable with some value in the provided key.
|
|
@@ -335,7 +338,7 @@ class BaseDataset(Generic[T,M]):
|
|
|
335
338
|
self._data_entries.append(new_entry)
|
|
336
339
|
return new_entry
|
|
337
340
|
|
|
338
|
-
def _grid_axes(self) ->
|
|
341
|
+
def _grid_axes(self) -> bool:
|
|
339
342
|
"""This method attepmts to create a gritted version of the scalar dataset
|
|
340
343
|
|
|
341
344
|
Returns:
|
|
@@ -405,7 +408,9 @@ class BaseDataset(Generic[T,M]):
|
|
|
405
408
|
"""
|
|
406
409
|
if self._gritted is None:
|
|
407
410
|
self._grid_axes()
|
|
411
|
+
|
|
408
412
|
if self._gritted is False:
|
|
409
413
|
logger.error('The dataset cannot be cast to a structured grid.')
|
|
410
414
|
raise ValueError('Data not in regular grid')
|
|
415
|
+
|
|
411
416
|
return self._gridobj
|
|
@@ -24,7 +24,7 @@ import site
|
|
|
24
24
|
from ctypes.util import find_library
|
|
25
25
|
from enum import Enum
|
|
26
26
|
import numpy as np
|
|
27
|
-
from scipy import sparse
|
|
27
|
+
from scipy import sparse # type: ignore
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Iterable, Iterator
|
|
30
30
|
import pickle
|
|
@@ -60,7 +60,8 @@ PARDISO_ERROR_CODES = {
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
#: Environment variable that overrides automatic searching
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
ENV_VAR = "EMERGE_PARDISO_PATH"
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
def _candidate_dirs() -> Iterable[Path]:
|
|
@@ -71,7 +72,7 @@ def _candidate_dirs() -> Iterable[Path]:
|
|
|
71
72
|
for p in ( # likely “local” env first
|
|
72
73
|
Path(sys.prefix),
|
|
73
74
|
Path(getattr(sys, "base_prefix", sys.prefix)),
|
|
74
|
-
Path(site.USER_BASE),
|
|
75
|
+
Path(str(site.USER_BASE)),
|
|
75
76
|
*(Path(x) for x in os.getenv("LD_LIBRARY_PATH", "").split(":") if x),
|
|
76
77
|
):
|
|
77
78
|
if p not in seen:
|
|
@@ -93,7 +94,7 @@ def _search_mkl() -> Iterator[Path]:
|
|
|
93
94
|
if regex.match(path.name):
|
|
94
95
|
yield path
|
|
95
96
|
|
|
96
|
-
def cache_path_result(tag: str, compute_fn, force: bool = False):
|
|
97
|
+
def cache_path_result(tag: str, compute_fn, force: bool = False) -> str:
|
|
97
98
|
"""
|
|
98
99
|
Retrieve a cached Path object or compute it and store it.
|
|
99
100
|
|
|
@@ -113,13 +114,13 @@ def cache_path_result(tag: str, compute_fn, force: bool = False):
|
|
|
113
114
|
if not force and cache_file.exists():
|
|
114
115
|
with open(cache_file, "rb") as f:
|
|
115
116
|
filename = pickle.load(f)
|
|
116
|
-
|
|
117
|
-
return filename
|
|
117
|
+
logger.debug(f"Using cached MKL file: {filename}")
|
|
118
|
+
return str(filename)
|
|
118
119
|
|
|
119
120
|
result = compute_fn()
|
|
120
121
|
with open(cache_file, "wb") as f:
|
|
121
122
|
pickle.dump(result, f)
|
|
122
|
-
return result
|
|
123
|
+
return str(result)
|
|
123
124
|
|
|
124
125
|
def search_mkl() -> str:
|
|
125
126
|
"""Searches for the file path of the PARDISO MKL executable
|
|
@@ -130,11 +131,11 @@ def search_mkl() -> str:
|
|
|
130
131
|
logger.debug('Searching for MKL executable...')
|
|
131
132
|
for candidate in _search_mkl():
|
|
132
133
|
try:
|
|
133
|
-
ctypes.CDLL(candidate)
|
|
134
|
+
ctypes.CDLL(str(candidate))
|
|
134
135
|
except OSError:
|
|
135
136
|
continue # try the next one
|
|
136
137
|
logger.debug(f'Executable found: {candidate}')
|
|
137
|
-
return candidate
|
|
138
|
+
return str(candidate)
|
|
138
139
|
|
|
139
140
|
def load_mkl() -> ctypes.CDLL:
|
|
140
141
|
"""Locate and load **mkl_rt**; raise ImportError on failure."""
|
|
@@ -200,6 +201,9 @@ CFLOAT32_P = ctypes.POINTER(CFLOAT32)
|
|
|
200
201
|
CFLOAT64_P = ctypes.POINTER(CFLOAT64)
|
|
201
202
|
VOID_SIZE = ctypes.sizeof(ctypes.c_void_p)
|
|
202
203
|
|
|
204
|
+
PT_A: type = CINT32
|
|
205
|
+
PT_B: type = np.int32
|
|
206
|
+
|
|
203
207
|
if VOID_SIZE == 8:
|
|
204
208
|
PT_A = CINT64
|
|
205
209
|
PT_B = np.int64
|
|
@@ -210,7 +214,7 @@ elif VOID_SIZE == 4:
|
|
|
210
214
|
def c_int(value: int):
|
|
211
215
|
return ctypes.byref(ctypes.c_int32(value))
|
|
212
216
|
|
|
213
|
-
PARDISO_ARG_TYPES = (ctypes.POINTER(PT_A),CINT32_P,CINT32_P,
|
|
217
|
+
PARDISO_ARG_TYPES: tuple = (ctypes.POINTER(PT_A),CINT32_P,CINT32_P,
|
|
214
218
|
CINT32_P,CINT32_P,CINT32_P,CNONE_P,CINT32_P,CINT32_P,
|
|
215
219
|
CINT32_P,CINT32_P,CINT32_P,CINT32_P,CNONE_P,CNONE_P,CINT32_P,
|
|
216
220
|
)
|
|
@@ -314,7 +318,7 @@ class PardisoInterface:
|
|
|
314
318
|
self.MATRIX_TYPE = PARDISOMType.REAL_SYM_STRUCT
|
|
315
319
|
self.complex = False
|
|
316
320
|
|
|
317
|
-
def _prepare_B(self, b: np.ndarray) -> np.ndarray:
|
|
321
|
+
def _prepare_B(self, b: np.ndarray | sparse.sparray) -> np.ndarray:
|
|
318
322
|
"""Fixes the forcing-vector for the solution process
|
|
319
323
|
|
|
320
324
|
Args:
|
|
@@ -324,7 +328,8 @@ class PardisoInterface:
|
|
|
324
328
|
np.ndarray: The prepared forcing-vector
|
|
325
329
|
"""
|
|
326
330
|
if sparse.issparse(b):
|
|
327
|
-
b = b.todense()
|
|
331
|
+
b = b.todense() # type: ignore
|
|
332
|
+
|
|
328
333
|
if np.iscomplexobj(b):
|
|
329
334
|
b = b.astype(np.complex128)
|
|
330
335
|
else:
|
|
@@ -337,7 +342,7 @@ class PardisoInterface:
|
|
|
337
342
|
Returns:
|
|
338
343
|
int: The error code
|
|
339
344
|
"""
|
|
340
|
-
|
|
345
|
+
|
|
341
346
|
self._configure(A)
|
|
342
347
|
zerovec = np.zeros_like((A.shape[0], 1), dtype=A.dtype)
|
|
343
348
|
_, error = self._call_solver(A, zerovec, phase=PARDISOPhase.SYMBOLIC_FACTOR)
|
|
@@ -349,7 +354,7 @@ class PardisoInterface:
|
|
|
349
354
|
Returns:
|
|
350
355
|
int: The error code
|
|
351
356
|
"""
|
|
352
|
-
|
|
357
|
+
|
|
353
358
|
self._configure(A)
|
|
354
359
|
zerovec = np.zeros_like((A.shape[0], 1), dtype=A.dtype)
|
|
355
360
|
_, error = self._call_solver(A, zerovec, phase=PARDISOPhase.NUMERIC_FACTOR)
|
|
@@ -372,7 +377,7 @@ class PardisoInterface:
|
|
|
372
377
|
|
|
373
378
|
def configure_solver(self,
|
|
374
379
|
perm_algo: int = 3,
|
|
375
|
-
nthreads: int = None,
|
|
380
|
+
nthreads: int | None = None,
|
|
376
381
|
user_perm: int = 0,
|
|
377
382
|
n_refine_steps: int = 0,
|
|
378
383
|
pivot_pert: int = 13,
|
|
@@ -388,7 +393,7 @@ class PardisoInterface:
|
|
|
388
393
|
weighted_matching (int, optional): weighted matching mode. Defaults to 2.
|
|
389
394
|
"""
|
|
390
395
|
if nthreads is None:
|
|
391
|
-
nthreads = int(os.environ.get('OMP_NUM_THREADS'))
|
|
396
|
+
nthreads = int(os.environ.get('OMP_NUM_THREADS', default="4"))
|
|
392
397
|
|
|
393
398
|
self.IPARM[1] = perm_algo
|
|
394
399
|
self.IPARM[2] = nthreads
|
|
@@ -418,14 +423,15 @@ class PardisoInterface:
|
|
|
418
423
|
A_indices = A.indices + 1
|
|
419
424
|
|
|
420
425
|
# Define the appropriate data type (complex vs real)
|
|
426
|
+
|
|
421
427
|
if self.complex:
|
|
422
428
|
VALUE_P = A.data.ctypes.data_as(CPX16_P)
|
|
423
429
|
RHS_P = b.ctypes.data_as(CPX16_P)
|
|
424
430
|
X_P = x.ctypes.data_as(CPX16_P)
|
|
425
431
|
else:
|
|
426
432
|
VALUE_P = A.data.ctypes.data_as(CFLOAT64_P)
|
|
427
|
-
RHS_P = b.ctypes.data_as(CFLOAT64_P)
|
|
428
|
-
X_P = x.ctypes.data_as(CFLOAT64_P)
|
|
433
|
+
RHS_P = b.ctypes.data_as(CFLOAT64_P) # type: ignore
|
|
434
|
+
X_P = x.ctypes.data_as(CFLOAT64_P) # type: ignore
|
|
429
435
|
|
|
430
436
|
# Calls the pardiso function
|
|
431
437
|
self._pardiso_interface(
|