pyMOTO 1.3.0__py3-none-any.whl → 1.4.0__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.
- {pyMOTO-1.3.0.dist-info → pyMOTO-1.4.0.dist-info}/METADATA +7 -8
- pyMOTO-1.4.0.dist-info/RECORD +29 -0
- {pyMOTO-1.3.0.dist-info → pyMOTO-1.4.0.dist-info}/WHEEL +1 -1
- pymoto/__init__.py +17 -11
- pymoto/common/domain.py +60 -5
- pymoto/common/dyadcarrier.py +33 -4
- pymoto/common/mma.py +83 -53
- pymoto/core_objects.py +117 -113
- pymoto/modules/aggregation.py +209 -0
- pymoto/modules/assembly.py +136 -10
- pymoto/modules/complex.py +3 -3
- pymoto/modules/filter.py +171 -24
- pymoto/modules/generic.py +12 -1
- pymoto/modules/io.py +22 -11
- pymoto/modules/linalg.py +21 -110
- pymoto/modules/scaling.py +4 -4
- pymoto/routines.py +23 -9
- pymoto/solvers/__init__.py +14 -0
- pymoto/solvers/auto_determine.py +108 -0
- pymoto/{common/solvers_dense.py → solvers/dense.py} +90 -70
- pymoto/solvers/iterative.py +361 -0
- pymoto/solvers/matrix_checks.py +56 -0
- pymoto/solvers/solvers.py +253 -0
- pymoto/{common/solvers_sparse.py → solvers/sparse.py} +41 -29
- pyMOTO-1.3.0.dist-info/RECORD +0 -24
- pymoto/common/solvers.py +0 -236
- {pyMOTO-1.3.0.dist-info → pyMOTO-1.4.0.dist-info}/LICENSE +0 -0
- {pyMOTO-1.3.0.dist-info → pyMOTO-1.4.0.dist-info}/top_level.txt +0 -0
- {pyMOTO-1.3.0.dist-info → pyMOTO-1.4.0.dist-info}/zip-safe +0 -0
pymoto/modules/linalg.py
CHANGED
@@ -7,10 +7,9 @@ import scipy.linalg as spla # Dense matrix solvers
|
|
7
7
|
import scipy.sparse as sps
|
8
8
|
import scipy.sparse.linalg as spsla
|
9
9
|
|
10
|
-
from pymoto import Signal, Module, DyadCarrier
|
11
|
-
from pymoto import
|
12
|
-
from pymoto import
|
13
|
-
from pymoto import matrix_is_symmetric, matrix_is_hermitian, matrix_is_diagonal
|
10
|
+
from pymoto import Signal, Module, DyadCarrier
|
11
|
+
from pymoto.solvers import auto_determine_solver
|
12
|
+
from pymoto.solvers import matrix_is_hermitian, LDAWrapper
|
14
13
|
|
15
14
|
|
16
15
|
class StaticCondensation(Module):
|
@@ -172,7 +171,7 @@ class SystemOfEquations(Module):
|
|
172
171
|
adjoint_load += self.Afp * dgdb[self.p, ...]
|
173
172
|
|
174
173
|
lam = np.zeros_like(self.x)
|
175
|
-
lamf = -1.0 * self.module_LinSolve.solver.
|
174
|
+
lamf = -1.0 * self.module_LinSolve.solver.solve(adjoint_load, trans='T')
|
176
175
|
lam[self.f, ...] = lamf
|
177
176
|
|
178
177
|
if dgdb is not None:
|
@@ -219,105 +218,6 @@ class Inverse(Module):
|
|
219
218
|
return dA if np.iscomplexobj(A) else np.real(dA)
|
220
219
|
|
221
220
|
|
222
|
-
# flake8: noqa: C901
|
223
|
-
def auto_determine_solver(A, isdiagonal=None, islowertriangular=None, isuppertriangular=None,
|
224
|
-
ishermitian=None, issymmetric=None, ispositivedefinite=None):
|
225
|
-
"""
|
226
|
-
Uses parts of Matlab's scheme https://nl.mathworks.com/help/matlab/ref/mldivide.html
|
227
|
-
:param A: The matrix
|
228
|
-
:param isdiagonal: Manual override for diagonal matrix
|
229
|
-
:param islowertriangular: Override for lower triangular matrix
|
230
|
-
:param isuppertriangular: Override for upper triangular matrix
|
231
|
-
:param ishermitian: Override for hermitian matrix (prevents check)
|
232
|
-
:param issymmetric: Override for symmetric matrix (prevents check). Is the same as hermitian for a real matrix
|
233
|
-
:param ispositivedefinite: Manual override for positive definiteness
|
234
|
-
:return: LinearSolver which should be 'best' for the matrix
|
235
|
-
"""
|
236
|
-
issparse = sps.issparse(A) # Check if the matrix is sparse
|
237
|
-
issquare = A.shape[0] == A.shape[1] # Check if the matrix is square
|
238
|
-
|
239
|
-
if not issquare:
|
240
|
-
if issparse:
|
241
|
-
sps.SparseEfficiencyWarning("Only a dense version of QR solver is available") # TODO
|
242
|
-
return SolverDenseQR()
|
243
|
-
|
244
|
-
# l_bw, u_bw = spla.bandwidth(A) # TODO Get bandwidth (implemented in scipy version > 1.8.0)
|
245
|
-
|
246
|
-
if isdiagonal is None: # Check if matrix is diagonal
|
247
|
-
# TODO: This could be improved to check other sparse matrix types as well
|
248
|
-
isdiagonal = matrix_is_diagonal(A)
|
249
|
-
if isdiagonal:
|
250
|
-
return SolverDiagonal()
|
251
|
-
|
252
|
-
# Check if the matrix is triangular
|
253
|
-
# TODO Currently only for dense matrices
|
254
|
-
if islowertriangular is None: # Check if matrix is lower triangular
|
255
|
-
islowertriangular = False if issparse else np.allclose(A, np.tril(A))
|
256
|
-
if islowertriangular:
|
257
|
-
warnings.WarningMessage("Lower triangular solver not implemented",
|
258
|
-
UserWarning, getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno)
|
259
|
-
|
260
|
-
if isuppertriangular is None: # Check if matrix is upper triangular
|
261
|
-
isuppertriangular = False if issparse else np.allclose(A, np.triu(A))
|
262
|
-
if isuppertriangular:
|
263
|
-
warnings.WarningMessage("Upper triangular solver not implemented",
|
264
|
-
UserWarning, getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno)
|
265
|
-
|
266
|
-
ispermutedtriangular = False
|
267
|
-
if ispermutedtriangular:
|
268
|
-
warnings.WarningMessage("Permuted triangular solver not implemented",
|
269
|
-
UserWarning, getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno)
|
270
|
-
|
271
|
-
# Check if the matrix is complex-valued
|
272
|
-
iscomplex = np.iscomplexobj(A)
|
273
|
-
if iscomplex:
|
274
|
-
# Detect if the matrix is hermitian and/or symmetric
|
275
|
-
if ishermitian is None:
|
276
|
-
ishermitian = matrix_is_hermitian(A)
|
277
|
-
if issymmetric is None:
|
278
|
-
issymmetric = matrix_is_symmetric(A)
|
279
|
-
else:
|
280
|
-
if ishermitian is None and issymmetric is None:
|
281
|
-
# Detect if the matrix is symmetric
|
282
|
-
issymmetric = matrix_is_symmetric(A)
|
283
|
-
ishermitian = issymmetric
|
284
|
-
elif ishermitian is not None and issymmetric is not None:
|
285
|
-
assert ishermitian == issymmetric, "For real-valued matrices, symmetry and hermitian must be equal"
|
286
|
-
elif ishermitian is None:
|
287
|
-
ishermitian = issymmetric
|
288
|
-
elif issymmetric is None:
|
289
|
-
issymmetric = ishermitian
|
290
|
-
|
291
|
-
if issparse:
|
292
|
-
# Prefer Intel Pardiso solver as it can solve any matrix TODO: Check for complex matrix
|
293
|
-
if SolverSparsePardiso.defined and not iscomplex:
|
294
|
-
# TODO check for positive definiteness? np.all(A.diagonal() > 0) or np.all(A.diagonal() < 0)
|
295
|
-
return SolverSparsePardiso(symmetric=issymmetric, hermitian=ishermitian, positive_definite=ispositivedefinite)
|
296
|
-
|
297
|
-
if ishermitian:
|
298
|
-
# Check if diagonal is all positive or all negative -> Cholesky
|
299
|
-
if np.all(A.diagonal() > 0) or np.all(A.diagonal() < 0): # TODO what about the complex case?
|
300
|
-
if SolverSparseCholeskyScikit.defined:
|
301
|
-
return SolverSparseCholeskyScikit()
|
302
|
-
if SolverSparseCholeskyCVXOPT.defined:
|
303
|
-
return SolverSparseCholeskyCVXOPT()
|
304
|
-
|
305
|
-
return SolverSparseLU() # Default to LU, which should be possible for any non-singular square matrix
|
306
|
-
|
307
|
-
else: # Dense branch
|
308
|
-
if ishermitian:
|
309
|
-
# Check if diagonal is all positive or all negative
|
310
|
-
if np.all(A.diagonal() > 0) or np.all(A.diagonal() < 0):
|
311
|
-
return SolverDenseCholesky()
|
312
|
-
else:
|
313
|
-
return SolverDenseLDL(hermitian=ishermitian)
|
314
|
-
elif issymmetric:
|
315
|
-
return SolverDenseLDL(hermitian=ishermitian)
|
316
|
-
else:
|
317
|
-
# TODO: Detect if the matrix is Hessenberg
|
318
|
-
return SolverDenseLU()
|
319
|
-
|
320
|
-
|
321
221
|
class LinSolve(Module):
|
322
222
|
r""" Solves linear system of equations :math:`\mathbf{A}\mathbf{x}=\mathbf{b}`
|
323
223
|
|
@@ -347,6 +247,7 @@ class LinSolve(Module):
|
|
347
247
|
self.ishermitian = hermitian
|
348
248
|
self.issymmetric = symmetric
|
349
249
|
self.solver = solver
|
250
|
+
self.u = None # Solution storage
|
350
251
|
|
351
252
|
def _response(self, mat, rhs):
|
352
253
|
# Do some detections on the matrix type
|
@@ -365,19 +266,23 @@ class LinSolve(Module):
|
|
365
266
|
if self.solver is None:
|
366
267
|
self.solver = auto_determine_solver(mat, ishermitian=self.ishermitian)
|
367
268
|
if not isinstance(self.solver, LDAWrapper) and self.use_lda_solver:
|
368
|
-
|
269
|
+
lda_kwargs = dict(hermitian=self.ishermitian, symmetric=self.issymmetric)
|
270
|
+
if hasattr(self.solver, 'tol'):
|
271
|
+
lda_kwargs['tol'] = self.solver.tol * 2
|
272
|
+
self.solver = LDAWrapper(self.solver, **lda_kwargs)
|
369
273
|
|
370
274
|
# Update solver with new matrix
|
371
275
|
self.solver.update(mat)
|
372
276
|
|
373
277
|
# Solution
|
374
|
-
self.u = self.solver.solve(rhs)
|
278
|
+
self.u = self.solver.solve(rhs, x0=self.u)
|
375
279
|
|
376
280
|
return self.u
|
377
281
|
|
378
282
|
def _sensitivity(self, dfdv):
|
379
283
|
mat, rhs = [s.state for s in self.sig_in]
|
380
|
-
lam = self.solver.
|
284
|
+
# lam = self.solver.solve(dfdv.conj(), trans='H').conj()
|
285
|
+
lam = self.solver.solve(dfdv, trans='T')
|
381
286
|
|
382
287
|
if self.issparse:
|
383
288
|
if self.u.ndim > 1:
|
@@ -440,6 +345,7 @@ class EigenSolve(Module):
|
|
440
345
|
self.sigma = sigma
|
441
346
|
self.mode = mode
|
442
347
|
self.Ainv = None
|
348
|
+
self.do_solve = False
|
443
349
|
|
444
350
|
def _response(self, A, *args):
|
445
351
|
B = args[0] if len(args) > 0 else None
|
@@ -500,9 +406,14 @@ class EigenSolve(Module):
|
|
500
406
|
# Use shift-and-invert, so make inverse operator
|
501
407
|
if self.Ainv is None:
|
502
408
|
self.Ainv = auto_determine_solver(mat_shifted, ishermitian=self.is_hermitian)
|
503
|
-
|
504
|
-
|
505
|
-
|
409
|
+
self.do_solve = True
|
410
|
+
if self.sigma != 0:
|
411
|
+
self.do_solve = True
|
412
|
+
if self.do_solve:
|
413
|
+
self.Ainv.update(mat_shifted)
|
414
|
+
|
415
|
+
AinvOp = spsla.LinearOperator(mat_shifted.shape, matvec=self.Ainv.solve,
|
416
|
+
rmatvec=lambda b: self.Ainv.solve(b, trans='H'))
|
506
417
|
|
507
418
|
if self.is_hermitian:
|
508
419
|
return spsla.eigsh(A, M=B, k=self.nmodes, OPinv=AinvOp, sigma=self.sigma, mode=self.mode)
|
pymoto/modules/scaling.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
from pymoto import Module
|
2
|
-
|
2
|
+
import numpy as np
|
3
3
|
|
4
4
|
class Scaling(Module):
|
5
5
|
r""" Scales (scalar) input for different response functions in optimization (objective / constraints).
|
6
6
|
This is useful, for instance, for MMA where the objective must be scaled in a certain way for good convergence.
|
7
7
|
|
8
|
-
Objective scaling (`minval` and `maxval` are both undefined):
|
9
|
-
:math:`y^{(i)} = s \frac{x^{(i)}}{x^{(0)}}`
|
8
|
+
Objective scaling using absolute value or vector norm (`minval` and `maxval` are both undefined):
|
9
|
+
:math:`y^{(i)} = s \frac{x^{(i)}}{||x^{(0)}||}`
|
10
10
|
|
11
11
|
For the constraints, the negative null-form convention is used, which means the constraint is :math:`y(x) \leq 0`.
|
12
12
|
|
@@ -39,7 +39,7 @@ class Scaling(Module):
|
|
39
39
|
|
40
40
|
def _response(self, x):
|
41
41
|
if not hasattr(self, 'sf'):
|
42
|
-
self.sf = self.scaling/x
|
42
|
+
self.sf = self.scaling/np.linalg.norm(x)
|
43
43
|
if self.minval is not None:
|
44
44
|
g = 1 - x/self.minval
|
45
45
|
elif self.maxval is not None:
|
pymoto/routines.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
import warnings
|
1
2
|
import numpy as np
|
2
3
|
from .utils import _parse_to_list, _concatenate_to_array
|
3
4
|
from .core_objects import Signal, SignalSlice, Module, Network
|
4
5
|
from .common.mma import MMA
|
5
6
|
from typing import List, Iterable, Union, Callable
|
7
|
+
from scipy.sparse import issparse
|
6
8
|
|
7
9
|
|
8
10
|
def _has_signal_overlap(sig1: List[Signal], sig2: List[Signal]):
|
@@ -84,15 +86,15 @@ def finite_difference(blk: Module, fromsig: Union[Signal, Iterable[Signal]] = No
|
|
84
86
|
blk.response()
|
85
87
|
|
86
88
|
print("Outputs:")
|
87
|
-
if
|
89
|
+
if verbose:
|
90
|
+
[print("{}\t{} = {}".format(i, s.tag, s.state)) for i, s in enumerate(outps)]
|
91
|
+
else:
|
88
92
|
print(", ".join([s.tag for s in outps]))
|
89
93
|
|
90
94
|
# Get analytical response and sensitivities, by looping over all outputs
|
91
95
|
for Iout, Sout in enumerate(outps):
|
92
96
|
# Obtain the output state
|
93
97
|
output = Sout.state
|
94
|
-
if verbose:
|
95
|
-
print("{}\t{} = {}".format(Iout, Sout.tag, output))
|
96
98
|
|
97
99
|
# Store the output value
|
98
100
|
f0[Iout] = (output.copy() if hasattr(output, "copy") else output)
|
@@ -171,7 +173,10 @@ def finite_difference(blk: Module, fromsig: Union[Signal, Iterable[Signal]] = No
|
|
171
173
|
fp = Sout.state
|
172
174
|
|
173
175
|
# Finite difference sensitivity
|
174
|
-
|
176
|
+
if issparse(fp):
|
177
|
+
df = (fp.toarray() - f0[Iout].toarray()) / (dx * sf)
|
178
|
+
else:
|
179
|
+
df = (fp - f0[Iout])/(dx*sf)
|
175
180
|
|
176
181
|
dgdx_fd = np.real(np.sum(df*df_an[Iout]))
|
177
182
|
|
@@ -183,7 +188,7 @@ def finite_difference(blk: Module, fromsig: Union[Signal, Iterable[Signal]] = No
|
|
183
188
|
else:
|
184
189
|
dgdx_an = 0.0
|
185
190
|
|
186
|
-
if abs(dgdx_an)
|
191
|
+
if abs(dgdx_an) == 0:
|
187
192
|
error = abs(dgdx_fd - dgdx_an)
|
188
193
|
else:
|
189
194
|
error = abs(dgdx_fd - dgdx_an)/max(abs(dgdx_fd), abs(dgdx_an))
|
@@ -223,7 +228,10 @@ def finite_difference(blk: Module, fromsig: Union[Signal, Iterable[Signal]] = No
|
|
223
228
|
fp = Sout.state
|
224
229
|
|
225
230
|
# Finite difference sensitivity
|
226
|
-
|
231
|
+
if issparse(fp):
|
232
|
+
df = (fp.toarray() - f0[Iout].toarray()) / (dx * 1j * sf)
|
233
|
+
else:
|
234
|
+
df = (fp - f0[Iout])/(dx*1j*sf)
|
227
235
|
dgdx_fd = np.imag(np.sum(df*df_an[Iout]))
|
228
236
|
|
229
237
|
if dx_an[Iout][Iin] is not None:
|
@@ -335,8 +343,9 @@ def minimize_oc(function, variables, objective: Signal,
|
|
335
343
|
function.sensitivity()
|
336
344
|
dfdx, _ = _concatenate_to_array(obtain_sensitivities(variables))
|
337
345
|
maxdfdx = max(dfdx)
|
338
|
-
if maxdfdx >
|
339
|
-
|
346
|
+
if maxdfdx > 1e-15:
|
347
|
+
warnings.warn(f"OC only works for negative sensitivities: max(dfdx) = {maxdfdx}. Clipping positive values.")
|
348
|
+
dfdx = np.minimum(dfdx, 0)
|
340
349
|
|
341
350
|
# Do OC update
|
342
351
|
l1, l2 = l1init, l2init
|
@@ -374,7 +383,12 @@ def minimize_mma(function, variables, responses, **kwargs):
|
|
374
383
|
move: Move limit on relative variable change per iteration
|
375
384
|
xmin: Minimum design variable (can be a vector)
|
376
385
|
xmax: Maximum design variable (can be a vector)
|
377
|
-
verbosity:
|
386
|
+
verbosity: Level of information to print
|
387
|
+
0 - No prints
|
388
|
+
1 - Only convergence message
|
389
|
+
2 - Convergence and iteration info (default)
|
390
|
+
3 - Additional info on variables
|
391
|
+
4 - Additional info on sensitivity information
|
378
392
|
|
379
393
|
"""
|
380
394
|
# Save initial state
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from .solvers import LinearSolver, LDAWrapper
|
2
|
+
from .matrix_checks import matrix_is_complex, matrix_is_diagonal, matrix_is_symmetric, matrix_is_hermitian
|
3
|
+
from .dense import SolverDiagonal, SolverDenseQR, SolverDenseLU, SolverDenseCholesky, SolverDenseLDL
|
4
|
+
from .sparse import SolverSparsePardiso, SolverSparseLU, SolverSparseCholeskyScikit, SolverSparseCholeskyCVXOPT
|
5
|
+
from .iterative import Preconditioner, CG, DampedJacobi, SOR, ILU, GeometricMultigrid
|
6
|
+
from .auto_determine import auto_determine_solver
|
7
|
+
|
8
|
+
__all__ = ['matrix_is_complex', 'matrix_is_diagonal', 'matrix_is_symmetric', 'matrix_is_hermitian',
|
9
|
+
'LinearSolver', 'LDAWrapper',
|
10
|
+
'SolverDiagonal', 'SolverDenseQR', 'SolverDenseLU', 'SolverDenseCholesky', 'SolverDenseLDL',
|
11
|
+
'SolverSparsePardiso', 'SolverSparseLU', 'SolverSparseCholeskyScikit', 'SolverSparseCholeskyCVXOPT',
|
12
|
+
'Preconditioner', 'CG', 'DampedJacobi', 'SOR', 'ILU', 'GeometricMultigrid',
|
13
|
+
'auto_determine_solver',
|
14
|
+
]
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import warnings
|
2
|
+
import scipy.sparse as sps
|
3
|
+
from inspect import currentframe, getframeinfo
|
4
|
+
|
5
|
+
from .dense import *
|
6
|
+
from .sparse import *
|
7
|
+
from .matrix_checks import *
|
8
|
+
|
9
|
+
|
10
|
+
# flake8: noqa: C901
|
11
|
+
def auto_determine_solver(A, isdiagonal=None, islowertriangular=None, isuppertriangular=None,
|
12
|
+
ishermitian=None, issymmetric=None, ispositivedefinite=None):
|
13
|
+
"""
|
14
|
+
Uses parts of Matlab's scheme https://nl.mathworks.com/help/matlab/ref/mldivide.html
|
15
|
+
:param A: The matrix
|
16
|
+
:param isdiagonal: Manual override for diagonal matrix
|
17
|
+
:param islowertriangular: Override for lower triangular matrix
|
18
|
+
:param isuppertriangular: Override for upper triangular matrix
|
19
|
+
:param ishermitian: Override for hermitian matrix (prevents check)
|
20
|
+
:param issymmetric: Override for symmetric matrix (prevents check). Is the same as hermitian for a real matrix
|
21
|
+
:param ispositivedefinite: Manual override for positive definiteness
|
22
|
+
:return: LinearSolver which should be 'best' for the matrix
|
23
|
+
"""
|
24
|
+
issparse = sps.issparse(A) # Check if the matrix is sparse
|
25
|
+
issquare = A.shape[0] == A.shape[1] # Check if the matrix is square
|
26
|
+
|
27
|
+
if not issquare:
|
28
|
+
if issparse:
|
29
|
+
sps.SparseEfficiencyWarning("Only a dense version of QR solver is available") # TODO
|
30
|
+
return SolverDenseQR()
|
31
|
+
|
32
|
+
# l_bw, u_bw = spla.bandwidth(A) # TODO Get bandwidth (implemented in scipy version > 1.8.0)
|
33
|
+
|
34
|
+
if isdiagonal is None: # Check if matrix is diagonal
|
35
|
+
# TODO: This could be improved to check other sparse matrix types as well
|
36
|
+
isdiagonal = matrix_is_diagonal(A)
|
37
|
+
if isdiagonal:
|
38
|
+
return SolverDiagonal()
|
39
|
+
|
40
|
+
# Check if the matrix is triangular
|
41
|
+
# TODO Currently only for dense matrices
|
42
|
+
if islowertriangular is None: # Check if matrix is lower triangular
|
43
|
+
islowertriangular = False if issparse else np.allclose(A, np.tril(A))
|
44
|
+
if islowertriangular:
|
45
|
+
warnings.WarningMessage("Lower triangular solver not implemented",
|
46
|
+
UserWarning, getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno)
|
47
|
+
|
48
|
+
if isuppertriangular is None: # Check if matrix is upper triangular
|
49
|
+
isuppertriangular = False if issparse else np.allclose(A, np.triu(A))
|
50
|
+
if isuppertriangular:
|
51
|
+
warnings.WarningMessage("Upper triangular solver not implemented",
|
52
|
+
UserWarning, getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno)
|
53
|
+
|
54
|
+
ispermutedtriangular = False
|
55
|
+
if ispermutedtriangular:
|
56
|
+
warnings.WarningMessage("Permuted triangular solver not implemented",
|
57
|
+
UserWarning, getframeinfo(currentframe()).filename, getframeinfo(currentframe()).lineno)
|
58
|
+
|
59
|
+
# Check if the matrix is complex-valued
|
60
|
+
iscomplex = np.iscomplexobj(A)
|
61
|
+
if iscomplex:
|
62
|
+
# Detect if the matrix is hermitian and/or symmetric
|
63
|
+
if ishermitian is None:
|
64
|
+
ishermitian = matrix_is_hermitian(A)
|
65
|
+
if issymmetric is None:
|
66
|
+
issymmetric = matrix_is_symmetric(A)
|
67
|
+
else:
|
68
|
+
if ishermitian is None and issymmetric is None:
|
69
|
+
# Detect if the matrix is symmetric
|
70
|
+
issymmetric = matrix_is_symmetric(A)
|
71
|
+
ishermitian = issymmetric
|
72
|
+
elif ishermitian is not None and issymmetric is not None:
|
73
|
+
assert ishermitian == issymmetric, "For real-valued matrices, symmetry and hermitian must be equal"
|
74
|
+
elif ishermitian is None:
|
75
|
+
ishermitian = issymmetric
|
76
|
+
elif issymmetric is None:
|
77
|
+
issymmetric = ishermitian
|
78
|
+
|
79
|
+
if issparse:
|
80
|
+
# Prefer Intel Pardiso solver as it can solve any matrix TODO: Check for complex matrix
|
81
|
+
if SolverSparsePardiso.defined and not iscomplex:
|
82
|
+
# TODO check for positive definiteness? np.all(A.diagonal() > 0) or np.all(A.diagonal() < 0)
|
83
|
+
return SolverSparsePardiso(symmetric=issymmetric, hermitian=ishermitian, positive_definite=ispositivedefinite)
|
84
|
+
|
85
|
+
if ishermitian:
|
86
|
+
# Check if diagonal is all positive or all negative -> Cholesky
|
87
|
+
if ispositivedefinite is None:
|
88
|
+
ispositivedefinite = np.all(A.diagonal() > 0) or np.all(A.diagonal() < 0)
|
89
|
+
if ispositivedefinite: # TODO what about the complex case?
|
90
|
+
if SolverSparseCholeskyScikit.defined:
|
91
|
+
return SolverSparseCholeskyScikit()
|
92
|
+
if SolverSparseCholeskyCVXOPT.defined:
|
93
|
+
return SolverSparseCholeskyCVXOPT()
|
94
|
+
|
95
|
+
return SolverSparseLU() # Default to LU, which should be possible for any non-singular square matrix
|
96
|
+
|
97
|
+
else: # Dense branch
|
98
|
+
if ishermitian:
|
99
|
+
# Check if diagonal is all positive or all negative
|
100
|
+
if np.all(A.diagonal() > 0) or np.all(A.diagonal() < 0):
|
101
|
+
return SolverDenseCholesky()
|
102
|
+
else:
|
103
|
+
return SolverDenseLDL(hermitian=ishermitian)
|
104
|
+
elif issymmetric:
|
105
|
+
return SolverDenseLDL(hermitian=ishermitian)
|
106
|
+
else:
|
107
|
+
# TODO: Detect if the matrix is Hessenberg
|
108
|
+
return SolverDenseLU()
|