emerge 0.5.5__py3-none-any.whl → 0.5.6__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/__init__.py +3 -0
- emerge/_emerge/cs.py +2 -2
- emerge/_emerge/elements/ned2_interp.py +21 -26
- emerge/_emerge/elements/nedleg2.py +25 -43
- emerge/_emerge/geo/shapes.py +26 -3
- emerge/_emerge/geometry.py +27 -1
- emerge/_emerge/material.py +1 -0
- emerge/_emerge/mesh3d.py +63 -14
- emerge/_emerge/mesher.py +7 -4
- emerge/_emerge/mth/optimized.py +30 -0
- emerge/_emerge/periodic.py +46 -16
- emerge/_emerge/physics/microwave/assembly/assembler.py +4 -21
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +23 -19
- emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +465 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +59 -18
- emerge/_emerge/physics/microwave/microwave_3d.py +22 -4
- emerge/_emerge/physics/microwave/microwave_bc.py +101 -35
- emerge/_emerge/physics/microwave/microwave_data.py +1 -1
- emerge/_emerge/plot/pyvista/display.py +40 -7
- emerge/_emerge/plot/pyvista/display_settings.py +1 -0
- emerge/_emerge/simmodel.py +15 -1
- emerge/_emerge/solve_interfaces/cudss_interface.py +44 -2
- emerge/_emerge/solve_interfaces/pardiso_interface.py +1 -0
- emerge/_emerge/solver.py +26 -19
- emerge/ext.py +4 -0
- emerge/lib.py +1 -1
- {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/METADATA +5 -3
- {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/RECORD +31 -30
- emerge/_emerge/elements/legrange2.py +0 -172
- {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/WHEEL +0 -0
- {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -20,7 +20,7 @@ import numpy as np
|
|
|
20
20
|
from loguru import logger
|
|
21
21
|
from typing import Callable, Literal
|
|
22
22
|
from ...selection import Selection, FaceSelection
|
|
23
|
-
from ...cs import CoordinateSystem, Axis, GCS
|
|
23
|
+
from ...cs import CoordinateSystem, Axis, GCS, _parse_axis
|
|
24
24
|
from ...coord import Line
|
|
25
25
|
from ...geometry import GeoSurface, GeoObject
|
|
26
26
|
from dataclasses import dataclass
|
|
@@ -28,7 +28,23 @@ from collections import defaultdict
|
|
|
28
28
|
from ...bc import BoundaryCondition, BoundaryConditionSet, Periodic
|
|
29
29
|
from ...periodic import PeriodicCell, HexCell, RectCell
|
|
30
30
|
from ...material import Material
|
|
31
|
-
from ...const import Z0, C0,
|
|
31
|
+
from ...const import Z0, C0, EPS0, MU0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
############################################################
|
|
36
|
+
# UTILITY FUNCTIONS #
|
|
37
|
+
############################################################
|
|
38
|
+
|
|
39
|
+
def _inner_product(function: Callable, x: np.ndarray, y: np.ndarray, z: np.ndarray, ax: Axis) -> float:
|
|
40
|
+
Exyz = function(x,y,z)
|
|
41
|
+
return np.sum(Exyz[0,:]*ax.x + Exyz[1,:]*ax.y + Exyz[2,:]*ax.z)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
############################################################
|
|
46
|
+
# MAIN BC MANAGER CLASS #
|
|
47
|
+
############################################################
|
|
32
48
|
|
|
33
49
|
class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
34
50
|
|
|
@@ -83,6 +99,10 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
|
83
99
|
return port
|
|
84
100
|
|
|
85
101
|
|
|
102
|
+
############################################################
|
|
103
|
+
# BOUNDARY CONDITIONS #
|
|
104
|
+
############################################################
|
|
105
|
+
|
|
86
106
|
|
|
87
107
|
class PEC(BoundaryCondition):
|
|
88
108
|
|
|
@@ -313,7 +333,7 @@ class PortMode:
|
|
|
313
333
|
self.energy = np.mean(np.abs(self.modefield)**2)
|
|
314
334
|
|
|
315
335
|
def __str__(self):
|
|
316
|
-
return f'PortMode(k0={self.k0}, beta={self.beta}
|
|
336
|
+
return f'PortMode(k0={self.k0}, beta={self.beta}({self.neff:.3f}))'
|
|
317
337
|
|
|
318
338
|
def set_power(self, power: complex) -> None:
|
|
319
339
|
self.norm_factor = np.sqrt(1/np.abs(power))
|
|
@@ -371,8 +391,8 @@ class FloquetPort(PortBC):
|
|
|
371
391
|
"""
|
|
372
392
|
return 1j*self.get_beta(k0)
|
|
373
393
|
|
|
374
|
-
def get_Uinc(self,
|
|
375
|
-
return -2*1j*self.get_beta(k0)*self.
|
|
394
|
+
def get_Uinc(self, x_global: np.ndarray, y_global: np.ndarray, z_global: np.ndarray, k0: float) -> np.ndarray:
|
|
395
|
+
return -2*1j*self.get_beta(k0)*self.port_mode_3d_global(x_global, y_global, z_global, k0)
|
|
376
396
|
|
|
377
397
|
def port_mode_3d(self,
|
|
378
398
|
x_local: np.ndarray,
|
|
@@ -449,7 +469,7 @@ class ModalPort(PortBC):
|
|
|
449
469
|
self.port_number: int= port_number
|
|
450
470
|
self.active: bool = active
|
|
451
471
|
self.power: float = power
|
|
452
|
-
|
|
472
|
+
self.alignment_vectors: list[Axis] = []
|
|
453
473
|
|
|
454
474
|
self.selected_mode: int = 0
|
|
455
475
|
self.modes: dict[float, list[PortMode]] = defaultdict(list)
|
|
@@ -459,6 +479,7 @@ class ModalPort(PortBC):
|
|
|
459
479
|
self.initialized: bool = False
|
|
460
480
|
self._first_k0: float | None = None
|
|
461
481
|
self._last_k0: float | None = None
|
|
482
|
+
|
|
462
483
|
|
|
463
484
|
if cs is None:
|
|
464
485
|
logger.info('Constructing coordinate system from normal port')
|
|
@@ -474,6 +495,17 @@ class ModalPort(PortBC):
|
|
|
474
495
|
def modetype(self, k0: float) -> Literal['TEM','TE','TM']:
|
|
475
496
|
return self.get_mode(k0).modetype
|
|
476
497
|
|
|
498
|
+
def align_modes(self, *axes: tuple | np.ndarray | Axis) -> None:
|
|
499
|
+
"""Set a reriees of Axis objects that define a sequence of mode field
|
|
500
|
+
alignments.
|
|
501
|
+
|
|
502
|
+
The modes will be sorted to maximize the inner product: |∬ E(x,y) · ax dS|
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
*axes (tuple, np.ndarray, Axis): The alignment vectors.
|
|
506
|
+
"""
|
|
507
|
+
self.alignment_vectors = [_parse_axis(ax) for ax in axes]
|
|
508
|
+
|
|
477
509
|
@property
|
|
478
510
|
def nmodes(self) -> int:
|
|
479
511
|
if self._last_k0 is None:
|
|
@@ -488,6 +520,26 @@ class ModalPort(PortBC):
|
|
|
488
520
|
def sort_modes(self) -> None:
|
|
489
521
|
"""Sorts the port modes based on total energy
|
|
490
522
|
"""
|
|
523
|
+
|
|
524
|
+
if len(self.alignment_vectors) > 0:
|
|
525
|
+
logger.trace(f'Sorting modes based on alignment vectors: {self.alignment_vectors}')
|
|
526
|
+
X, Y, Z = self.selection.sample(5)
|
|
527
|
+
X = X.flatten()
|
|
528
|
+
Y = Y.flatten()
|
|
529
|
+
Z = Z.flatten()
|
|
530
|
+
for k0, modes in self.modes.items():
|
|
531
|
+
logger.trace(f'Aligning modes for k0={k0:.3f} rad/m')
|
|
532
|
+
new_modes = []
|
|
533
|
+
for ax in self.alignment_vectors:
|
|
534
|
+
logger.trace(f'.mode vector {ax}')
|
|
535
|
+
integrals = [_inner_product(m.E_function, X, Y, Z, ax) for m in modes]
|
|
536
|
+
integral, opt_mode = sorted([pair for pair in zip(integrals, modes)], key=lambda x: abs(x[0]), reverse=True)[0]
|
|
537
|
+
opt_mode.polarity = np.sign(integral.real)
|
|
538
|
+
logger.trace(f'Optimal mode = {opt_mode} ({integral}), polarization alignment = {opt_mode.polarity}')
|
|
539
|
+
new_modes.append(opt_mode)
|
|
540
|
+
|
|
541
|
+
self.modes[k0] = new_modes
|
|
542
|
+
return
|
|
491
543
|
for k0, modes in self.modes.items():
|
|
492
544
|
self.modes[k0] = sorted(modes, key=lambda m: m.energy, reverse=True)
|
|
493
545
|
|
|
@@ -578,8 +630,8 @@ class ModalPort(PortBC):
|
|
|
578
630
|
def get_gamma(self, k0: float) -> complex:
|
|
579
631
|
return 1j*self.get_beta(k0)
|
|
580
632
|
|
|
581
|
-
def get_Uinc(self,
|
|
582
|
-
return -2*1j*self.get_beta(k0)*self.
|
|
633
|
+
def get_Uinc(self, x_global: np.ndarray, y_global: np.ndarray, z_global: np.ndarray, k0) -> np.ndarray:
|
|
634
|
+
return -2*1j*self.get_beta(k0)*self.port_mode_3d_global(x_global, y_global, z_global, k0)
|
|
583
635
|
|
|
584
636
|
def port_mode_3d(self,
|
|
585
637
|
x_local: np.ndarray,
|
|
@@ -691,8 +743,8 @@ class RectangularWaveguide(PortBC):
|
|
|
691
743
|
"""
|
|
692
744
|
return 1j*self.get_beta(k0)
|
|
693
745
|
|
|
694
|
-
def get_Uinc(self,
|
|
695
|
-
return -2*1j*self.get_beta(k0)*self.
|
|
746
|
+
def get_Uinc(self, x_global: np.ndarray, y_global: np.ndarray, z_global: np.ndarray, k0: float) -> np.ndarray:
|
|
747
|
+
return -2*1j*self.get_beta(k0)*self.port_mode_3d_global(x_global, y_global, z_global, k0)
|
|
696
748
|
|
|
697
749
|
def port_mode_3d(self,
|
|
698
750
|
x_local: np.ndarray,
|
|
@@ -826,9 +878,9 @@ class LumpedPort(PortBC):
|
|
|
826
878
|
"""
|
|
827
879
|
return 1j*k0*Z0/self.surfZ
|
|
828
880
|
|
|
829
|
-
def get_Uinc(self,
|
|
881
|
+
def get_Uinc(self, x_global: np.ndarray, y_global: np.ndarray, z_global: np.ndarray, k0) -> np.ndarray:
|
|
830
882
|
Emag = -1j*2*k0 * self.voltage/self.height * (Z0/self.surfZ)
|
|
831
|
-
return Emag*self.
|
|
883
|
+
return Emag*self.port_mode_3d_global(x_global, y_global, z_global, k0)
|
|
832
884
|
|
|
833
885
|
def port_mode_3d(self,
|
|
834
886
|
x_local: np.ndarray,
|
|
@@ -871,7 +923,6 @@ class LumpedPort(PortBC):
|
|
|
871
923
|
Exg, Eyg, Ezg = self.cs.in_global_basis(Ex, Ey, Ez)
|
|
872
924
|
return np.array([Exg, Eyg, Ezg])
|
|
873
925
|
|
|
874
|
-
|
|
875
926
|
class LumpedElement(RobinBC):
|
|
876
927
|
|
|
877
928
|
_include_stiff: bool = True
|
|
@@ -948,8 +999,6 @@ class LumpedElement(RobinBC):
|
|
|
948
999
|
"""
|
|
949
1000
|
return 1j*k0*Z0/self.surfZ(k0)
|
|
950
1001
|
|
|
951
|
-
|
|
952
|
-
|
|
953
1002
|
class SurfaceImpedance(RobinBC):
|
|
954
1003
|
|
|
955
1004
|
_include_stiff: bool = True
|
|
@@ -959,29 +1008,45 @@ class SurfaceImpedance(RobinBC):
|
|
|
959
1008
|
def __init__(self,
|
|
960
1009
|
face: FaceSelection | GeoSurface,
|
|
961
1010
|
material: Material | None = None,
|
|
1011
|
+
surface_conductance: float | None = None,
|
|
1012
|
+
surface_roughness: float = 0,
|
|
1013
|
+
sr_model: Literal['Hammerstad-Jensen'] = 'Hammerstad-Jensen',
|
|
962
1014
|
):
|
|
963
|
-
"""Generates a
|
|
964
|
-
|
|
965
|
-
The
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1015
|
+
"""Generates a SurfaceImpedance bounary condition.
|
|
1016
|
+
|
|
1017
|
+
The surface impedance model treats a 2D surface selection as a finite conductor. It is not
|
|
1018
|
+
intended to be used for dielectric materials.
|
|
1019
|
+
|
|
1020
|
+
The surface resistivity is computed based on the material properties: σ, ε and μ.
|
|
1021
|
+
|
|
1022
|
+
The user may also supply the surface condutivity directly.
|
|
1023
|
+
|
|
1024
|
+
Optionally, a surface roughness in meters RMS may be supplied. In the current implementation
|
|
1025
|
+
The Hammersstad-Jensen model is used increasing the resistivity by a factor (1 + 2/π tan⁻¹(1.4(Δ/δ)²).
|
|
970
1026
|
|
|
971
1027
|
Args:
|
|
972
|
-
face (FaceSelection
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
active (bool, optional): Whether the port is active. Defaults to False.
|
|
978
|
-
power (float, optional): The port output power. Defaults to 1.
|
|
979
|
-
Z0 (float, optional): The port impedance. Defaults to 50.
|
|
1028
|
+
face (FaceSelection | GeoSurface): The face to apply this condition to.
|
|
1029
|
+
material (Material | None, optional): The matrial to assign. Defaults to None.
|
|
1030
|
+
surface_conductance (float | None, optional): The specific bulk conductivity to use. Defaults to None.
|
|
1031
|
+
surface_roughness (float, optional): The surface roughness. Defaults to 0.
|
|
1032
|
+
sr_model (Literal['Hammerstad, optional): The surface roughness model. Defaults to 'Hammerstad-Jensen'.
|
|
980
1033
|
"""
|
|
981
1034
|
super().__init__(face)
|
|
982
1035
|
|
|
983
|
-
self.
|
|
984
|
-
|
|
1036
|
+
self._material: Material | None = material
|
|
1037
|
+
self._mur: float | complex = 1.0
|
|
1038
|
+
self._epsr: float | complex = 1.0
|
|
1039
|
+
|
|
1040
|
+
self.sigma: float = 0.0
|
|
1041
|
+
if material is not None:
|
|
1042
|
+
self.sigma = material.cond
|
|
1043
|
+
self._mur = material.ur
|
|
1044
|
+
self._epsr = material.er
|
|
1045
|
+
if surface_conductance is not None:
|
|
1046
|
+
self.sigma = surface_conductance
|
|
1047
|
+
|
|
1048
|
+
self._sr: float = surface_roughness
|
|
1049
|
+
self._sr_model: str = sr_model
|
|
985
1050
|
|
|
986
1051
|
def get_basis(self) -> np.ndarray | None:
|
|
987
1052
|
return None
|
|
@@ -1004,9 +1069,10 @@ class SurfaceImpedance(RobinBC):
|
|
|
1004
1069
|
complex: The γ-constant
|
|
1005
1070
|
"""
|
|
1006
1071
|
w0 = k0*C0
|
|
1007
|
-
sigma = self.
|
|
1072
|
+
sigma = self.sigma
|
|
1008
1073
|
rho = 1/sigma
|
|
1009
|
-
d_skin = (2*rho/(w0*MU0) * ((1+(w0*EPS0*rho)**2)**0.5 + rho*w0*EPS0))**0.5
|
|
1010
|
-
d_skin = (2*rho/(w0*MU0))**0.5
|
|
1074
|
+
d_skin = (2*rho/(w0*MU0*self._mur) * ((1+(w0*EPS0*self._epsr*rho)**2)**0.5 + rho*w0*EPS0*self._epsr))**0.5
|
|
1011
1075
|
R = rho/d_skin
|
|
1076
|
+
if self._sr_model=='Hammerstad-Jensen' and self._sr > 0.0:
|
|
1077
|
+
R = R * (1 + 2/np.pi * np.arctan(1.4*(self._sr/d_skin)**2))
|
|
1012
1078
|
return 1j*k0*Z0/R
|
|
@@ -777,7 +777,7 @@ class MWField:
|
|
|
777
777
|
syms (list[Literal['Ex','Ey','Ez','Hx','Hy','Hz']], optional): E and H-plane symmetry planes where Ex is E-symmetry in x=0. Defaults to []
|
|
778
778
|
|
|
779
779
|
Returns:
|
|
780
|
-
tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
780
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: Angles (N,), E(3,N), H(3,N)
|
|
781
781
|
"""
|
|
782
782
|
refdir = _parse_axis(ref_direction).np
|
|
783
783
|
plane_normal_parsed = _parse_axis(plane_normal).np
|
|
@@ -213,8 +213,18 @@ class PVDisplay(BaseDisplay):
|
|
|
213
213
|
self._plot.add_key_event("m", self.activate_ruler) # type: ignore
|
|
214
214
|
self._plot.add_key_event("f", self.activate_object) # type: ignore
|
|
215
215
|
|
|
216
|
-
self._ctr: int = 0
|
|
217
|
-
|
|
216
|
+
self._ctr: int = 0
|
|
217
|
+
|
|
218
|
+
self.camera_position = (1, -1, 1) # +X, +Z, -Y
|
|
219
|
+
|
|
220
|
+
def _update_camera(self):
|
|
221
|
+
x,y,z = self._plot.camera.position
|
|
222
|
+
d = (x**2+y**2+z**2)**(0.5)
|
|
223
|
+
px, py, pz = self.camera_position
|
|
224
|
+
dp = (px**2+py**2+pz**2)**(0.5)
|
|
225
|
+
px, py, pz = px/dp, py/dp, pz/dp
|
|
226
|
+
self._plot.camera.position = (d*px, d*py, d*pz)
|
|
227
|
+
|
|
218
228
|
def activate_ruler(self):
|
|
219
229
|
self._plot.disable_picking()
|
|
220
230
|
self._selector.turn_off()
|
|
@@ -228,6 +238,7 @@ class PVDisplay(BaseDisplay):
|
|
|
228
238
|
def show(self):
|
|
229
239
|
""" Shows the Pyvista display. """
|
|
230
240
|
self._ruler.min_length = max(1e-3, min(self._mesh.edge_lengths))
|
|
241
|
+
self._update_camera()
|
|
231
242
|
self._add_aux_items()
|
|
232
243
|
if self._do_animate:
|
|
233
244
|
self._plot.show(auto_close=False, interactive_update=True, before_close_callback=self._close_callback)
|
|
@@ -300,7 +311,25 @@ class PVDisplay(BaseDisplay):
|
|
|
300
311
|
cells[:,1:] = self._mesh.tets[:,tets].T
|
|
301
312
|
cells[:,0] = 4
|
|
302
313
|
celltypes = np.full(ntets, fill_value=pv.CellType.TETRA, dtype=np.uint8)
|
|
303
|
-
points = self._mesh.nodes.T
|
|
314
|
+
points = self._mesh.nodes.copy().T
|
|
315
|
+
return pv.UnstructuredGrid(cells, celltypes, points)
|
|
316
|
+
|
|
317
|
+
def _volume_edges(self, obj: GeoObject | Selection) -> pv.UnstructuredGrid:
|
|
318
|
+
"""Adds the edges of objects
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
obj (DomainSelection | None, optional): _description_. Defaults to None.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
pv.UnstructuredGrid: The unstrutured grid object
|
|
325
|
+
"""
|
|
326
|
+
edge_ids = self._mesh.domain_edges(obj.dimtags)
|
|
327
|
+
nedges = edge_ids.shape[0]
|
|
328
|
+
cells = np.zeros((nedges,3), dtype=np.int64)
|
|
329
|
+
cells[:,1:] = self._mesh.edges[:,edge_ids].T
|
|
330
|
+
cells[:,0] = 2
|
|
331
|
+
celltypes = np.full(nedges, fill_value=pv.CellType.CUBIC_LINE, dtype=np.uint8)
|
|
332
|
+
points = self._mesh.nodes.copy().T
|
|
304
333
|
return pv.UnstructuredGrid(cells, celltypes, points)
|
|
305
334
|
|
|
306
335
|
def mesh_surface(self, surface: FaceSelection) -> pv.UnstructuredGrid:
|
|
@@ -310,7 +339,8 @@ class PVDisplay(BaseDisplay):
|
|
|
310
339
|
cells[:,1:] = self._mesh.tris[:,tris].T
|
|
311
340
|
cells[:,0] = 3
|
|
312
341
|
celltypes = np.full(ntris, fill_value=pv.CellType.TRIANGLE, dtype=np.uint8)
|
|
313
|
-
points = self._mesh.nodes.T
|
|
342
|
+
points = self._mesh.nodes.copy().T
|
|
343
|
+
points[:,2] += self.set.z_boost
|
|
314
344
|
return pv.UnstructuredGrid(cells, celltypes, points)
|
|
315
345
|
|
|
316
346
|
def mesh(self, obj: GeoObject | Selection | Iterable) -> pv.UnstructuredGrid | None:
|
|
@@ -328,8 +358,10 @@ class PVDisplay(BaseDisplay):
|
|
|
328
358
|
|
|
329
359
|
## OBLIGATORY METHODS
|
|
330
360
|
def add_object(self, obj: GeoObject | Selection, *args, **kwargs):
|
|
331
|
-
kwargs = setdefault(kwargs, color=obj.color_rgb, opacity=obj.opacity, silhouette=
|
|
332
|
-
|
|
361
|
+
kwargs = setdefault(kwargs, color=obj.color_rgb, opacity=obj.opacity, silhouette=False, show_edges=False, pickable=True)
|
|
362
|
+
|
|
363
|
+
actor = self._plot.add_mesh(self.mesh(obj), *args, **kwargs)
|
|
364
|
+
self._plot.add_mesh(self._volume_edges(_select(obj)), color='#000000', line_width=1, show_edges=True)
|
|
333
365
|
|
|
334
366
|
def add_scatter(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray):
|
|
335
367
|
"""Adds a scatter point cloud
|
|
@@ -348,7 +380,7 @@ class PVDisplay(BaseDisplay):
|
|
|
348
380
|
XYZ=None,
|
|
349
381
|
field: Literal['E','H'] = 'E',
|
|
350
382
|
k0: float | None = None,
|
|
351
|
-
mode_number: int
|
|
383
|
+
mode_number: int = 0) -> None:
|
|
352
384
|
|
|
353
385
|
if XYZ:
|
|
354
386
|
X,Y,Z = XYZ
|
|
@@ -385,6 +417,7 @@ class PVDisplay(BaseDisplay):
|
|
|
385
417
|
k0 = port.get_mode(0).k0
|
|
386
418
|
else:
|
|
387
419
|
k0 = 1
|
|
420
|
+
port.selected_mode = mode_number
|
|
388
421
|
F = port.port_mode_3d_global(xf,yf,zf,k0, which=field)
|
|
389
422
|
|
|
390
423
|
Fx = F[0,:].reshape(X.shape).T
|
emerge/_emerge/simmodel.py
CHANGED
|
@@ -313,7 +313,7 @@ class Simulation3D:
|
|
|
313
313
|
for geo in _GEOMANAGER.all_geometries():
|
|
314
314
|
self.display.add_object(geo)
|
|
315
315
|
if selections:
|
|
316
|
-
[self.display.add_object(sel, color='red', opacity=0.
|
|
316
|
+
[self.display.add_object(sel, color='red', opacity=0.3) for sel in selections]
|
|
317
317
|
self.display.show()
|
|
318
318
|
|
|
319
319
|
return None
|
|
@@ -435,7 +435,21 @@ class Simulation3D:
|
|
|
435
435
|
else:
|
|
436
436
|
yield (dim[i_iter] for dim in dims_flat) # type: ignore
|
|
437
437
|
self.mw.cache_matrices = True
|
|
438
|
+
|
|
439
|
+
def export(self, filename: str):
|
|
440
|
+
"""Exports the model or mesh depending on the extension.
|
|
441
|
+
|
|
442
|
+
Exporting is realized by GMSH.
|
|
443
|
+
Supported file formats are:
|
|
444
|
+
|
|
445
|
+
3D Model: .opt, .geo_unrolled, .brep, .xao ,.step and .iges
|
|
446
|
+
Mesh: .msh, .inp, .key, ._0000.rad, .celum, .cgns, .diff, .unv, .ir3, .mes, .mesh
|
|
447
|
+
.mail, .m, .bdf, .off, .p3d, .stl, .wrl, .vtk, .dat, .ply2, .su2, .neu, .x3d
|
|
438
448
|
|
|
449
|
+
Args:
|
|
450
|
+
filename (str): The filename
|
|
451
|
+
"""
|
|
452
|
+
gmsh.write(filename)
|
|
439
453
|
############################################################
|
|
440
454
|
# DEPRICATED FUNCTIONS #
|
|
441
455
|
############################################################
|
|
@@ -21,6 +21,7 @@ from nvmath import CudaDataType # ty: ignore
|
|
|
21
21
|
|
|
22
22
|
from scipy.sparse import csr_matrix
|
|
23
23
|
import numpy as np
|
|
24
|
+
from typing import Literal
|
|
24
25
|
|
|
25
26
|
from loguru import logger
|
|
26
27
|
|
|
@@ -51,6 +52,11 @@ def _c_pointer(arry) -> int:
|
|
|
51
52
|
############################################################
|
|
52
53
|
|
|
53
54
|
class CuDSSInterface:
|
|
55
|
+
"""the CuDSSInterface class implements the nvmath bindings and cupy
|
|
56
|
+
control for EMerge.
|
|
57
|
+
"""
|
|
58
|
+
AlgType = cudss.AlgType
|
|
59
|
+
|
|
54
60
|
def __init__(self):
|
|
55
61
|
self.A_cu = None
|
|
56
62
|
self.b_cu = None
|
|
@@ -92,10 +98,31 @@ class CuDSSInterface:
|
|
|
92
98
|
reorder_alg.nbytes
|
|
93
99
|
)
|
|
94
100
|
|
|
95
|
-
def set_algorithm(self, alg_type:
|
|
96
|
-
|
|
101
|
+
def set_algorithm(self, alg_type: Literal['METIS','COLAMD','COLAM_BT','AMD']):
|
|
102
|
+
"""Define fill-in reduction column permuation algorithm. The options are:
|
|
103
|
+
|
|
104
|
+
- "METIS" (Default) = NVidia's own Nested Dissection METIS sorter
|
|
105
|
+
- "COLAMD" = Column approximate minimum degree
|
|
106
|
+
- "COLAM_BT" = Column Approximate Minimum Degree Block Triangular
|
|
107
|
+
- "AMD" = Approximate Minimum Degree
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
alg_type (str): The chosen type
|
|
111
|
+
"""
|
|
112
|
+
if alg_type=='METIS':
|
|
113
|
+
self.RALG = ALG_NEST_DISS_METIS
|
|
114
|
+
elif alg_type =='COLAMD':
|
|
115
|
+
self.RALG = ALG_COLAMD
|
|
116
|
+
elif alg_type == 'COLAMD_BT':
|
|
117
|
+
self.RALG = ALG_COLAMD_BLOCK_TRI
|
|
118
|
+
elif alg_type == 'AMD':
|
|
119
|
+
self.RALG = ALG_AMD
|
|
120
|
+
else:
|
|
121
|
+
logger.warning(f'Algorithm type {alg_type} is not of the chosen set. Ignoring setting.')
|
|
97
122
|
|
|
98
123
|
def init_type(self):
|
|
124
|
+
"""Initializes the value data type of the solver (float vs complex, single vs double).
|
|
125
|
+
"""
|
|
99
126
|
if self._PRES == 1:
|
|
100
127
|
if self._COMP:
|
|
101
128
|
self.c_dtype = cp.complex64
|
|
@@ -112,6 +139,11 @@ class CuDSSInterface:
|
|
|
112
139
|
self.VTYPE = FLOAT64
|
|
113
140
|
|
|
114
141
|
def submit_matrix(self, A: csr_matrix):
|
|
142
|
+
"""Sets the given csr_matrix as the matrix to be solved.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
A (csr_matrix): The csr_format matrix for the problem Ax=b
|
|
146
|
+
"""
|
|
115
147
|
self.N = A.shape[0]
|
|
116
148
|
|
|
117
149
|
if np.iscomplexobj(A):
|
|
@@ -132,12 +164,21 @@ class CuDSSInterface:
|
|
|
132
164
|
self._COL_IDS = self.A_cu.indices.astype(cp.int32)
|
|
133
165
|
|
|
134
166
|
def submit_vector(self, b: np.ndarray):
|
|
167
|
+
"""Submits the dense vector b to be solved.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
b (np.ndarray): The dense vector for the problem Ax=b
|
|
171
|
+
"""
|
|
135
172
|
self.b_cu = cp.array(b).astype(self.c_dtype)
|
|
136
173
|
|
|
137
174
|
def create_solvec(self):
|
|
175
|
+
"""Initializes a solution vector that the nvmath binding can access.
|
|
176
|
+
"""
|
|
138
177
|
self.x_cu = cp.empty_like(self.b_cu)
|
|
139
178
|
|
|
140
179
|
def _update_dss_data(self):
|
|
180
|
+
"""Updates the currently defined matrix data into the existing memory.ALG_AMD
|
|
181
|
+
"""
|
|
141
182
|
cudss.matrix_set_values(self.A_cobj, _c_pointer(self._VAL))
|
|
142
183
|
|
|
143
184
|
|
|
@@ -147,6 +188,7 @@ class CuDSSInterface:
|
|
|
147
188
|
int(self.VTYPE), int(cudss.Layout.COL_MAJOR))
|
|
148
189
|
|
|
149
190
|
def _create_dss_data(self):
|
|
191
|
+
"""Creates a new memory slot for the CSR matrix of the matrix A"""
|
|
150
192
|
self.A_cobj = cudss.matrix_create_csr(
|
|
151
193
|
self.N,self.N,self._NNZ,
|
|
152
194
|
_c_pointer(self._ROW_START),
|
emerge/_emerge/solver.py
CHANGED
|
@@ -44,11 +44,8 @@ If so, attempt to import PyPardiso (if its installed)
|
|
|
44
44
|
############################################################
|
|
45
45
|
|
|
46
46
|
if 'arm' not in platform.processor():
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
_PARDISO_AVAILABLE = True
|
|
50
|
-
except ModuleNotFoundError:
|
|
51
|
-
logger.info('Pardiso not found, defaulting to SuperLU')
|
|
47
|
+
from .solve_interfaces.pardiso_interface import PardisoInterface
|
|
48
|
+
_PARDISO_AVAILABLE = True
|
|
52
49
|
|
|
53
50
|
|
|
54
51
|
############################################################
|
|
@@ -62,7 +59,6 @@ try:
|
|
|
62
59
|
except ModuleNotFoundError:
|
|
63
60
|
logger.debug('UMFPACK not found, defaulting to SuperLU')
|
|
64
61
|
|
|
65
|
-
|
|
66
62
|
############################################################
|
|
67
63
|
# CUDSS #
|
|
68
64
|
############################################################
|
|
@@ -72,7 +68,10 @@ try:
|
|
|
72
68
|
_CUDSS_AVAILABLE = True
|
|
73
69
|
except ModuleNotFoundError:
|
|
74
70
|
pass
|
|
75
|
-
|
|
71
|
+
except ImportError as e:
|
|
72
|
+
logger.error('Error while importing CuDSS dependencies:')
|
|
73
|
+
logger.exception(e)
|
|
74
|
+
|
|
76
75
|
############################################################
|
|
77
76
|
# SOLVE REPORT #
|
|
78
77
|
############################################################
|
|
@@ -177,7 +176,8 @@ def filter_real_modes(eigvals: np.ndarray, eigvecs: np.ndarray,
|
|
|
177
176
|
# EIGENMODE ORTHOGONALITY CHECK #
|
|
178
177
|
############################################################
|
|
179
178
|
|
|
180
|
-
def filter_unique_eigenpairs(eigen_values: list[complex],
|
|
179
|
+
def filter_unique_eigenpairs(eigen_values: list[complex],
|
|
180
|
+
eigen_vectors: list[np.ndarray], tol=-3) -> tuple[list[complex], list[np.ndarray]]:
|
|
181
181
|
"""
|
|
182
182
|
Filters eigenvectors by orthogonality using dot-product tolerance.
|
|
183
183
|
|
|
@@ -301,7 +301,8 @@ class Solver:
|
|
|
301
301
|
Options may be ignored depending on the type of solver used."""
|
|
302
302
|
pass
|
|
303
303
|
|
|
304
|
-
def solve(self, A: csr_matrix, b: np.ndarray, precon: Preconditioner,
|
|
304
|
+
def solve(self, A: csr_matrix, b: np.ndarray, precon: Preconditioner,
|
|
305
|
+
reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
305
306
|
raise NotImplementedError("This classes Ax=B solver method is not implemented.")
|
|
306
307
|
|
|
307
308
|
def reset(self) -> None:
|
|
@@ -329,7 +330,8 @@ class EigSolver:
|
|
|
329
330
|
def duplicate(self) -> Solver:
|
|
330
331
|
return self.__class__()
|
|
331
332
|
|
|
332
|
-
def eig(self, A: csr_matrix | csr_matrix, B: csr_matrix | csr_matrix, nmodes: int = 6,
|
|
333
|
+
def eig(self, A: csr_matrix | csr_matrix, B: csr_matrix | csr_matrix, nmodes: int = 6,
|
|
334
|
+
target_k0: float = 0.0, which: str = 'LM', sign: float = 1.):
|
|
333
335
|
raise NotImplementedError("This classes eigenmdoe solver method is not implemented.")
|
|
334
336
|
|
|
335
337
|
def reset(self) -> None:
|
|
@@ -337,7 +339,6 @@ class EigSolver:
|
|
|
337
339
|
pass
|
|
338
340
|
|
|
339
341
|
|
|
340
|
-
|
|
341
342
|
############################################################
|
|
342
343
|
# SORTERS #
|
|
343
344
|
############################################################
|
|
@@ -379,7 +380,9 @@ class ILUPrecon(Preconditioner):
|
|
|
379
380
|
|
|
380
381
|
def init(self, A, b):
|
|
381
382
|
logger.info("Generating ILU Preconditioner")
|
|
382
|
-
self.ilu = sparse.linalg.spilu(A, drop_tol=1e-2, fill_factor=self.fill_factor,
|
|
383
|
+
self.ilu = sparse.linalg.spilu(A, drop_tol=1e-2, fill_factor=self.fill_factor, # ty: ignore
|
|
384
|
+
permc_spec='MMD_AT_PLUS_A', diag_pivot_thresh=0.001,
|
|
385
|
+
options=self.options)
|
|
383
386
|
self.M = sparse.linalg.LinearOperator(A.shape, self.ilu.solve) # ty: ignore
|
|
384
387
|
|
|
385
388
|
|
|
@@ -601,7 +604,7 @@ class CuDSSSolver(Solver):
|
|
|
601
604
|
self.fact_numb = False
|
|
602
605
|
|
|
603
606
|
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1):
|
|
604
|
-
logger.info(f'[{id}] Calling cuDSS Solver')
|
|
607
|
+
logger.info(f'[ID={id}] Calling cuDSS Solver')
|
|
605
608
|
|
|
606
609
|
if self.fact_symb is False:
|
|
607
610
|
logger.debug('Executing symbollic factorization')
|
|
@@ -911,7 +914,8 @@ class SolveRoutine:
|
|
|
911
914
|
return new_routine
|
|
912
915
|
|
|
913
916
|
def set_solver(self, *solvers: EMSolver | EigSolver | Solver) -> None:
|
|
914
|
-
"""Set a given Solver class instance as the main solver.
|
|
917
|
+
"""Set a given Solver class instance as the main solver.
|
|
918
|
+
Solvers will be checked on validity for the given problem.
|
|
915
919
|
|
|
916
920
|
Args:
|
|
917
921
|
solver (EMSolver | Solver): The solver objects
|
|
@@ -923,7 +927,8 @@ class SolveRoutine:
|
|
|
923
927
|
self.forced_solver.append(solver)
|
|
924
928
|
|
|
925
929
|
def disable(self, *solvers: EMSolver) -> None:
|
|
926
|
-
"""Disable a given Solver class instance as the main solver.
|
|
930
|
+
"""Disable a given Solver class instance as the main solver.
|
|
931
|
+
Solvers will be checked on validity for the given problem.
|
|
927
932
|
|
|
928
933
|
Args:
|
|
929
934
|
solver (EMSolver): The solver objects
|
|
@@ -945,7 +950,8 @@ class SolveRoutine:
|
|
|
945
950
|
- "SI" = Single threaded
|
|
946
951
|
- "MT" = Multi threaded
|
|
947
952
|
- "MP" = Multi-processing,
|
|
948
|
-
smart_search (bool, optional): Wether to use smart-search solvers
|
|
953
|
+
smart_search (bool, optional): Wether to use smart-search solvers
|
|
954
|
+
for eigenmode problems. Defaults to False.
|
|
949
955
|
|
|
950
956
|
Returns:
|
|
951
957
|
SolveRoutine: The same SolveRoutine object.
|
|
@@ -998,7 +1004,6 @@ class SolveRoutine:
|
|
|
998
1004
|
return solver
|
|
999
1005
|
return self.pick_solver(A,b)
|
|
1000
1006
|
|
|
1001
|
-
|
|
1002
1007
|
def pick_solver(self, A: csr_matrix, b: np.ndarray) -> Solver:
|
|
1003
1008
|
"""Returns the relevant Solver object given a certain matrix and source vector
|
|
1004
1009
|
|
|
@@ -1187,7 +1192,10 @@ class SolveRoutine:
|
|
|
1187
1192
|
end = time.time()
|
|
1188
1193
|
|
|
1189
1194
|
simtime = end-start
|
|
1190
|
-
return eigen_values, eigen_modes, SolveReport(ndof=A.shape[0], nnz=A.nnz,
|
|
1195
|
+
return eigen_values, eigen_modes, SolveReport(ndof=A.shape[0], nnz=A.nnz,
|
|
1196
|
+
ndof_solve=Asel.shape[0], nnz_solve=Asel.nnz,
|
|
1197
|
+
simtime=simtime, solver=str(solver),
|
|
1198
|
+
sorter='None', precon='None')
|
|
1191
1199
|
|
|
1192
1200
|
def eig(self,
|
|
1193
1201
|
A: csr_matrix | csr_matrix,
|
|
@@ -1234,7 +1242,6 @@ class SolveRoutine:
|
|
|
1234
1242
|
|
|
1235
1243
|
return eigen_values, sols, SolveReport(ndof=A.shape[0], nnz=A.nnz, ndof_solve=Asel.shape[0], nnz_solve=Asel.nnz, simtime=simtime, solver=str(solver), sorter='None', precon='None')
|
|
1236
1244
|
|
|
1237
|
-
|
|
1238
1245
|
class AutomaticRoutine(SolveRoutine):
|
|
1239
1246
|
""" Defines the Automatic Routine for EMerge.
|
|
1240
1247
|
"""
|
emerge/ext.py
ADDED
emerge/lib.py
CHANGED
|
@@ -29,7 +29,7 @@ GREY = "#bfbfbf"
|
|
|
29
29
|
MET_ALUMINUM = Material(cond=3.77e7, color=GREY, opacity=0.5)
|
|
30
30
|
MET_CARBON = Material(cond=3.33e4, color=GREY, opacity=0.5)
|
|
31
31
|
MET_CHROMIUM = Material(cond=5.56e6, color=GREY, opacity=0.5)
|
|
32
|
-
MET_COPPER = Material(cond=5.8e7, color="#62290c", opacity=0
|
|
32
|
+
MET_COPPER = Material(cond=5.8e7, color="#62290c", opacity=1.0)
|
|
33
33
|
MET_GOLD = Material(cond=4.10e7, color=GREY, opacity=0.5)
|
|
34
34
|
MET_INDIUM = Material(cond=6.44e6, color=GREY, opacity=0.5)
|
|
35
35
|
MET_IRIDIUM = Material(cond=2.13e7, color=GREY, opacity=0.5)
|