emerge 0.4.11__py3-none-any.whl → 0.5.1__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 +15 -8
- emerge/_emerge/bc.py +41 -2
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/pcb.py +49 -11
- emerge/_emerge/howto.py +2 -2
- emerge/_emerge/logsettings.py +83 -5
- emerge/_emerge/mesh3d.py +30 -12
- emerge/_emerge/mth/common_functions.py +28 -1
- emerge/_emerge/mth/integrals.py +25 -3
- emerge/_emerge/mth/optimized.py +126 -33
- emerge/_emerge/mth/pairing.py +97 -0
- emerge/_emerge/periodic.py +22 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +129 -155
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +35 -3
- emerge/_emerge/physics/microwave/assembly/periodicbc.py +130 -0
- emerge/_emerge/physics/microwave/microwave_3d.py +3 -3
- emerge/_emerge/physics/microwave/microwave_bc.py +5 -4
- emerge/_emerge/physics/microwave/microwave_data.py +2 -2
- emerge/_emerge/physics/microwave/sparam.py +2 -2
- emerge/_emerge/projects/_gen_base.txt +1 -1
- emerge/_emerge/simmodel.py +137 -126
- emerge/_emerge/solve_interfaces/pardiso_interface.py +468 -0
- emerge/_emerge/solver.py +102 -31
- emerge/lib.py +276 -41
- {emerge-0.4.11.dist-info → emerge-0.5.1.dist-info}/METADATA +1 -1
- {emerge-0.4.11.dist-info → emerge-0.5.1.dist-info}/RECORD +29 -27
- emerge/_emerge/pardiso/pardiso_solver.py +0 -455
- {emerge-0.4.11.dist-info → emerge-0.5.1.dist-info}/WHEEL +0 -0
- {emerge-0.4.11.dist-info → emerge-0.5.1.dist-info}/entry_points.txt +0 -0
- {emerge-0.4.11.dist-info → emerge-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
|
|
2
|
+
# EMerge is an open source Python based FEM EM simulation module.
|
|
3
|
+
# Copyright (C) 2025 Robert Fennis.
|
|
4
|
+
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, see
|
|
17
|
+
# <https://www.gnu.org/licenses/>.
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import ctypes
|
|
22
|
+
import re
|
|
23
|
+
import site
|
|
24
|
+
from ctypes.util import find_library
|
|
25
|
+
from enum import Enum
|
|
26
|
+
import numpy as np
|
|
27
|
+
from scipy import sparse
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Iterable, Iterator
|
|
30
|
+
import pickle
|
|
31
|
+
from loguru import logger
|
|
32
|
+
|
|
33
|
+
############################################################
|
|
34
|
+
# ERROR CODES #
|
|
35
|
+
############################################################
|
|
36
|
+
|
|
37
|
+
PARDISO_ERROR_CODES = {
|
|
38
|
+
0: "No error.",
|
|
39
|
+
-1: "Input inconsistent.",
|
|
40
|
+
-2: "Not enough memory.",
|
|
41
|
+
-3: "Reordering problem.",
|
|
42
|
+
-4: "Zero pivot, numerical fac. or iterative refinement problem.",
|
|
43
|
+
-5: "Unclassified (internal) error.",
|
|
44
|
+
-6: "Preordering failed (matrix types 11(real and nonsymmetric), 13(complex and nonsymmetric) only).",
|
|
45
|
+
-7: "Diagonal Matrix problem.",
|
|
46
|
+
-8: "32-bit integer overflow problem.",
|
|
47
|
+
-10: "No license file pardiso.lic found.",
|
|
48
|
+
-11: "License is expired.",
|
|
49
|
+
-12: "Wrong username or hostname.",
|
|
50
|
+
-100: "Reached maximum number of Krylov-subspace iteration in iterative solver.",
|
|
51
|
+
-101: "No sufficient convergence in Krylov-subspace iteration within 25 iterations.",
|
|
52
|
+
-102: "Error in Krylov-subspace iteration.",
|
|
53
|
+
-103: "Bread-Down in Krylov-subspace iteration",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
############################################################
|
|
58
|
+
# FINDING THE PARDISO DLL FILES #
|
|
59
|
+
############################################################
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
#: Environment variable that overrides automatic searching
|
|
63
|
+
ENV_VAR = "PYPARDISO_MKL_RT"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _candidate_dirs() -> Iterable[Path]:
|
|
67
|
+
"""Return directories in which to look for MKL."""
|
|
68
|
+
# Ordered from most to least likely
|
|
69
|
+
seen: set[Path] = set()
|
|
70
|
+
|
|
71
|
+
for p in ( # likely “local” env first
|
|
72
|
+
Path(sys.prefix),
|
|
73
|
+
Path(getattr(sys, "base_prefix", sys.prefix)),
|
|
74
|
+
Path(site.USER_BASE),
|
|
75
|
+
*(Path(x) for x in os.getenv("LD_LIBRARY_PATH", "").split(":") if x),
|
|
76
|
+
):
|
|
77
|
+
if p not in seen:
|
|
78
|
+
seen.add(p)
|
|
79
|
+
yield p
|
|
80
|
+
|
|
81
|
+
def _search_mkl() -> Iterator[Path]:
|
|
82
|
+
"""Yield candidate MKL library paths, shortest first."""
|
|
83
|
+
pattern = {
|
|
84
|
+
"win32": r"^mkl_rt.*\.dll$",
|
|
85
|
+
"darwin": r"^libmkl_rt(\.\d+)*\.dylib$",
|
|
86
|
+
"linux": r"^libmkl_rt(\.so(\.\d+)*)?$",
|
|
87
|
+
}.get(sys.platform, r"^libmkl_rt")
|
|
88
|
+
|
|
89
|
+
regex = re.compile(pattern, re.IGNORECASE)
|
|
90
|
+
|
|
91
|
+
for base in _candidate_dirs():
|
|
92
|
+
for path in sorted(base.rglob("**/*mkl_rt*"), key=lambda p: len(str(p))):
|
|
93
|
+
if regex.match(path.name):
|
|
94
|
+
yield path
|
|
95
|
+
|
|
96
|
+
def cache_path_result(tag: str, compute_fn, force: bool = False):
|
|
97
|
+
"""
|
|
98
|
+
Retrieve a cached Path object or compute it and store it.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
tag : str
|
|
103
|
+
Cache key.
|
|
104
|
+
compute_fn : callable
|
|
105
|
+
Callable that returns a Path.
|
|
106
|
+
force : bool
|
|
107
|
+
If True, bypass and overwrite the cache.
|
|
108
|
+
"""
|
|
109
|
+
cache_dir = Path(__file__).parent / "__pycache__"
|
|
110
|
+
cache_dir.mkdir(exist_ok=True)
|
|
111
|
+
cache_file = cache_dir / f"{tag}.pkl"
|
|
112
|
+
|
|
113
|
+
if not force and cache_file.exists():
|
|
114
|
+
with open(cache_file, "rb") as f:
|
|
115
|
+
filename = pickle.load(f)
|
|
116
|
+
print(f"Using cached MKL file: {filename}")
|
|
117
|
+
return filename
|
|
118
|
+
|
|
119
|
+
result = compute_fn()
|
|
120
|
+
with open(cache_file, "wb") as f:
|
|
121
|
+
pickle.dump(result, f)
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
def search_mkl() -> str:
|
|
125
|
+
"""Searches for the file path of the PARDISO MKL executable
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
str: The filepath
|
|
129
|
+
"""
|
|
130
|
+
logger.debug('Searching for MKL executable...')
|
|
131
|
+
for candidate in _search_mkl():
|
|
132
|
+
try:
|
|
133
|
+
ctypes.CDLL(candidate)
|
|
134
|
+
except OSError:
|
|
135
|
+
continue # try the next one
|
|
136
|
+
logger.debug(f'Executable found: {candidate}')
|
|
137
|
+
return candidate
|
|
138
|
+
|
|
139
|
+
def load_mkl() -> ctypes.CDLL:
|
|
140
|
+
"""Locate and load **mkl_rt**; raise ImportError on failure."""
|
|
141
|
+
# 1 explicit override
|
|
142
|
+
override = os.getenv(ENV_VAR)
|
|
143
|
+
if override:
|
|
144
|
+
try:
|
|
145
|
+
return ctypes.CDLL(override)
|
|
146
|
+
except OSError as e:
|
|
147
|
+
raise ImportError(f"{override!r} could not be loaded: {e}") from None
|
|
148
|
+
|
|
149
|
+
# 2 system utility (cheap)
|
|
150
|
+
lib = find_library("mkl_rt")
|
|
151
|
+
if lib:
|
|
152
|
+
try:
|
|
153
|
+
return ctypes.CDLL(lib)
|
|
154
|
+
except OSError:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
# 2 system utility (cheap)
|
|
158
|
+
lib = find_library("mkl_rt.1")
|
|
159
|
+
if lib:
|
|
160
|
+
try:
|
|
161
|
+
return ctypes.CDLL(lib)
|
|
162
|
+
except OSError:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
# 3 filesystem walk (expensive, but last resort)
|
|
166
|
+
try:
|
|
167
|
+
filename = cache_path_result('mkl_file',search_mkl)
|
|
168
|
+
return ctypes.CDLL(filename)
|
|
169
|
+
except OSError:
|
|
170
|
+
logger.warning('File name {filename} is no longer valid. Re-executing MKL search.')
|
|
171
|
+
filename = cache_path_result('mkl_file',search_mkl,force=True)
|
|
172
|
+
return ctypes.CDLL(filename)
|
|
173
|
+
|
|
174
|
+
raise ImportError(
|
|
175
|
+
"Shared library *mkl_rt* not found. "
|
|
176
|
+
f"Set the environment variable {ENV_VAR} to its full path if it is in a non-standard location."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
############################################################
|
|
182
|
+
# ALL C-TYPE DEFINITIONS #
|
|
183
|
+
############################################################
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class MKL_Complex16(ctypes.Structure):
|
|
187
|
+
_fields_ = [("real", ctypes.c_double),
|
|
188
|
+
("imag", ctypes.c_double)]
|
|
189
|
+
|
|
190
|
+
CINT64 = ctypes.c_int64
|
|
191
|
+
CINT32 = ctypes.c_int32
|
|
192
|
+
CFLOAT32 = ctypes.c_float
|
|
193
|
+
CFLOAT64 = ctypes.c_double
|
|
194
|
+
|
|
195
|
+
CPX16_P = ctypes.POINTER(MKL_Complex16)
|
|
196
|
+
CINT64_P = ctypes.POINTER(CINT64)
|
|
197
|
+
CINT32_P = ctypes.POINTER(CINT32)
|
|
198
|
+
CNONE_P = ctypes.POINTER(None)
|
|
199
|
+
CFLOAT32_P = ctypes.POINTER(CFLOAT32)
|
|
200
|
+
CFLOAT64_P = ctypes.POINTER(CFLOAT64)
|
|
201
|
+
VOID_SIZE = ctypes.sizeof(ctypes.c_void_p)
|
|
202
|
+
|
|
203
|
+
if VOID_SIZE == 8:
|
|
204
|
+
PT_A = CINT64
|
|
205
|
+
PT_B = np.int64
|
|
206
|
+
elif VOID_SIZE == 4:
|
|
207
|
+
PT_A = CINT32
|
|
208
|
+
PT_B = np.int32
|
|
209
|
+
|
|
210
|
+
def c_int(value: int):
|
|
211
|
+
return ctypes.byref(ctypes.c_int32(value))
|
|
212
|
+
|
|
213
|
+
PARDISO_ARG_TYPES = (ctypes.POINTER(PT_A),CINT32_P,CINT32_P,
|
|
214
|
+
CINT32_P,CINT32_P,CINT32_P,CNONE_P,CINT32_P,CINT32_P,
|
|
215
|
+
CINT32_P,CINT32_P,CINT32_P,CINT32_P,CNONE_P,CNONE_P,CINT32_P,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
############################################################
|
|
220
|
+
# PARDISO CONFIGURATIONS #
|
|
221
|
+
############################################################
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class PARDISOMType(Enum):
|
|
225
|
+
REAL_SYM_STRUCT = 1
|
|
226
|
+
REAL_SYM_POSDEF = 2
|
|
227
|
+
REAL_SYM_INDEF = -2
|
|
228
|
+
COMP_SYM_STRUCT = 3
|
|
229
|
+
COMP_HERM_POSDEF = 4
|
|
230
|
+
COMP_HERM_INDEF = -4
|
|
231
|
+
COMP_SYM = 6
|
|
232
|
+
REAL_NONSYM = 11
|
|
233
|
+
COMP_NONSYM = 13
|
|
234
|
+
|
|
235
|
+
class PARDISOPhase(Enum):
|
|
236
|
+
SYMBOLIC_FACTOR = 11
|
|
237
|
+
NUMERIC_FACTOR = 12
|
|
238
|
+
NUMERIC_SOLVE = 33
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
############################################################
|
|
243
|
+
# GENERIC MATRIX CLASS #
|
|
244
|
+
############################################################
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class SolveMatrix:
|
|
248
|
+
def __init__(self, A: sparse.csr_matrix):
|
|
249
|
+
A = A.tocsr()
|
|
250
|
+
if not A.has_sorted_indices:
|
|
251
|
+
A.sort_indices()
|
|
252
|
+
|
|
253
|
+
if (A.getnnz(axis=1) == 0).any() or (A.getnnz(axis=0) == 0).any():
|
|
254
|
+
raise ValueError('Matrix A is singular, because it contains empty rows or columns')
|
|
255
|
+
|
|
256
|
+
if A.dtype in (np.float16, np.float32, np.float64):
|
|
257
|
+
A = A.astype(np.float64)
|
|
258
|
+
elif np.iscomplexobj(A):
|
|
259
|
+
A = A.astype(np.complex128)
|
|
260
|
+
self.mat: sparse.csr_matrix = A
|
|
261
|
+
|
|
262
|
+
self.factorized_symb: bool = False
|
|
263
|
+
self.factorized_num: bool = False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def is_same(self, matrix: SolveMatrix):
|
|
267
|
+
return id(self.mat) is id(matrix.mat)
|
|
268
|
+
|
|
269
|
+
def same_structure(self, matrix: SolveMatrix):
|
|
270
|
+
return np.all(matrix.mat.indices == self.mat.indices) & np.all(matrix.mat.indptr == self.mat.indptr)
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def zerovec(self):
|
|
274
|
+
return np.zeros_like((self.mat.shape[0], 1), dtype=self.mat.dtype)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
############################################################
|
|
278
|
+
# THE PARDISO INTERFACE #
|
|
279
|
+
############################################################
|
|
280
|
+
|
|
281
|
+
class PardisoInterface:
|
|
282
|
+
|
|
283
|
+
def __init__(self):
|
|
284
|
+
|
|
285
|
+
self.libmkl: ctypes.CDLL = load_mkl()
|
|
286
|
+
self._pardiso_interface = self.libmkl.pardiso
|
|
287
|
+
|
|
288
|
+
self._pardiso_interface.restype = None
|
|
289
|
+
self._pardiso_interface.argtypes = PARDISO_ARG_TYPES
|
|
290
|
+
|
|
291
|
+
self.PT = np.zeros(64, dtype=PT_B)
|
|
292
|
+
self.IPARM = np.zeros(64, dtype=np.int32)
|
|
293
|
+
self.PERM = np.zeros(0, dtype=np.int32)
|
|
294
|
+
self.MATRIX_TYPE: PARDISOMType = None
|
|
295
|
+
self.complex: bool = False
|
|
296
|
+
self.mat_structure: np.ndarray = None
|
|
297
|
+
self.message_level: int = 0
|
|
298
|
+
|
|
299
|
+
self.matrix: SolveMatrix = None
|
|
300
|
+
self.factored_matrix: SolveMatrix = None
|
|
301
|
+
|
|
302
|
+
self.configure_solver()
|
|
303
|
+
|
|
304
|
+
def _configure(self, A: sparse.csr_matrix) -> None:
|
|
305
|
+
"""Configures the solver for the appropriate data type (float/complex)
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
A (sparse.csr_matrix): The sparse matrix to solve
|
|
309
|
+
"""
|
|
310
|
+
if np.iscomplexobj(A):
|
|
311
|
+
self.MATRIX_TYPE = PARDISOMType.COMP_SYM_STRUCT
|
|
312
|
+
self.complex = True
|
|
313
|
+
else:
|
|
314
|
+
self.MATRIX_TYPE = PARDISOMType.REAL_SYM_STRUCT
|
|
315
|
+
self.complex = False
|
|
316
|
+
|
|
317
|
+
def _prepare_B(self, b: np.ndarray) -> np.ndarray:
|
|
318
|
+
"""Fixes the forcing-vector for the solution process
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
b (np.ndarray): The forcing vector in Ax=b
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
np.ndarray: The prepared forcing-vector
|
|
325
|
+
"""
|
|
326
|
+
if sparse.issparse(b):
|
|
327
|
+
b = b.todense()
|
|
328
|
+
if np.iscomplexobj(b):
|
|
329
|
+
b = b.astype(np.complex128)
|
|
330
|
+
else:
|
|
331
|
+
b = b.astype(np.float64)
|
|
332
|
+
return b
|
|
333
|
+
|
|
334
|
+
def symbolic(self, A: sparse.csr_matrix) -> int:
|
|
335
|
+
"""Calls the Symbollic solve routinge
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
int: The error code
|
|
339
|
+
"""
|
|
340
|
+
print("SYMBOLIC FACTORIZATION")
|
|
341
|
+
self._configure(A)
|
|
342
|
+
zerovec = np.zeros_like((A.shape[0], 1), dtype=A.dtype)
|
|
343
|
+
_, error = self._call_solver(A, zerovec, phase=PARDISOPhase.SYMBOLIC_FACTOR)
|
|
344
|
+
return error
|
|
345
|
+
|
|
346
|
+
def numeric(self, A: sparse.csr_matrix) -> int:
|
|
347
|
+
"""Calls the Numeric solve routine
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
int: The error code
|
|
351
|
+
"""
|
|
352
|
+
print("NUMERIC FACTORIZATION")
|
|
353
|
+
self._configure(A)
|
|
354
|
+
zerovec = np.zeros_like((A.shape[0], 1), dtype=A.dtype)
|
|
355
|
+
_, error = self._call_solver(A, zerovec, phase=PARDISOPhase.NUMERIC_FACTOR)
|
|
356
|
+
return error
|
|
357
|
+
|
|
358
|
+
def solve(self, A: sparse.csr_matrix, b: np.ndarray) -> tuple[np.ndarray, int]:
|
|
359
|
+
""" Solves the linear problem Ax=b with PARDISO
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
A (sparse.csr_matrix): The A-matrix
|
|
363
|
+
b (np.ndarray): The b-vector
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
tuple[np.ndarray, int]: The solution vector x, and error code
|
|
367
|
+
"""
|
|
368
|
+
self._configure(A)
|
|
369
|
+
b = self._prepare_B(b)
|
|
370
|
+
x, error = self._call_solver(A, b, phase=PARDISOPhase.NUMERIC_SOLVE)
|
|
371
|
+
return x, error
|
|
372
|
+
|
|
373
|
+
def configure_solver(self,
|
|
374
|
+
perm_algo: int = 3,
|
|
375
|
+
nthreads: int = None,
|
|
376
|
+
user_perm: int = 0,
|
|
377
|
+
n_refine_steps: int = 0,
|
|
378
|
+
pivot_pert: int = 13,
|
|
379
|
+
weighted_matching: int = 2):
|
|
380
|
+
"""Configures the solver
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
perm_algo (int, optional): The permutation algorithm. Defaults to 3.
|
|
384
|
+
nthreads (int, optional): The number of threads (Must be greater than OMP_NUM_THREADS). Defaults to None.
|
|
385
|
+
user_perm (int, optional): 1, if a user permuation is provided (not supported yet). Defaults to 0.
|
|
386
|
+
n_refine_steps (int, optional): Number of refinement steps. Defaults to 0.
|
|
387
|
+
pivot_pert (int, optional): _description_. Defaults to 13.
|
|
388
|
+
weighted_matching (int, optional): weighted matching mode. Defaults to 2.
|
|
389
|
+
"""
|
|
390
|
+
if nthreads is None:
|
|
391
|
+
nthreads = int(os.environ.get('OMP_NUM_THREADS'))
|
|
392
|
+
|
|
393
|
+
self.IPARM[1] = perm_algo
|
|
394
|
+
self.IPARM[2] = nthreads
|
|
395
|
+
self.IPARM[4] = user_perm
|
|
396
|
+
self.IPARM[7] = n_refine_steps
|
|
397
|
+
self.IPARM[9] = pivot_pert
|
|
398
|
+
self.IPARM[12] = weighted_matching
|
|
399
|
+
|
|
400
|
+
def _call_solver(self, A: sparse.csr_matrix, b: np.ndarray, phase: PARDISOPhase) -> tuple[np.ndarray, int]:
|
|
401
|
+
"""Calls the PARDISO solver on linear problem Ax=b
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
A (sparse.csr_matrix): The A-matrix
|
|
405
|
+
b (np.ndarray): The b-vector
|
|
406
|
+
phase (PARDISOPhase): The solution phase
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
tuple[np.ndarray, int]: The solution vector x and error code.
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
# Declare the empty vector
|
|
413
|
+
x = np.zeros_like(b)
|
|
414
|
+
error = ctypes.c_int32(0)
|
|
415
|
+
|
|
416
|
+
# Up the pointers as PARDISO uses [1,...] indexing
|
|
417
|
+
A_index_pointers = A.indptr + 1
|
|
418
|
+
A_indices = A.indices + 1
|
|
419
|
+
|
|
420
|
+
# Define the appropriate data type (complex vs real)
|
|
421
|
+
if self.complex:
|
|
422
|
+
VALUE_P = A.data.ctypes.data_as(CPX16_P)
|
|
423
|
+
RHS_P = b.ctypes.data_as(CPX16_P)
|
|
424
|
+
X_P = x.ctypes.data_as(CPX16_P)
|
|
425
|
+
else:
|
|
426
|
+
VALUE_P = A.data.ctypes.data_as(CFLOAT64_P)
|
|
427
|
+
RHS_P = b.ctypes.data_as(CFLOAT64_P)
|
|
428
|
+
X_P = x.ctypes.data_as(CFLOAT64_P)
|
|
429
|
+
|
|
430
|
+
# Calls the pardiso function
|
|
431
|
+
self._pardiso_interface(
|
|
432
|
+
self.PT.ctypes.data_as(ctypes.POINTER(PT_A)),
|
|
433
|
+
c_int(1),
|
|
434
|
+
c_int(1),
|
|
435
|
+
c_int(self.MATRIX_TYPE.value),
|
|
436
|
+
c_int(phase.value),
|
|
437
|
+
c_int(A.shape[0]),
|
|
438
|
+
VALUE_P,
|
|
439
|
+
A_index_pointers.ctypes.data_as(CINT32_P),
|
|
440
|
+
A_indices.ctypes.data_as(CINT32_P),
|
|
441
|
+
self.PERM.ctypes.data_as(CINT32_P),
|
|
442
|
+
c_int(1),
|
|
443
|
+
self.IPARM.ctypes.data_as(CINT32_P),
|
|
444
|
+
c_int(self.message_level),
|
|
445
|
+
RHS_P,
|
|
446
|
+
X_P,
|
|
447
|
+
ctypes.byref(error))
|
|
448
|
+
|
|
449
|
+
# Returns the solution vector plus error code
|
|
450
|
+
return np.ascontiguousarray(x), error.value
|
|
451
|
+
|
|
452
|
+
def get_error(self, error: int) -> str:
|
|
453
|
+
"""Returns the PARDISO error description string given an error number
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
error (int): The error number
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
str: A description string
|
|
460
|
+
"""
|
|
461
|
+
return PARDISO_ERROR_CODES[error]
|
|
462
|
+
|
|
463
|
+
def clear_memory(self):
|
|
464
|
+
"""Clear the memory of this solver plus the PARDISO process.
|
|
465
|
+
"""
|
|
466
|
+
self.factorized_A = None
|
|
467
|
+
self.matrix = None
|
|
468
|
+
self.symbolic(sparse.csr_matrix((0,0), dtype=np.complex128), np.zeros(0, dtype=np.complex128))
|