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.

Files changed (32) hide show
  1. emerge/__init__.py +3 -0
  2. emerge/_emerge/cs.py +2 -2
  3. emerge/_emerge/elements/ned2_interp.py +21 -26
  4. emerge/_emerge/elements/nedleg2.py +25 -43
  5. emerge/_emerge/geo/shapes.py +26 -3
  6. emerge/_emerge/geometry.py +27 -1
  7. emerge/_emerge/material.py +1 -0
  8. emerge/_emerge/mesh3d.py +63 -14
  9. emerge/_emerge/mesher.py +7 -4
  10. emerge/_emerge/mth/optimized.py +30 -0
  11. emerge/_emerge/periodic.py +46 -16
  12. emerge/_emerge/physics/microwave/assembly/assembler.py +4 -21
  13. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +23 -19
  14. emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +465 -0
  15. emerge/_emerge/physics/microwave/assembly/robinbc.py +59 -18
  16. emerge/_emerge/physics/microwave/microwave_3d.py +22 -4
  17. emerge/_emerge/physics/microwave/microwave_bc.py +101 -35
  18. emerge/_emerge/physics/microwave/microwave_data.py +1 -1
  19. emerge/_emerge/plot/pyvista/display.py +40 -7
  20. emerge/_emerge/plot/pyvista/display_settings.py +1 -0
  21. emerge/_emerge/simmodel.py +15 -1
  22. emerge/_emerge/solve_interfaces/cudss_interface.py +44 -2
  23. emerge/_emerge/solve_interfaces/pardiso_interface.py +1 -0
  24. emerge/_emerge/solver.py +26 -19
  25. emerge/ext.py +4 -0
  26. emerge/lib.py +1 -1
  27. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/METADATA +5 -3
  28. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/RECORD +31 -30
  29. emerge/_emerge/elements/legrange2.py +0 -172
  30. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/WHEEL +0 -0
  31. {emerge-0.5.5.dist-info → emerge-0.5.6.dist-info}/entry_points.txt +0 -0
  32. {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, PI, EPS0, MU0
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}, neff={self.neff}, energy={self.energy})'
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, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
375
- return -2*1j*self.get_beta(k0)*self.port_mode_3d(x_local, y_local, k0)
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, x_local, y_local, k0) -> np.ndarray:
582
- return -2*1j*self.get_beta(k0)*self.port_mode_3d(x_local, y_local, k0)
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, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
695
- return -2*1j*self.get_beta(k0)*self.port_mode_3d(x_local, y_local, k0)
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, x_local, y_local, k0) -> np.ndarray:
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.port_mode_3d(x_local, y_local, k0)
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 lumped power boundary condition.
964
-
965
- The lumped port boundary condition assumes a uniform E-field along the "direction" axis.
966
- The port with and height must be provided manually in meters. The height is the size
967
- in the "direction" axis along which the potential is imposed. The width dimension
968
- is orthogonal to that. For a rectangular face its the width and for a cyllindrical face
969
- its the circumpherance.
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, GeoSurface): The port surface
973
- port_number (int): The port number
974
- width (float): The port width (meters).
975
- height (float): The port height (meters).
976
- direction (Axis): The port direction as an Axis object (em.Axis(..) or em.ZAX)
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.material: Material = material
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.material.cond
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]: _description_
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=True, pickable=True)
332
- self._plot.add_mesh(self.mesh(obj), *args, **kwargs)
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 | None = None) -> None:
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
@@ -22,3 +22,4 @@ class PVDisplaySettings:
22
22
  self.background_bottom: str = "#c0d2e8"
23
23
  self.background_top: str = "#ffffff"
24
24
  self.grid_line_color: str = "#8e8e8e"
25
+ self.z_boost: float = 1e-6
@@ -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.7) for sel in selections]
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: cudss.AlgType):
96
- self.RALG = alg_type
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),
@@ -15,6 +15,7 @@
15
15
  # You should have received a copy of the GNU General Public License
16
16
  # along with this program; if not, see
17
17
  # <https://www.gnu.org/licenses/>.
18
+
18
19
  from __future__ import annotations
19
20
  import os
20
21
  import sys
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
- try:
48
- from .solve_interfaces.pardiso_interface import PardisoInterface
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], eigen_vectors: list[np.ndarray], tol=-3) -> tuple[list[complex], list[np.ndarray]]:
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, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
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, target_k0: float = 0.0, which: str = 'LM', sign: float = 1.):
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, permc_spec='MMD_AT_PLUS_A', diag_pivot_thresh=0.001, options=self.options) # ty: ignore
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. Solvers will be checked on validity for the given problem.
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. Solvers will be checked on validity for the given problem.
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 for eigenmode problems. Defaults to False.
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, ndof_solve=Asel.shape[0], nnz_solve=Asel.nnz, simtime=simtime, solver=str(solver), sorter='None', precon='None')
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
@@ -0,0 +1,4 @@
1
+
2
+ from ._emerge.solver import Solver, EigSolver, Preconditioner, Sorter
3
+ from ._emerge.geometry import GeoVolume, GeoSurface, GeoEdge, GeoPoint, _FacePointer
4
+ from ._emerge.physics.microwave.microwave_bc import RobinBC, PortBC
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.5)
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)