emerge 0.5.2__py3-none-any.whl → 0.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of emerge might be problematic. Click here for more details.
- emerge/_emerge/bc.py +3 -12
- emerge/_emerge/const.py +5 -0
- emerge/_emerge/elements/nedleg2.py +2 -2
- emerge/_emerge/geo/pcb.py +110 -13
- emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
- emerge/_emerge/geometry.py +1 -1
- emerge/_emerge/logsettings.py +12 -13
- emerge/_emerge/material.py +4 -0
- emerge/_emerge/mth/integrals.py +1 -1
- emerge/_emerge/physics/microwave/adaptive_freq.py +1 -5
- emerge/_emerge/physics/microwave/assembly/assembler.py +62 -39
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
- emerge/_emerge/physics/microwave/microwave_3d.py +33 -26
- emerge/_emerge/physics/microwave/microwave_bc.py +97 -27
- emerge/_emerge/physics/microwave/microwave_data.py +3 -5
- emerge/_emerge/physics/microwave/sc.py +26 -26
- emerge/_emerge/physics/microwave/simjob.py +8 -3
- emerge/_emerge/simmodel.py +12 -8
- emerge/_emerge/simulation_data.py +5 -1
- emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
- emerge/_emerge/solver.py +285 -107
- emerge/cli.py +1 -1
- emerge/lib.py +2 -5
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/RECORD +28 -26
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/solver.py
CHANGED
|
@@ -17,44 +17,126 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
|
-
from scipy.sparse import
|
|
20
|
+
from scipy.sparse import csr_matrix # type: ignore
|
|
21
21
|
from scipy.sparse.csgraph import reverse_cuthill_mckee # type: ignore
|
|
22
22
|
from scipy.sparse.linalg import bicgstab, gmres, gcrotmk, eigs, splu # type: ignore
|
|
23
23
|
from scipy.linalg import eig # type: ignore
|
|
24
24
|
from scipy import sparse # type: ignore
|
|
25
|
-
from dataclasses import dataclass
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
26
|
import numpy as np
|
|
27
27
|
from loguru import logger
|
|
28
28
|
import platform
|
|
29
29
|
import time
|
|
30
|
-
from typing import Literal
|
|
30
|
+
from typing import Literal, Callable
|
|
31
31
|
from enum import Enum
|
|
32
32
|
|
|
33
33
|
_PARDISO_AVAILABLE = False
|
|
34
34
|
_UMFPACK_AVAILABLE = False
|
|
35
|
+
_CUDSS_AVAILABLE = False
|
|
35
36
|
|
|
36
37
|
""" Check if the PC runs on a non-ARM architechture
|
|
37
38
|
If so, attempt to import PyPardiso (if its installed)
|
|
38
39
|
"""
|
|
39
40
|
|
|
41
|
+
|
|
42
|
+
############################################################
|
|
43
|
+
# PARDISO #
|
|
44
|
+
############################################################
|
|
45
|
+
|
|
40
46
|
if 'arm' not in platform.processor():
|
|
41
47
|
try:
|
|
42
48
|
from .solve_interfaces.pardiso_interface import PardisoInterface
|
|
43
49
|
_PARDISO_AVAILABLE = True
|
|
44
|
-
except ModuleNotFoundError
|
|
50
|
+
except ModuleNotFoundError:
|
|
45
51
|
logger.info('Pardiso not found, defaulting to SuperLU')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
############################################################
|
|
55
|
+
# UMFPACK #
|
|
56
|
+
############################################################
|
|
57
|
+
|
|
58
|
+
|
|
46
59
|
try:
|
|
47
60
|
import scikits.umfpack as um # type: ignore
|
|
48
61
|
_UMFPACK_AVAILABLE = True
|
|
49
|
-
except ModuleNotFoundError
|
|
62
|
+
except ModuleNotFoundError:
|
|
50
63
|
logger.debug('UMFPACK not found, defaulting to SuperLU')
|
|
51
64
|
|
|
52
65
|
|
|
66
|
+
############################################################
|
|
67
|
+
# CUDSS #
|
|
68
|
+
############################################################
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
from .solve_interfaces.cudss_interface import CuDSSInterface
|
|
72
|
+
_CUDSS_AVAILABLE = True
|
|
73
|
+
except ModuleNotFoundError:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
############################################################
|
|
77
|
+
# SOLVE REPORT #
|
|
78
|
+
############################################################
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class SolveReport:
|
|
82
|
+
simtime: float = -1.0
|
|
83
|
+
jobid: int = -1
|
|
84
|
+
ndof: int = -1
|
|
85
|
+
nnz: int = -1
|
|
86
|
+
ndof_solve: int = -1
|
|
87
|
+
nnz_solve: int = -1
|
|
88
|
+
exit_code: int = 0
|
|
89
|
+
solver: str = 'None'
|
|
90
|
+
sorter: str = 'None'
|
|
91
|
+
precon: str = 'None'
|
|
92
|
+
aux: dict[str, str] = field(default_factory=dict)
|
|
93
|
+
|
|
94
|
+
def add(self, **kwargs: str):
|
|
95
|
+
for key, value in kwargs.items():
|
|
96
|
+
self.aux[key] = str(value)
|
|
97
|
+
|
|
98
|
+
def pretty_print(self, print_cal: Callable | None = None):
|
|
99
|
+
if print_cal is None:
|
|
100
|
+
print_cal = print
|
|
101
|
+
# Set column widths
|
|
102
|
+
col1_width = 22 # Wider key column
|
|
103
|
+
col2_width = 40 # Value column
|
|
104
|
+
total_width = col1_width + col2_width + 5 # +5 for borders/padding
|
|
105
|
+
|
|
106
|
+
def row(key, val):
|
|
107
|
+
val_str = f"{val:.4f}" if isinstance(val, float) else str(val)
|
|
108
|
+
print_cal(f"| {key:<{col1_width}} | {val_str:<{col2_width}} |") # ty: ignore
|
|
109
|
+
|
|
110
|
+
border = "+" + "-" * (col1_width + 2) + "+" + "-" * (col2_width + 2) + "+"
|
|
111
|
+
|
|
112
|
+
print_cal(border)
|
|
113
|
+
print_cal(f"| {'FEM Solve Report':^{total_width - 2}} |")
|
|
114
|
+
print_cal(border)
|
|
115
|
+
row("Solver", self.solver)
|
|
116
|
+
row("Sorter", self.sorter)
|
|
117
|
+
row("Preconditioner", self.precon)
|
|
118
|
+
row("Job ID", self.jobid)
|
|
119
|
+
row("Sim Time (s)", self.simtime)
|
|
120
|
+
row("DOFs (Total)", self.ndof)
|
|
121
|
+
row("NNZ (Total)", self.nnz)
|
|
122
|
+
row("DOFs (Solve)", self.ndof_solve)
|
|
123
|
+
row("NNZ (Solve)", self.nnz_solve)
|
|
124
|
+
row("Exit Code", self.exit_code)
|
|
125
|
+
print_cal(border)
|
|
126
|
+
|
|
127
|
+
if self.aux:
|
|
128
|
+
print_cal(f"| {'Additional Info':^{total_width - 2}} |")
|
|
129
|
+
print_cal(border)
|
|
130
|
+
for k, v in self.aux.items():
|
|
131
|
+
row(str(k), v)
|
|
132
|
+
print_cal(border)
|
|
133
|
+
|
|
53
134
|
############################################################
|
|
54
135
|
# EIGENMODE FILTER ROUTINE #
|
|
55
136
|
############################################################
|
|
56
137
|
|
|
57
|
-
def filter_real_modes(eigvals, eigvecs
|
|
138
|
+
def filter_real_modes(eigvals: np.ndarray, eigvecs: np.ndarray,
|
|
139
|
+
k0: float, ermax: complex, urmax: complex, sign: float) -> tuple[np.ndarray, np.ndarray]:
|
|
58
140
|
"""
|
|
59
141
|
Given arrays of eigenvalues `eigvals` and eigenvectors `eigvecs` (cols of shape (N,)),
|
|
60
142
|
and a free‐space wavenumber k0, return only those eigenpairs whose eigenvalue can
|
|
@@ -174,7 +256,7 @@ class Sorter:
|
|
|
174
256
|
def __str__(self) -> str:
|
|
175
257
|
return f'{self.__class__.__name__}'
|
|
176
258
|
|
|
177
|
-
def sort(self, A:
|
|
259
|
+
def sort(self, A: csr_matrix, b: np.ndarray, reuse_sorting: bool = False) -> tuple[csr_matrix, np.ndarray]:
|
|
178
260
|
return A,b
|
|
179
261
|
|
|
180
262
|
def unsort(self, x: np.ndarray) -> np.ndarray:
|
|
@@ -190,8 +272,8 @@ class Preconditioner:
|
|
|
190
272
|
def __str__(self) -> str:
|
|
191
273
|
return f'{self.__class__.__name__}'
|
|
192
274
|
|
|
193
|
-
def init(self, A:
|
|
194
|
-
|
|
275
|
+
def init(self, A: csr_matrix, b: np.ndarray) -> None:
|
|
276
|
+
raise NotImplementedError('')
|
|
195
277
|
|
|
196
278
|
class Solver:
|
|
197
279
|
""" A generic class representing a solver for the problem Ax=b
|
|
@@ -204,13 +286,22 @@ class Solver:
|
|
|
204
286
|
"""
|
|
205
287
|
real_only: bool = False
|
|
206
288
|
req_sorter: bool = False
|
|
289
|
+
|
|
207
290
|
def __init__(self):
|
|
208
291
|
self.own_preconditioner: bool = False
|
|
209
292
|
|
|
210
293
|
def __str__(self) -> str:
|
|
211
294
|
return f'{self.__class__.__name__}'
|
|
212
295
|
|
|
213
|
-
def
|
|
296
|
+
def duplicate(self) -> Solver:
|
|
297
|
+
return self.__class__()
|
|
298
|
+
|
|
299
|
+
def set_options(self, pivoting_threshold: float | None = None) -> None:
|
|
300
|
+
"""Write generic simulation options to the solver object.
|
|
301
|
+
Options may be ignored depending on the type of solver used."""
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
def solve(self, A: csr_matrix, b: np.ndarray, precon: Preconditioner, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
214
305
|
raise NotImplementedError("This classes Ax=B solver method is not implemented.")
|
|
215
306
|
|
|
216
307
|
def reset(self) -> None:
|
|
@@ -228,13 +319,17 @@ class EigSolver:
|
|
|
228
319
|
"""
|
|
229
320
|
real_only: bool = False
|
|
230
321
|
req_sorter: bool = False
|
|
322
|
+
|
|
231
323
|
def __init__(self):
|
|
232
324
|
self.own_preconditioner: bool = False
|
|
233
325
|
|
|
234
326
|
def __str__(self) -> str:
|
|
235
327
|
return f'{self.__class__.__name__}'
|
|
236
328
|
|
|
237
|
-
def
|
|
329
|
+
def duplicate(self) -> Solver:
|
|
330
|
+
return self.__class__()
|
|
331
|
+
|
|
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.):
|
|
238
333
|
raise NotImplementedError("This classes eigenmdoe solver method is not implemented.")
|
|
239
334
|
|
|
240
335
|
def reset(self) -> None:
|
|
@@ -247,16 +342,6 @@ class EigSolver:
|
|
|
247
342
|
# SORTERS #
|
|
248
343
|
############################################################
|
|
249
344
|
|
|
250
|
-
@dataclass
|
|
251
|
-
class SolveReport:
|
|
252
|
-
solver: str
|
|
253
|
-
sorter: str
|
|
254
|
-
precon: str
|
|
255
|
-
simtime: float
|
|
256
|
-
ndof: int
|
|
257
|
-
nnz: int
|
|
258
|
-
code: int = 0
|
|
259
|
-
|
|
260
345
|
|
|
261
346
|
class ReverseCuthillMckee(Sorter):
|
|
262
347
|
""" Implements the Reverse Cuthill-Mckee sorting."""
|
|
@@ -294,8 +379,8 @@ class ILUPrecon(Preconditioner):
|
|
|
294
379
|
|
|
295
380
|
def init(self, A, b):
|
|
296
381
|
logger.info("Generating ILU Preconditioner")
|
|
297
|
-
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)
|
|
298
|
-
self.M = sparse.linalg.LinearOperator(A.shape, self.ilu.solve)
|
|
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.M = sparse.linalg.LinearOperator(A.shape, self.ilu.solve) # ty: ignore
|
|
299
384
|
|
|
300
385
|
|
|
301
386
|
############################################################
|
|
@@ -316,22 +401,21 @@ class SolverBicgstab(Solver):
|
|
|
316
401
|
convergence = np.linalg.norm((self.A @ xk - self.b))
|
|
317
402
|
logger.info(f'Iteration {convergence:.4f}')
|
|
318
403
|
|
|
319
|
-
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1):
|
|
320
|
-
logger.info(f'
|
|
404
|
+
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
405
|
+
logger.info(f'[ID={id}] Calling BiCGStab.')
|
|
321
406
|
self.A = A
|
|
322
407
|
self.b = b
|
|
323
408
|
if precon.M is not None:
|
|
324
409
|
x, info = bicgstab(A, b, M=precon.M, atol=self.atol, callback=self.callback)
|
|
325
410
|
else:
|
|
326
411
|
x, info = bicgstab(A, b, atol=self.atol, callback=self.callback)
|
|
327
|
-
return x, info
|
|
412
|
+
return x, SolveReport(solver=str(self), exit_code=info)
|
|
328
413
|
|
|
329
414
|
class SolverGCROTMK(Solver):
|
|
330
415
|
""" Implements the GCRO-T(m,k) Iterative solver. """
|
|
331
416
|
def __init__(self):
|
|
332
417
|
super().__init__()
|
|
333
418
|
self.atol = 1e-5
|
|
334
|
-
|
|
335
419
|
self.A: np.ndarray = None
|
|
336
420
|
self.b: np.ndarray = None
|
|
337
421
|
|
|
@@ -339,15 +423,15 @@ class SolverGCROTMK(Solver):
|
|
|
339
423
|
convergence = np.linalg.norm((self.A @ xk - self.b))
|
|
340
424
|
logger.info(f'Iteration {convergence:.4f}')
|
|
341
425
|
|
|
342
|
-
def solve(self, A:
|
|
343
|
-
logger.info(f'Calling GCRO-T(m,k) algorithm
|
|
426
|
+
def solve(self, A: csr_matrix, b: np.ndarray, precon: Preconditioner, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
427
|
+
logger.info(f'[ID={id}] Calling GCRO-T(m,k) algorithm')
|
|
344
428
|
self.A = A
|
|
345
429
|
self.b = b
|
|
346
430
|
if precon.M is not None:
|
|
347
431
|
x, info = gcrotmk(A, b, M=precon.M, atol=self.atol, callback=self.callback)
|
|
348
432
|
else:
|
|
349
433
|
x, info = gcrotmk(A, b, atol=self.atol, callback=self.callback)
|
|
350
|
-
return x, info
|
|
434
|
+
return x, SolveReport(solver=str(self), exit_code=info)
|
|
351
435
|
|
|
352
436
|
class SolverGMRES(Solver):
|
|
353
437
|
""" Implements the GMRES solver. """
|
|
@@ -365,15 +449,15 @@ class SolverGMRES(Solver):
|
|
|
365
449
|
#convergence = np.linalg.norm((self.A @ xk - self.b))
|
|
366
450
|
logger.info(f'Iteration {norm:.4f}')
|
|
367
451
|
|
|
368
|
-
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1):
|
|
369
|
-
logger.info(f'Calling GMRES Function.
|
|
452
|
+
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
453
|
+
logger.info(f'[ID={id}] Calling GMRES Function.')
|
|
370
454
|
self.A = A
|
|
371
455
|
self.b = b
|
|
372
456
|
if precon.M is not None:
|
|
373
457
|
x, info = gmres(A, b, M=precon.M, atol=self.atol, callback=self.callback, callback_type='pr_norm')
|
|
374
458
|
else:
|
|
375
459
|
x, info = gmres(A, b, atol=self.atol, callback=self.callback, restart=500, callback_type='pr_norm')
|
|
376
|
-
return x, info
|
|
460
|
+
return x, SolveReport(solver=str(self), exit_code=info)
|
|
377
461
|
|
|
378
462
|
|
|
379
463
|
############################################################
|
|
@@ -385,6 +469,7 @@ class SolverSuperLU(Solver):
|
|
|
385
469
|
""" Implements Scipi's direct SuperLU solver."""
|
|
386
470
|
req_sorter: bool = False
|
|
387
471
|
real_only: bool = False
|
|
472
|
+
|
|
388
473
|
def __init__(self):
|
|
389
474
|
super().__init__()
|
|
390
475
|
self.atol = 1e-5
|
|
@@ -392,16 +477,29 @@ class SolverSuperLU(Solver):
|
|
|
392
477
|
self.A: np.ndarray = None
|
|
393
478
|
self.b: np.ndarray = None
|
|
394
479
|
self.options: dict[str, str] = dict(SymmetricMode=True, Equil=False, IterRefine='SINGLE')
|
|
480
|
+
self._pivoting_threshold: float = 0.001
|
|
395
481
|
self.lu = None
|
|
396
|
-
|
|
397
|
-
def
|
|
398
|
-
|
|
482
|
+
|
|
483
|
+
def duplicate(self) -> Solver:
|
|
484
|
+
new_solver = self.__class__()
|
|
485
|
+
new_solver._pivoting_threshold = self._pivoting_threshold
|
|
486
|
+
return new_solver
|
|
487
|
+
|
|
488
|
+
def set_options(self,
|
|
489
|
+
pivoting_threshold: float | None = None) -> None:
|
|
490
|
+
if pivoting_threshold is not None:
|
|
491
|
+
self._pivoting_threshold = pivoting_threshold
|
|
492
|
+
|
|
493
|
+
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
494
|
+
logger.info(f'[ID={id}] Calling SuperLU Solver.')
|
|
399
495
|
self.single = True
|
|
400
496
|
if not reuse_factorization:
|
|
401
|
-
self.lu = splu(A, permc_spec='MMD_AT_PLUS_A', relax=0, diag_pivot_thresh=
|
|
497
|
+
self.lu = splu(A, permc_spec='MMD_AT_PLUS_A', relax=0, diag_pivot_thresh=self._pivoting_threshold, options=self.options)
|
|
402
498
|
x = self.lu.solve(b)
|
|
403
|
-
|
|
404
|
-
|
|
499
|
+
aux = {
|
|
500
|
+
"pivoting threshold": str(self._pivoting_threshold)
|
|
501
|
+
}
|
|
502
|
+
return x, SolveReport(solver=str(self), exit_code=0, aux=aux)
|
|
405
503
|
|
|
406
504
|
class SolverUMFPACK(Solver):
|
|
407
505
|
""" Implements the UMFPACK Sparse SP solver."""
|
|
@@ -413,35 +511,52 @@ class SolverUMFPACK(Solver):
|
|
|
413
511
|
self.A: np.ndarray = None
|
|
414
512
|
self.b: np.ndarray = None
|
|
415
513
|
self.umfpack: um.UmfpackContext = um.UmfpackContext('zl')
|
|
416
|
-
self.umfpack.control[um.UMFPACK_PRL] = 0
|
|
417
|
-
self.umfpack.control[um.UMFPACK_IRSTEP] = 2
|
|
418
|
-
self.umfpack.control[um.UMFPACK_STRATEGY] = um.UMFPACK_STRATEGY_SYMMETRIC
|
|
419
|
-
self.umfpack.control[um.UMFPACK_ORDERING] = 3
|
|
420
|
-
self.umfpack.control[um.UMFPACK_PIVOT_TOLERANCE] = 0.001
|
|
421
|
-
self.umfpack.control[um.UMFPACK_SYM_PIVOT_TOLERANCE] = 0.001
|
|
422
|
-
self.umfpack.control[um.UMFPACK_BLOCK_SIZE] = 64
|
|
423
|
-
self.umfpack.control[um.UMFPACK_FIXQ] = -1
|
|
514
|
+
self.umfpack.control[um.UMFPACK_PRL] = 0 # ty: ignore
|
|
515
|
+
self.umfpack.control[um.UMFPACK_IRSTEP] = 2 # ty: ignore
|
|
516
|
+
self.umfpack.control[um.UMFPACK_STRATEGY] = um.UMFPACK_STRATEGY_SYMMETRIC # ty: ignore
|
|
517
|
+
self.umfpack.control[um.UMFPACK_ORDERING] = 3 # ty: ignore
|
|
518
|
+
self.umfpack.control[um.UMFPACK_PIVOT_TOLERANCE] = 0.001 # ty: ignore
|
|
519
|
+
self.umfpack.control[um.UMFPACK_SYM_PIVOT_TOLERANCE] = 0.001 # ty: ignore
|
|
520
|
+
self.umfpack.control[um.UMFPACK_BLOCK_SIZE] = 64 # ty: ignore
|
|
521
|
+
self.umfpack.control[um.UMFPACK_FIXQ] = -1 # ty: ignore
|
|
522
|
+
|
|
523
|
+
# SETTINGS
|
|
524
|
+
self._pivoting_threshold: float = 0.001
|
|
424
525
|
|
|
425
526
|
self.fact_symb: bool = False
|
|
426
527
|
|
|
427
528
|
def reset(self) -> None:
|
|
428
529
|
self.fact_symb = False
|
|
429
|
-
|
|
430
|
-
def
|
|
431
|
-
|
|
530
|
+
|
|
531
|
+
def set_options(self,
|
|
532
|
+
pivoting_threshold: float | None = None) -> None:
|
|
533
|
+
if pivoting_threshold is not None:
|
|
534
|
+
self.umfpack.control[um.UMFPACK_PIVOT_TOLERANCE] = pivoting_threshold # ty: ignore
|
|
535
|
+
self.umfpack.control[um.UMFPACK_SYM_PIVOT_TOLERANCE] = pivoting_threshold # ty: ignore
|
|
536
|
+
self._pivoting_threshold = pivoting_threshold
|
|
537
|
+
|
|
538
|
+
def duplicate(self) -> Solver:
|
|
539
|
+
new_solver = self.__class__()
|
|
540
|
+
new_solver.set_options(pivoting_threshold = self._pivoting_threshold)
|
|
541
|
+
return new_solver
|
|
542
|
+
|
|
543
|
+
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
544
|
+
logger.info(f'[ID={id}] Calling UMFPACK Solver.')
|
|
432
545
|
A.indptr = A.indptr.astype(np.int64)
|
|
433
546
|
A.indices = A.indices.astype(np.int64)
|
|
434
547
|
if self.fact_symb is False:
|
|
435
|
-
logger.debug('Executing symbollic factorization.')
|
|
548
|
+
logger.debug(f'[ID={id}] Executing symbollic factorization.')
|
|
436
549
|
self.umfpack.symbolic(A)
|
|
437
|
-
#self.up.report_symbolic()
|
|
438
550
|
self.fact_symb = True
|
|
439
551
|
if not reuse_factorization:
|
|
440
552
|
#logger.debug('Executing numeric factorization.')
|
|
441
553
|
self.umfpack.numeric(A)
|
|
442
554
|
self.A = A
|
|
443
|
-
x = self.umfpack.solve(um.UMFPACK_A, self.A, b, autoTranspose = False )
|
|
444
|
-
|
|
555
|
+
x = self.umfpack.solve(um.UMFPACK_A, self.A, b, autoTranspose = False ) # ty: ignore
|
|
556
|
+
aux = {
|
|
557
|
+
"Pivoting Threshold": str(self._pivoting_threshold),
|
|
558
|
+
}
|
|
559
|
+
return x, SolveReport(solver=str(self), exit_code=0, aux=aux)
|
|
445
560
|
|
|
446
561
|
class SolverPardiso(Solver):
|
|
447
562
|
""" Implements the PARDISO solver through PyPardiso. """
|
|
@@ -454,11 +569,11 @@ class SolverPardiso(Solver):
|
|
|
454
569
|
self.fact_symb: bool = False
|
|
455
570
|
self.A: np.ndarray = None
|
|
456
571
|
self.b: np.ndarray = None
|
|
457
|
-
|
|
458
|
-
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1):
|
|
459
|
-
logger.info(f'Calling Pardiso Solver
|
|
572
|
+
|
|
573
|
+
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
|
|
574
|
+
logger.info(f'[ID={id}] Calling Pardiso Solver')
|
|
460
575
|
if self.fact_symb is False:
|
|
461
|
-
logger.debug('Executing symbollic factorization.')
|
|
576
|
+
logger.debug(f'[ID={id}] Executing symbollic factorization.')
|
|
462
577
|
self.solver.symbolic(A)
|
|
463
578
|
self.fact_symb = True
|
|
464
579
|
if not reuse_factorization:
|
|
@@ -466,12 +581,42 @@ class SolverPardiso(Solver):
|
|
|
466
581
|
self.A = A
|
|
467
582
|
x, error = self.solver.solve(A, b)
|
|
468
583
|
if error != 0:
|
|
469
|
-
logger.error(f'Terminated with error code {error}')
|
|
584
|
+
logger.error(f'[ID={id}] Terminated with error code {error}')
|
|
470
585
|
logger.error(self.solver.get_error(error))
|
|
471
|
-
raise SimulationError(f'PARDISO Terminated with error code {error}')
|
|
472
|
-
|
|
586
|
+
raise SimulationError(f'[ID={id}] PARDISO Terminated with error code {error}')
|
|
587
|
+
aux = {}
|
|
588
|
+
return x, SolveReport(solver=str(self), exit_code=error, aux=aux)
|
|
473
589
|
|
|
474
590
|
|
|
591
|
+
class CuDSSSolver(Solver):
|
|
592
|
+
real_only = False
|
|
593
|
+
def __init__(self):
|
|
594
|
+
self._cudss = CuDSSInterface()
|
|
595
|
+
self.fact_symb: bool = False
|
|
596
|
+
self.fact_numb: bool = False
|
|
597
|
+
self._cudss._PRES = 2
|
|
598
|
+
|
|
599
|
+
def reset(self) -> None:
|
|
600
|
+
self.fact_symb = False
|
|
601
|
+
self.fact_numb = False
|
|
602
|
+
|
|
603
|
+
def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1):
|
|
604
|
+
logger.info(f'[{id}] Calling cuDSS Solver')
|
|
605
|
+
|
|
606
|
+
if self.fact_symb is False:
|
|
607
|
+
logger.debug('Executing symbollic factorization')
|
|
608
|
+
x = self._cudss.from_symbolic(A,b)
|
|
609
|
+
self.fact_symb = True
|
|
610
|
+
return x, 0
|
|
611
|
+
else:
|
|
612
|
+
if reuse_factorization:
|
|
613
|
+
x = self._cudss.from_solve(b)
|
|
614
|
+
return x, 0
|
|
615
|
+
else:
|
|
616
|
+
x = self._cudss.from_numeric(A,b)
|
|
617
|
+
return x, 0
|
|
618
|
+
|
|
619
|
+
|
|
475
620
|
############################################################
|
|
476
621
|
# DIRECT EIGENMODE SOLVERS #
|
|
477
622
|
############################################################
|
|
@@ -482,12 +627,12 @@ class SolverLAPACK(EigSolver):
|
|
|
482
627
|
super().__init__()
|
|
483
628
|
|
|
484
629
|
def eig(self,
|
|
485
|
-
A:
|
|
486
|
-
B:
|
|
630
|
+
A: csr_matrix | csr_matrix,
|
|
631
|
+
B: csr_matrix | csr_matrix,
|
|
487
632
|
nmodes: int = 6,
|
|
488
|
-
target_k0: float
|
|
633
|
+
target_k0: float = 0,
|
|
489
634
|
which: str = 'LM',
|
|
490
|
-
sign: float = 1.0):
|
|
635
|
+
sign: float = 1.0) -> tuple[np.ndarray, np.ndarray]:
|
|
491
636
|
"""
|
|
492
637
|
Dense solver for A x = λ B x with A = Aᴴ, B = Bᴴ (B may be indefinite).
|
|
493
638
|
|
|
@@ -521,12 +666,12 @@ class SolverARPACK(EigSolver):
|
|
|
521
666
|
super().__init__()
|
|
522
667
|
|
|
523
668
|
def eig(self,
|
|
524
|
-
A:
|
|
525
|
-
B:
|
|
669
|
+
A: csr_matrix | csr_matrix,
|
|
670
|
+
B: csr_matrix | csr_matrix,
|
|
526
671
|
nmodes: int = 6,
|
|
527
672
|
target_k0: float = 0,
|
|
528
673
|
which: str = 'LM',
|
|
529
|
-
sign: float = 1.0):
|
|
674
|
+
sign: float = 1.0) -> tuple[np.ndarray, np.ndarray]:
|
|
530
675
|
logger.info(f'Searching around β = {target_k0:.2f} rad/m')
|
|
531
676
|
sigma = sign*(target_k0**2)
|
|
532
677
|
eigen_values, eigen_modes = eigs(A, k=nmodes, M=B, sigma=sigma, which=which)
|
|
@@ -544,12 +689,13 @@ class SmartARPACK_BMA(EigSolver):
|
|
|
544
689
|
self.energy_limit: float = 1e-4
|
|
545
690
|
|
|
546
691
|
def eig(self,
|
|
547
|
-
A:
|
|
548
|
-
B:
|
|
692
|
+
A: csr_matrix | csr_matrix,
|
|
693
|
+
B: csr_matrix | csr_matrix,
|
|
549
694
|
nmodes: int = 6,
|
|
550
695
|
target_k0: float = 0,
|
|
551
696
|
which: str = 'LM',
|
|
552
|
-
sign: float = 1.):
|
|
697
|
+
sign: float = 1.) -> tuple[np.ndarray, np.ndarray]:
|
|
698
|
+
|
|
553
699
|
logger.info(f'Searching around β = {target_k0:.2f} rad/m')
|
|
554
700
|
qs = np.geomspace(1, self.search_range, self.symmetric_steps)
|
|
555
701
|
tot_eigen_values = []
|
|
@@ -593,12 +739,12 @@ class SmartARPACK(EigSolver):
|
|
|
593
739
|
self.energy_limit: float = 1e-4
|
|
594
740
|
|
|
595
741
|
def eig(self,
|
|
596
|
-
A:
|
|
597
|
-
B:
|
|
742
|
+
A: csr_matrix | csr_matrix,
|
|
743
|
+
B: csr_matrix | csr_matrix,
|
|
598
744
|
nmodes: int = 6,
|
|
599
745
|
target_k0: float = 0,
|
|
600
746
|
which: str = 'LM',
|
|
601
|
-
sign: float = 1.):
|
|
747
|
+
sign: float = 1.) -> tuple[np.ndarray, np.ndarray]:
|
|
602
748
|
logger.info(f'Searching around β = {target_k0:.2f} rad/m')
|
|
603
749
|
qs = np.geomspace(1, self.search_range, self.symmetric_steps)
|
|
604
750
|
tot_eigen_values = []
|
|
@@ -647,18 +793,19 @@ class EMSolver(Enum):
|
|
|
647
793
|
ARPACK = 5
|
|
648
794
|
SMART_ARPACK = 6
|
|
649
795
|
SMART_ARPACK_BMA = 7
|
|
796
|
+
CUDSS = 8
|
|
650
797
|
|
|
651
|
-
def get_solver(self) -> Solver | EigSolver:
|
|
798
|
+
def get_solver(self) -> Solver | EigSolver | None:
|
|
652
799
|
if self==EMSolver.SUPERLU:
|
|
653
800
|
return SolverSuperLU()
|
|
654
801
|
elif self==EMSolver.UMFPACK:
|
|
655
802
|
if _UMFPACK_AVAILABLE is False:
|
|
656
|
-
return
|
|
803
|
+
return None
|
|
657
804
|
else:
|
|
658
805
|
return SolverUMFPACK()
|
|
659
806
|
elif self==EMSolver.PARDISO:
|
|
660
807
|
if _PARDISO_AVAILABLE is False:
|
|
661
|
-
return
|
|
808
|
+
return None
|
|
662
809
|
else:
|
|
663
810
|
return SolverPardiso()
|
|
664
811
|
elif self==EMSolver.LAPACK:
|
|
@@ -669,7 +816,12 @@ class EMSolver(Enum):
|
|
|
669
816
|
return SmartARPACK()
|
|
670
817
|
elif self==EMSolver.SMART_ARPACK_BMA:
|
|
671
818
|
return SmartARPACK_BMA()
|
|
672
|
-
|
|
819
|
+
elif self==EMSolver.CUDSS:
|
|
820
|
+
if _CUDSS_AVAILABLE is False:
|
|
821
|
+
return None
|
|
822
|
+
else:
|
|
823
|
+
return CuDSSSolver()
|
|
824
|
+
raise ValueError(f'An unsupported Enum case has been reached: {self}')
|
|
673
825
|
|
|
674
826
|
############################################################
|
|
675
827
|
# SOLVE ROUTINE #
|
|
@@ -687,6 +839,7 @@ class SolveRoutine:
|
|
|
687
839
|
self.sorter: Sorter = ReverseCuthillMckee()
|
|
688
840
|
self.precon: Preconditioner = ILUPrecon()
|
|
689
841
|
self.solvers: dict[EMSolver, Solver | EigSolver] = {slv: slv.get_solver() for slv in EMSolver}
|
|
842
|
+
self.solvers = {key: solver for key, solver in self.solvers.items() if solver is not None}
|
|
690
843
|
|
|
691
844
|
self.parallel: Literal['SI','MT','MP'] = 'SI'
|
|
692
845
|
self.smart_search: bool = False
|
|
@@ -697,6 +850,7 @@ class SolveRoutine:
|
|
|
697
850
|
self.use_preconditioner: bool = False
|
|
698
851
|
self.use_direct: bool = True
|
|
699
852
|
|
|
853
|
+
|
|
700
854
|
def __str__(self) -> str:
|
|
701
855
|
return 'SolveRoutine()'
|
|
702
856
|
|
|
@@ -753,6 +907,8 @@ class SolveRoutine:
|
|
|
753
907
|
new_routine.parallel = self.parallel
|
|
754
908
|
new_routine.smart_search = self.smart_search
|
|
755
909
|
new_routine.forced_solver = self.forced_solver
|
|
910
|
+
for tpe, solver in self.solvers.items():
|
|
911
|
+
new_routine.solvers[tpe] = solver.duplicate()
|
|
756
912
|
return new_routine
|
|
757
913
|
|
|
758
914
|
def set_solver(self, *solvers: EMSolver | EigSolver | Solver) -> None:
|
|
@@ -779,8 +935,9 @@ class SolveRoutine:
|
|
|
779
935
|
else:
|
|
780
936
|
self.disabled_solver.append(solver.__class__)
|
|
781
937
|
|
|
782
|
-
def
|
|
783
|
-
parallel: Literal['SI','MT','MP'] = 'SI',
|
|
938
|
+
def _configure_routine(self,
|
|
939
|
+
parallel: Literal['SI','MT','MP'] = 'SI',
|
|
940
|
+
smart_search: bool = False) -> SolveRoutine:
|
|
784
941
|
"""Configure the solver with the given settings
|
|
785
942
|
|
|
786
943
|
Args:
|
|
@@ -798,6 +955,20 @@ class SolveRoutine:
|
|
|
798
955
|
self.smart_search = smart_search
|
|
799
956
|
return self
|
|
800
957
|
|
|
958
|
+
def configure(self,
|
|
959
|
+
pivoting_threshold: float | None = None) -> None:
|
|
960
|
+
"""Sets general user configurations for all solvers.
|
|
961
|
+
|
|
962
|
+
Args:
|
|
963
|
+
pivoting_threshold (float | None, optional):
|
|
964
|
+
The diagonal pivoting threshold used in direct solvers. Standard values are 0.001.
|
|
965
|
+
In simulations with a very low surface impedance (such as with copper walls) a much
|
|
966
|
+
lower pivoting threshold is desired.
|
|
967
|
+
"""
|
|
968
|
+
for solver in self.solvers.values():
|
|
969
|
+
if isinstance(solver, Solver):
|
|
970
|
+
solver.set_options(pivoting_threshold=pivoting_threshold)
|
|
971
|
+
|
|
801
972
|
def reset(self) -> None:
|
|
802
973
|
"""Reset all solver states"""
|
|
803
974
|
for solver in self.solvers.values():
|
|
@@ -808,7 +979,7 @@ class SolveRoutine:
|
|
|
808
979
|
self.forced_solver = []
|
|
809
980
|
self.disabled_solver = []
|
|
810
981
|
|
|
811
|
-
def _get_solver(self, A:
|
|
982
|
+
def _get_solver(self, A: csr_matrix, b: np.ndarray) -> Solver:
|
|
812
983
|
"""Returns the relevant Solver object given a certain matrix and source vector
|
|
813
984
|
|
|
814
985
|
This is the default implementation for the SolveRoutine Class.
|
|
@@ -829,7 +1000,7 @@ class SolveRoutine:
|
|
|
829
1000
|
return self.pick_solver(A,b)
|
|
830
1001
|
|
|
831
1002
|
|
|
832
|
-
def pick_solver(self, A:
|
|
1003
|
+
def pick_solver(self, A: csr_matrix, b: np.ndarray) -> Solver:
|
|
833
1004
|
"""Returns the relevant Solver object given a certain matrix and source vector
|
|
834
1005
|
|
|
835
1006
|
This is the default implementation for the SolveRoutine Class.
|
|
@@ -844,7 +1015,7 @@ class SolveRoutine:
|
|
|
844
1015
|
"""
|
|
845
1016
|
return self._try_solver(EMSolver.SUPERLU)
|
|
846
1017
|
|
|
847
|
-
def _get_eig_solver(self, A:
|
|
1018
|
+
def _get_eig_solver(self, A: csr_matrix, b: csr_matrix, direct: bool | None = None) -> EigSolver:
|
|
848
1019
|
"""Returns the relevant eigenmode Solver object given a certain matrix and source vector
|
|
849
1020
|
|
|
850
1021
|
This is the default implementation for the SolveRoutine Class.
|
|
@@ -866,7 +1037,7 @@ class SolveRoutine:
|
|
|
866
1037
|
else:
|
|
867
1038
|
return self.solvers[EMSolver.SMART_ARPACK] # type: ignore
|
|
868
1039
|
|
|
869
|
-
def _get_eig_solver_bma(self, A:
|
|
1040
|
+
def _get_eig_solver_bma(self, A: csr_matrix, b: csr_matrix, direct: bool | None = None) -> EigSolver:
|
|
870
1041
|
"""Returns the relevant eigenmode Solver object given a certain matrix and source vector
|
|
871
1042
|
|
|
872
1043
|
This is the default implementation for the SolveRoutine Class.
|
|
@@ -889,7 +1060,7 @@ class SolveRoutine:
|
|
|
889
1060
|
else:
|
|
890
1061
|
return self.solvers[EMSolver.ARPACK] # type: ignore
|
|
891
1062
|
|
|
892
|
-
def solve(self, A:
|
|
1063
|
+
def solve(self, A: csr_matrix | csr_matrix,
|
|
893
1064
|
b: np.ndarray,
|
|
894
1065
|
solve_ids: np.ndarray,
|
|
895
1066
|
reuse: bool = False,
|
|
@@ -900,7 +1071,7 @@ class SolveRoutine:
|
|
|
900
1071
|
The solve routine will go through the required steps defined in the routine to tackle the problme.
|
|
901
1072
|
|
|
902
1073
|
Args:
|
|
903
|
-
A (np.ndarray |
|
|
1074
|
+
A (np.ndarray | csr_matrix | csr_matrix): The (Sparse) matrix
|
|
904
1075
|
b (np.ndarray): The source vector
|
|
905
1076
|
solve_ids (np.ndarray): A vector of ids for which to solve the problem. For EM problems this
|
|
906
1077
|
implies all non-PEC degrees of freedom.
|
|
@@ -909,7 +1080,7 @@ class SolveRoutine:
|
|
|
909
1080
|
Returns:
|
|
910
1081
|
np.ndarray: The resultant solution.
|
|
911
1082
|
"""
|
|
912
|
-
solver = self._get_solver(A, b)
|
|
1083
|
+
solver: Solver = self._get_solver(A, b)
|
|
913
1084
|
|
|
914
1085
|
NF = A.shape[0]
|
|
915
1086
|
NS = solve_ids.shape[0]
|
|
@@ -920,10 +1091,10 @@ class SolveRoutine:
|
|
|
920
1091
|
bsel = b[solve_ids]
|
|
921
1092
|
nnz = Asel.nnz
|
|
922
1093
|
|
|
923
|
-
logger.debug(f'
|
|
1094
|
+
logger.debug(f'[ID={id}] Removed {NF-NS} prescribed DOFs ({NS:,} left, {nnz:,}≠0)')
|
|
924
1095
|
|
|
925
1096
|
if solver.real_only:
|
|
926
|
-
logger.debug('
|
|
1097
|
+
logger.debug(f'[ID={id}] Converting to real matrix')
|
|
927
1098
|
Asel, bsel = complex_to_real_block(Asel, bsel)
|
|
928
1099
|
|
|
929
1100
|
# SORT
|
|
@@ -943,11 +1114,11 @@ class SolveRoutine:
|
|
|
943
1114
|
|
|
944
1115
|
start = time.time()
|
|
945
1116
|
|
|
946
|
-
x_solved,
|
|
1117
|
+
x_solved, report = solver.solve(Asorted, bsorted, self.precon, reuse_factorization=reuse, id=id)
|
|
947
1118
|
end = time.time()
|
|
948
1119
|
simtime = end-start
|
|
949
|
-
logger.info(f'
|
|
950
|
-
logger.debug(f'
|
|
1120
|
+
logger.info(f'[ID={id}] Elapsed time taken: {simtime:.3f} seconds')
|
|
1121
|
+
logger.debug(f'[ID={id}] O(N²) performance = {(NS**2)/((end-start+1e-6)*1e6):.3f} MDoF/s')
|
|
951
1122
|
|
|
952
1123
|
if self.use_sorter and solver.req_sorter:
|
|
953
1124
|
x = self.sorter.unsort(x_solved)
|
|
@@ -955,20 +1126,27 @@ class SolveRoutine:
|
|
|
955
1126
|
x = x_solved
|
|
956
1127
|
|
|
957
1128
|
if solver.real_only:
|
|
958
|
-
logger.debug('
|
|
1129
|
+
logger.debug(f'[ID={id}] Converting back to complex matrix')
|
|
959
1130
|
x = real_to_complex_block(x)
|
|
960
1131
|
|
|
961
1132
|
solution = np.zeros((NF,), dtype=np.complex128)
|
|
962
1133
|
|
|
963
1134
|
solution[solve_ids] = x
|
|
964
1135
|
|
|
965
|
-
logger.debug('Solver complete!')
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1136
|
+
logger.debug(f'[ID={id}] Solver complete!')
|
|
1137
|
+
report.jobid = id
|
|
1138
|
+
report.sorter = str(sorter)
|
|
1139
|
+
report.simtime = simtime
|
|
1140
|
+
report.nnz = A.nnz
|
|
1141
|
+
report.ndof = b.shape[0]
|
|
1142
|
+
report.nnz_solve = Asorted.nnz
|
|
1143
|
+
report.ndof_solve = bsorted.shape[0]
|
|
1144
|
+
report.precon = precon
|
|
1145
|
+
|
|
1146
|
+
return solution, report
|
|
969
1147
|
|
|
970
1148
|
def eig_boundary(self,
|
|
971
|
-
A:
|
|
1149
|
+
A: csr_matrix | csr_matrix,
|
|
972
1150
|
B: np.ndarray,
|
|
973
1151
|
solve_ids: np.ndarray,
|
|
974
1152
|
nmodes: int = 6,
|
|
@@ -981,8 +1159,8 @@ class SolveRoutine:
|
|
|
981
1159
|
For generalized eigenvalue problems of boundary mode analysis studies, the equation is: Ae = -β²Be
|
|
982
1160
|
|
|
983
1161
|
Args:
|
|
984
|
-
A (
|
|
985
|
-
B (
|
|
1162
|
+
A (csr_matrix): The Stiffness matrix
|
|
1163
|
+
B (csr_matrix): The mass matrix
|
|
986
1164
|
solve_ids (np.ndarray): The free nodes (non PEC)
|
|
987
1165
|
nmodes (int): The number of modes to solve for. Defaults to 6
|
|
988
1166
|
direct (bool): If the direct solver should be used (always). Defaults to False
|
|
@@ -1000,7 +1178,7 @@ class SolveRoutine:
|
|
|
1000
1178
|
NF = A.shape[0]
|
|
1001
1179
|
NS = solve_ids.shape[0]
|
|
1002
1180
|
|
|
1003
|
-
logger.debug(f'
|
|
1181
|
+
logger.debug(f'Removing {NF-NS} prescribed DOFs ({NS} left)')
|
|
1004
1182
|
|
|
1005
1183
|
Asel = A[np.ix_(solve_ids, solve_ids)]
|
|
1006
1184
|
Bsel = B[np.ix_(solve_ids, solve_ids)]
|
|
@@ -1010,10 +1188,10 @@ class SolveRoutine:
|
|
|
1010
1188
|
end = time.time()
|
|
1011
1189
|
|
|
1012
1190
|
simtime = end-start
|
|
1013
|
-
return eigen_values, eigen_modes, SolveReport(str(solver), 'None', 'None'
|
|
1191
|
+
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')
|
|
1014
1192
|
|
|
1015
1193
|
def eig(self,
|
|
1016
|
-
A:
|
|
1194
|
+
A: csr_matrix | csr_matrix,
|
|
1017
1195
|
B: np.ndarray,
|
|
1018
1196
|
solve_ids: np.ndarray,
|
|
1019
1197
|
nmodes: int = 6,
|
|
@@ -1024,8 +1202,8 @@ class SolveRoutine:
|
|
|
1024
1202
|
Find the eigenmodes for the system Ax = λBx for a boundary mode problem
|
|
1025
1203
|
|
|
1026
1204
|
Args:
|
|
1027
|
-
A (
|
|
1028
|
-
B (
|
|
1205
|
+
A (csr_matrix): The Stiffness matrix
|
|
1206
|
+
B (csr_matrix): The mass matrix
|
|
1029
1207
|
solve_ids (np.ndarray): The free nodes (non PEC)
|
|
1030
1208
|
nmodes (int): The number of modes to solve for. Defaults to 6
|
|
1031
1209
|
direct (bool): If the direct solver should be used (always). Defaults to False
|
|
@@ -1040,7 +1218,7 @@ class SolveRoutine:
|
|
|
1040
1218
|
NF = A.shape[0]
|
|
1041
1219
|
NS = solve_ids.shape[0]
|
|
1042
1220
|
|
|
1043
|
-
logger.debug(f'
|
|
1221
|
+
logger.debug(f'Removing {NF-NS} prescribed DOFs ({NS} left)')
|
|
1044
1222
|
|
|
1045
1223
|
Asel = A[np.ix_(solve_ids, solve_ids)]
|
|
1046
1224
|
Bsel = B[np.ix_(solve_ids, solve_ids)]
|
|
@@ -1055,7 +1233,7 @@ class SolveRoutine:
|
|
|
1055
1233
|
for i in range(Nsols):
|
|
1056
1234
|
sols[solve_ids,i] = eigen_modes[:,i]
|
|
1057
1235
|
|
|
1058
|
-
return eigen_values, sols, SolveReport(str(solver), 'None', 'None'
|
|
1236
|
+
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')
|
|
1059
1237
|
|
|
1060
1238
|
|
|
1061
1239
|
class AutomaticRoutine(SolveRoutine):
|