pyMOTO 1.2.1__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.2.1.dist-info → pyMOTO-1.4.0.dist-info}/METADATA +7 -8
- pyMOTO-1.4.0.dist-info/RECORD +29 -0
- {pyMOTO-1.2.1.dist-info → pyMOTO-1.4.0.dist-info}/WHEEL +1 -1
- pymoto/__init__.py +19 -13
- pymoto/common/domain.py +75 -0
- 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 +202 -41
- 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 +24 -118
- pymoto/modules/scaling.py +4 -4
- pymoto/routines.py +32 -15
- 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.2.1.dist-info/RECORD +0 -24
- pymoto/common/solvers.py +0 -236
- {pyMOTO-1.2.1.dist-info → pyMOTO-1.4.0.dist-info}/LICENSE +0 -0
- {pyMOTO-1.2.1.dist-info → pyMOTO-1.4.0.dist-info}/top_level.txt +0 -0
- {pyMOTO-1.2.1.dist-info → pyMOTO-1.4.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,253 @@
|
|
1
|
+
import warnings
|
2
|
+
import numpy as np
|
3
|
+
from .matrix_checks import matrix_is_hermitian, matrix_is_symmetric, matrix_is_complex
|
4
|
+
|
5
|
+
|
6
|
+
class LinearSolver:
|
7
|
+
""" Base class of all linear solvers
|
8
|
+
|
9
|
+
Keyword Args:
|
10
|
+
A (matrix): Optionally provide a matrix, which is used in :method:`update` right away.
|
11
|
+
|
12
|
+
Attributes:
|
13
|
+
defined (bool): Flag if the solver is able to run, e.g. false if some dependent library is not available
|
14
|
+
"""
|
15
|
+
|
16
|
+
defined = True
|
17
|
+
_err_msg = ""
|
18
|
+
|
19
|
+
def __init__(self, A=None):
|
20
|
+
if A is not None:
|
21
|
+
self.update(A)
|
22
|
+
|
23
|
+
def update(self, A):
|
24
|
+
""" Updates with a new matrix of the same structure
|
25
|
+
|
26
|
+
Args:
|
27
|
+
A (matrix): The new matrix of size ``(N, N)``
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
self
|
31
|
+
"""
|
32
|
+
raise NotImplementedError(f"Solver not implemented {self._err_msg}")
|
33
|
+
|
34
|
+
def solve(self, rhs, x0=None, trans='N'):
|
35
|
+
r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}`
|
36
|
+
|
37
|
+
Args:
|
38
|
+
rhs: Right hand side :math:`\mathbf{b}` of shape ``(N)`` or ``(N, K)`` for multiple right-hand-sides
|
39
|
+
x0 (optional): Initial guess for the solution
|
40
|
+
trans (optional): Option to transpose matrix
|
41
|
+
'N': A @ x == rhs (default) Normal matrix
|
42
|
+
'T': A^T @ x == rhs Transposed matrix
|
43
|
+
'H': A^H @ x == rhs Hermitian transposed matrix (conjugate transposed)
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
Solution vector :math:`\mathbf{x}` of same shape as :math:`\mathbf{b}`
|
47
|
+
"""
|
48
|
+
raise NotImplementedError(f"Solver not implemented {self._err_msg}")
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
def residual(A, x, b, trans='N'):
|
52
|
+
r""" Calculates the (relative) residual of the linear system of equations
|
53
|
+
|
54
|
+
The residual is calculated as
|
55
|
+
:math:`r = \frac{\left| \mathbf{A} \mathbf{x} - \mathbf{b} \right|}{\left| \mathbf{b} \right|}`
|
56
|
+
|
57
|
+
Args:
|
58
|
+
A: The matrix
|
59
|
+
x: Solution vector
|
60
|
+
b: Right-hand side
|
61
|
+
trans (optional): Matrix tranformation (`N` is normal, `T` is transposed, `H` is hermitian transposed)
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
Residual value
|
65
|
+
"""
|
66
|
+
assert x.shape == b.shape
|
67
|
+
if trans == 'N':
|
68
|
+
mat = A
|
69
|
+
elif trans == 'T':
|
70
|
+
mat = A.T
|
71
|
+
elif trans == 'H':
|
72
|
+
mat = A.conj().T
|
73
|
+
else:
|
74
|
+
raise TypeError("Only N, T, or H transposition is possible")
|
75
|
+
return np.linalg.norm(mat@x - b, axis=0) / np.linalg.norm(b, axis=0)
|
76
|
+
|
77
|
+
|
78
|
+
class LDAWrapper(LinearSolver):
|
79
|
+
r""" Linear dependency aware solver (LDAS)
|
80
|
+
|
81
|
+
This solver uses previous solutions of the system :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` to reduce computational
|
82
|
+
effort. In case the solution :math:`\mathbf{x}` is linearly dependent on the previous solutions, the solution
|
83
|
+
will be nearly free of cost.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
solver: The internal solver to be used
|
87
|
+
tol (optional): Residual tolerance above which the internal solver is used to add a new solution vector.
|
88
|
+
A (optional): The matrix :math:`\mathbf{A}`
|
89
|
+
symmetric (optional): Flag to indicate a symmetric matrix :math:`A=A^T`
|
90
|
+
hermitian (optional): Flag to indicate a Hermitian matrix :math:`A=A^H`
|
91
|
+
|
92
|
+
References:
|
93
|
+
Koppen, S., van der Kolk, M., van den Boom, S., & Langelaar, M. (2022).
|
94
|
+
Efficient computation of states and sensitivities for compound structural optimisation problems using a Linear Dependency Aware Solver (LDAS).
|
95
|
+
Structural and Multidisciplinary Optimization, 65(9), 273.
|
96
|
+
DOI: 10.1007/s00158-022-03378-8
|
97
|
+
"""
|
98
|
+
def __init__(self, solver: LinearSolver, tol=1e-7, A=None, symmetric=None, hermitian=None):
|
99
|
+
self.solver = solver
|
100
|
+
self.tol = tol
|
101
|
+
# Storage for solution vectors (solutions of A x = b)
|
102
|
+
self.x_stored = []
|
103
|
+
self.b_stored = []
|
104
|
+
# Storage for adjoint solution vectors (solutions of A^H x = b)
|
105
|
+
self.xadj_stored = []
|
106
|
+
self.badj_stored = []
|
107
|
+
self.A = None
|
108
|
+
self._did_solve = False # For debugging purposes
|
109
|
+
self._last_rtol = 0.
|
110
|
+
self.hermitian = hermitian
|
111
|
+
self.symmetric = symmetric
|
112
|
+
self.complex = None
|
113
|
+
super().__init__(A)
|
114
|
+
|
115
|
+
def update(self, A):
|
116
|
+
""" Clear the internal stored solution vectors and update the internal ``solver`` """
|
117
|
+
if self.symmetric is None:
|
118
|
+
self.symmetric = matrix_is_symmetric(A)
|
119
|
+
|
120
|
+
if self.hermitian is None:
|
121
|
+
if not matrix_is_complex(A):
|
122
|
+
self.hermitian = self.symmetric
|
123
|
+
self.hermitian = matrix_is_hermitian(A)
|
124
|
+
|
125
|
+
self.A = A
|
126
|
+
self.x_stored.clear()
|
127
|
+
self.b_stored.clear()
|
128
|
+
self.xadj_stored.clear()
|
129
|
+
self.badj_stored.clear()
|
130
|
+
self.solver.update(A)
|
131
|
+
|
132
|
+
def _do_solve_1rhs(self, A, rhs, x_data, b_data, solve_fn, x0=None):
|
133
|
+
dtype = np.result_type(A, rhs)
|
134
|
+
if rhs.ndim == 1:
|
135
|
+
rhs_loc = np.zeros((rhs.size, 1), dtype=dtype)
|
136
|
+
rhs_loc[:, 0] = rhs
|
137
|
+
else:
|
138
|
+
rhs_loc = np.zeros_like(rhs, dtype=dtype)
|
139
|
+
rhs_loc[:] = rhs
|
140
|
+
sol = np.zeros_like(rhs_loc, dtype=dtype)
|
141
|
+
|
142
|
+
# Check linear dependencies in the rhs using modified Gram-Schmidt
|
143
|
+
for (x, b) in zip(x_data, b_data):
|
144
|
+
assert x.ndim == b.ndim == 1
|
145
|
+
assert x.size == b.size == rhs_loc.shape[0]
|
146
|
+
alpha = rhs_loc.T @ b.conj() / (b.conj() @ b)
|
147
|
+
|
148
|
+
rem_rhs = alpha * b[:, None]
|
149
|
+
add_sol = alpha * x[:, None]
|
150
|
+
|
151
|
+
if np.iscomplexobj(rem_rhs) and not np.iscomplexobj(rhs_loc):
|
152
|
+
if np.linalg.norm(np.imag(rem_rhs)) < 1e-10*np.linalg.norm(np.real(rem_rhs)):
|
153
|
+
rem_rhs = np.real(rem_rhs)
|
154
|
+
else:
|
155
|
+
warnings.warn('LDAS: Complex vector cannot be subtracted from real rhs')
|
156
|
+
continue
|
157
|
+
|
158
|
+
if np.iscomplexobj(add_sol) and not np.iscomplexobj(sol):
|
159
|
+
if np.linalg.norm(np.imag(add_sol)) < 1e-10*np.linalg.norm(np.real(add_sol)):
|
160
|
+
add_sol = np.real(add_sol)
|
161
|
+
else:
|
162
|
+
warnings.warn('LDAS: Complex vector cannot be added to real solution')
|
163
|
+
continue
|
164
|
+
|
165
|
+
rhs_loc -= rem_rhs
|
166
|
+
sol += add_sol
|
167
|
+
|
168
|
+
# Check tolerance
|
169
|
+
self._last_rtol = np.ones(rhs_loc.shape[-1]) if len(x_data) == 0 else self.residual(A, sol, rhs if rhs.ndim > 1 else rhs.reshape(-1, 1))
|
170
|
+
self._did_solve = self._last_rtol > self.tol
|
171
|
+
if np.any(self._did_solve):
|
172
|
+
if x0 is not None:
|
173
|
+
if x0.ndim == 1:
|
174
|
+
x0_loc = x0.reshape(-1, 1).copy()
|
175
|
+
else:
|
176
|
+
x0_loc = x0[..., self._did_solve].copy()
|
177
|
+
for x in x_data:
|
178
|
+
beta = x0_loc.T @ x.conj() / (x.conj() @ x)
|
179
|
+
x0_loc -= beta * x
|
180
|
+
else:
|
181
|
+
x0_loc = None
|
182
|
+
|
183
|
+
# Calculate a new solution
|
184
|
+
xnew = solve_fn(rhs_loc[..., self._did_solve], x0_loc)
|
185
|
+
self.residual(A, xnew, rhs_loc[..., self._did_solve])
|
186
|
+
sol[..., self._did_solve] += xnew
|
187
|
+
|
188
|
+
# Add to database
|
189
|
+
for i in range(xnew.shape[-1]):
|
190
|
+
# Remove all previous components that are already in the database (orthogonalize)
|
191
|
+
xadd = xnew[..., i]
|
192
|
+
badd = A @ xadd
|
193
|
+
for x, b in zip(x_data, b_data):
|
194
|
+
beta = badd @ b.conj() / (b.conj() @ b)
|
195
|
+
badd -= beta * b
|
196
|
+
xadd -= beta * x
|
197
|
+
bnrm = np.linalg.norm(badd)
|
198
|
+
if not np.isfinite(bnrm) or bnrm == 0:
|
199
|
+
continue
|
200
|
+
badd /= bnrm
|
201
|
+
xadd /= bnrm
|
202
|
+
x_data.append(xadd)
|
203
|
+
b_data.append(badd)
|
204
|
+
|
205
|
+
if rhs.ndim == 1:
|
206
|
+
return sol.flatten()
|
207
|
+
else:
|
208
|
+
return sol
|
209
|
+
|
210
|
+
def solve(self, rhs, x0=None, trans='N'):
|
211
|
+
r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by performing a modified
|
212
|
+
Gram-Schmidt over the previously calculated solutions :math:`\mathbf{U}` and corresponding right-hand-sides
|
213
|
+
:math:`\mathbf{F}`. This is used to construct an approximate solution
|
214
|
+
:math:`\tilde{\mathbf{x}} = \sum_k \alpha_k \mathbf{u}_k` in the subspace of :math:`\mathbf{U}`.
|
215
|
+
If the residual of :math:`\mathbf{A} \tilde{\mathbf{x}} = \mathbf{b}` is above the tolerance, a new solution
|
216
|
+
:math:`\mathbf{u}_{k+1}` will be added to the database such that
|
217
|
+
:math:`\mathbf{x} = \tilde{\mathbf{x}}+\mathbf{u}_{k+1}` is the solution to the system
|
218
|
+
:math:`\mathbf{A} \mathbf{x} = \mathbf{b}`.
|
219
|
+
|
220
|
+
The right-hand-side :math:`\mathbf{b}` can be of size ``(N)`` or ``(N, K)``, where ``N`` is the size of matrix
|
221
|
+
:math:`\mathbf{A}` and ``K`` is the number of right-hand sides.
|
222
|
+
"""
|
223
|
+
''' Required Symm. matrix Herm. matrix Any matrix (uses adjoint storage)
|
224
|
+
A^T = A A^H = A
|
225
|
+
A x = b
|
226
|
+
A^T x = b A x = b A x^* = b^* A^H x^* = b^*
|
227
|
+
A^H x = b A x^* = b^* A x = b A^H x = b
|
228
|
+
|
229
|
+
For symmetric or Hermitian matrices, only the normal storage is required. For any other matrix, the `T` and
|
230
|
+
`H` mode will require the adjoint storage space.
|
231
|
+
'''
|
232
|
+
if trans not in ['N', 'T', 'H']:
|
233
|
+
raise TypeError("Only N, T, or H transposition is possible")
|
234
|
+
|
235
|
+
# Use adjoint storage?
|
236
|
+
adjoint_mode = trans != 'N' and not (self.symmetric or self.hermitian)
|
237
|
+
# Use conjugation?
|
238
|
+
conj_mode = self.symmetric and trans == 'H' or not self.symmetric and trans == 'T'
|
239
|
+
|
240
|
+
storage = 'H' if adjoint_mode else 'N'
|
241
|
+
rhs = rhs.conj() if conj_mode else rhs
|
242
|
+
if storage == 'N': # Use the normal storage
|
243
|
+
ret = self._do_solve_1rhs(self.A, rhs,
|
244
|
+
self.x_stored, self.b_stored,
|
245
|
+
lambda b, x_init: self.solver.solve(b, trans='N', x0=x_init),
|
246
|
+
x0=x0)
|
247
|
+
elif storage == 'H': # Use adjoint storage
|
248
|
+
ret = self._do_solve_1rhs(self.A.conj().T, rhs,
|
249
|
+
self.xadj_stored, self.badj_stored,
|
250
|
+
lambda b, x_init: self.solver.solve(b, trans='H', x0=x_init),
|
251
|
+
x0=x0)
|
252
|
+
|
253
|
+
return ret.conj() if conj_mode else ret
|
@@ -2,7 +2,8 @@ import warnings
|
|
2
2
|
import numpy as np
|
3
3
|
import scipy.sparse as sps
|
4
4
|
from scipy.sparse import SparseEfficiencyWarning
|
5
|
-
from .
|
5
|
+
from .matrix_checks import matrix_is_hermitian, matrix_is_complex, matrix_is_symmetric
|
6
|
+
from .solvers import LinearSolver
|
6
7
|
|
7
8
|
# ------------------------------------ Pardiso Solver -----------------------------------
|
8
9
|
try:
|
@@ -126,26 +127,31 @@ class SolverSparsePardiso(LinearSolver):
|
|
126
127
|
|
127
128
|
self._pardiso_solver.factorize(A)
|
128
129
|
|
129
|
-
def solve(self, b):
|
130
|
+
def solve(self, b, x0=None, trans='N'):
|
130
131
|
""" solve Ax=b for x
|
131
132
|
|
132
133
|
Args:
|
133
134
|
A (scipy.sparse.csr.csr_matrix): sparse square CSR matrix , CSC matrix also possible
|
134
135
|
b (numpy.ndarray): right-hand side(s), b.shape[0] needs to be the same as A.shape[0]
|
136
|
+
x0 (unused)
|
137
|
+
trans (optional): Indicate which system to solve (Normal, Transposed, or Hermitian transposed)
|
135
138
|
|
136
139
|
Returns:
|
137
140
|
Solution of the system of linear equations, same shape as input b
|
138
141
|
"""
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
142
|
+
if trans == 'N':
|
143
|
+
return self._pardiso_solver.solve(self.A, b)
|
144
|
+
elif trans == 'T' or trans == 'H':
|
145
|
+
# T and H are the same, because only real matrix is supported
|
146
|
+
# Cannot use _pardiso_solver.solve because it changes flag 12 internally
|
147
|
+
iparm_prev = self._pardiso_solver.get_iparm(12)
|
148
|
+
self._pardiso_solver.set_iparm(12, int(not iparm_prev)) # Adjoint solver (transpose)
|
149
|
+
b = self._pardiso_solver._check_b(self.A, b)
|
150
|
+
x = self._pardiso_solver._call_pardiso(self.A, b)
|
151
|
+
self._pardiso_solver.set_iparm(12, iparm_prev) # Revert back to normal solver
|
152
|
+
return x
|
153
|
+
else:
|
154
|
+
raise TypeError("Only N, T, or H transposition is possible")
|
149
155
|
|
150
156
|
def _print_iparm(self):
|
151
157
|
""" Print all iparm settings to console """
|
@@ -219,17 +225,16 @@ class SolverSparseLU(LinearSolver):
|
|
219
225
|
self.inv = splu(A)
|
220
226
|
return self
|
221
227
|
|
222
|
-
def solve(self, rhs):
|
228
|
+
def solve(self, rhs, x0=None, trans='N'):
|
223
229
|
r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by forward and backward
|
224
230
|
substitution of :math:`\mathbf{x} = \mathbf{U}^{-1}\mathbf{L}^{-1}\mathbf{b}`.
|
225
|
-
"""
|
226
|
-
return self.inv.solve(rhs)
|
227
231
|
|
228
|
-
|
229
|
-
|
230
|
-
backward substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{U}^{-\text{H}}\mathbf{b}`.
|
232
|
+
Adjoint system solves the linear system of equations :math:`\mathbf{A}^\text{H}\mathbf{x} = \mathbf{b}` by
|
233
|
+
forward and backward substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{U}^{-\text{H}}\mathbf{b}`
|
231
234
|
"""
|
232
|
-
|
235
|
+
if trans not in ['N', 'T', 'H']:
|
236
|
+
raise TypeError("Only N, T, or H transposition is possible")
|
237
|
+
return self.inv.solve(rhs, trans=trans)
|
233
238
|
|
234
239
|
|
235
240
|
# ------------------------------------ Cholesky Solver scikit-sparse -----------------------------------
|
@@ -281,7 +286,7 @@ class SolverSparseCholeskyScikit(LinearSolver):
|
|
281
286
|
|
282
287
|
return self
|
283
288
|
|
284
|
-
def solve(self, rhs):
|
289
|
+
def solve(self, rhs, x0=None, trans='N'):
|
285
290
|
r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by forward and backward
|
286
291
|
substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{L}^{-1}\mathbf{b}` in case of an
|
287
292
|
Hermitian matrix.
|
@@ -290,10 +295,12 @@ class SolverSparseCholeskyScikit(LinearSolver):
|
|
290
295
|
:math:`\mathbf{A}` and ``K`` is the number of right-hand sides.
|
291
296
|
|
292
297
|
"""
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
298
|
+
if trans not in ['N', 'T', 'H']:
|
299
|
+
raise TypeError("Only N, T, or H transposition is possible")
|
300
|
+
if trans == 'T':
|
301
|
+
return self.inv(rhs.conj()).conj()
|
302
|
+
else:
|
303
|
+
return self.inv(rhs)
|
297
304
|
|
298
305
|
|
299
306
|
# ------------------------------------ Cholesky Solver cvxopt -----------------------------------
|
@@ -344,15 +351,20 @@ class SolverSparseCholeskyCVXOPT(LinearSolver):
|
|
344
351
|
|
345
352
|
return self
|
346
353
|
|
347
|
-
def solve(self, rhs):
|
354
|
+
def solve(self, rhs, x0=None, trans='N'):
|
348
355
|
r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by forward and backward
|
349
356
|
substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{L}^{-1}\mathbf{b}`. """
|
357
|
+
if trans not in ['N', 'T', 'H']:
|
358
|
+
raise TypeError("Only N, T, or H transposition is possible")
|
350
359
|
if rhs.dtype != self._dtype:
|
351
360
|
warnings.warn(f"{type(self).__name__}: Type warning: rhs value type ({rhs.dtype}) is converted to {self._dtype}")
|
352
|
-
|
361
|
+
if trans == 'T':
|
362
|
+
B = cvxopt.matrix(rhs.conj().astype(self._dtype))
|
363
|
+
else:
|
364
|
+
B = cvxopt.matrix(rhs.astype(self._dtype))
|
353
365
|
nrhs = 1 if rhs.ndim == 1 else rhs.shape[1]
|
366
|
+
|
354
367
|
cvxopt.cholmod.solve(self.inv, B, nrhs=nrhs)
|
355
|
-
return np.array(B).flatten() if nrhs == 1 else np.array(B)
|
356
368
|
|
357
|
-
|
358
|
-
return
|
369
|
+
x = np.array(B).flatten() if rhs.ndim == 1 else np.array(B)
|
370
|
+
return x.conj() if trans == 'T' else x
|
pyMOTO-1.2.1.dist-info/RECORD
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
pymoto/__init__.py,sha256=V3OzqL_4NDy7lzIv_8tvu6s-8qS4bUwczd6XoH75M3s,2196
|
2
|
-
pymoto/core_objects.py,sha256=9TjGunvGVwa-LqM2tmE1XlWnqHDscZLeqbKFBZ7ZroU,25111
|
3
|
-
pymoto/routines.py,sha256=pMJlEFXa413XbqvbJuw3bZTNGQJ4Al-BRdAy_Es_M2g,14360
|
4
|
-
pymoto/utils.py,sha256=YJ-PNLJLc12Yx6TYCrEechS2aaBRx0o4mTM1soeeyz0,1122
|
5
|
-
pymoto/common/domain.py,sha256=_VWgm0sjMDsan_GiKnwmpJqZcuksgDU6UTdZYMTSi98,15153
|
6
|
-
pymoto/common/dyadcarrier.py,sha256=VwLJnOq1omfMX2udG6DMHOkD3AsIB05LTpDY7veYXcc,17136
|
7
|
-
pymoto/common/mma.py,sha256=W1Z0h5f3a9BP8nFIlCdahDCIHT4XrcDcDyE6Y1Brq3k,23318
|
8
|
-
pymoto/common/solvers.py,sha256=U7XNMSyHhp0fiZ8ASo1guUb-CHGygik7A4lfLOnh07c,8316
|
9
|
-
pymoto/common/solvers_dense.py,sha256=vuBUp3y4qJLwmsXbFQ_tEb-7LqqCEemFunTn1z_Qu0U,9901
|
10
|
-
pymoto/common/solvers_sparse.py,sha256=QVbGTwGtbhOqRUyg2gHmY-K5haiPbskGA6uj_g-dKz8,15776
|
11
|
-
pymoto/modules/assembly.py,sha256=i0zwigijmsJRR3aHZXSIzlDcYMbCWxKW4fe3NqjW8ew,11540
|
12
|
-
pymoto/modules/autodiff.py,sha256=WAfoAOHBSozf7jbr9gQz9Vw4a_2G9wGJxLMMqUQP0Co,1684
|
13
|
-
pymoto/modules/complex.py,sha256=vwzqRo5W319mVf_RqbB7LpYe7jXruVxa3ZV560Iq39k,4421
|
14
|
-
pymoto/modules/filter.py,sha256=8A-dmWSFEqFyQcutjFv__pfgAwszCVZeZgLxuG9hi0g,18840
|
15
|
-
pymoto/modules/generic.py,sha256=27EuDMfUtWkkwEqkfbHMCRlHkt6wcV40aUQKfhL2xKI,9783
|
16
|
-
pymoto/modules/io.py,sha256=4k5S-YQHKhw_HwmqOoYQWFEzdcL5nMJ5fVD2FJFqpFg,10532
|
17
|
-
pymoto/modules/linalg.py,sha256=j8bZfjo5Il_vbdEHr2BhWB3e7OssYVovieoN54zygx8,24266
|
18
|
-
pymoto/modules/scaling.py,sha256=hK3sfCoAoseabjqdn5VXe6aGA_fV-MRmMtiv4uIg_I4,2252
|
19
|
-
pyMOTO-1.2.1.dist-info/LICENSE,sha256=ZXMC2Txpzs-dBwz9Me4_1rQCSVl4P1B27MomNi43F30,1072
|
20
|
-
pyMOTO-1.2.1.dist-info/METADATA,sha256=YKmcOzqRFlAObECUODa5oZgcIG9r7GRhjn86x12_8fk,4907
|
21
|
-
pyMOTO-1.2.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
22
|
-
pyMOTO-1.2.1.dist-info/top_level.txt,sha256=EdvAUSmFMaiqhuEZW8jxANMiK-LdPtlmDWL6SfmCdUU,7
|
23
|
-
pyMOTO-1.2.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
24
|
-
pyMOTO-1.2.1.dist-info/RECORD,,
|
pymoto/common/solvers.py
DELETED
@@ -1,236 +0,0 @@
|
|
1
|
-
import numpy as np
|
2
|
-
import scipy.sparse as sps
|
3
|
-
try:
|
4
|
-
import cvxopt
|
5
|
-
_has_cvxopt = True
|
6
|
-
except ImportError:
|
7
|
-
_has_cvxopt = False
|
8
|
-
|
9
|
-
|
10
|
-
def is_cvxopt_spmatrix(A):
|
11
|
-
""" Checks if the argument is a cvxopt sparse matrix """
|
12
|
-
return isinstance(A, cvxopt.spmatrix) if _has_cvxopt else False
|
13
|
-
|
14
|
-
|
15
|
-
def matrix_is_complex(A):
|
16
|
-
""" Checks if the matrix is complex """
|
17
|
-
if is_cvxopt_spmatrix(A):
|
18
|
-
return A.typecode == 'z'
|
19
|
-
else:
|
20
|
-
return np.iscomplexobj(A)
|
21
|
-
|
22
|
-
|
23
|
-
def matrix_is_diagonal(A):
|
24
|
-
""" Checks if the matrix is diagonal"""
|
25
|
-
if sps.issparse(A):
|
26
|
-
if isinstance(A, sps.dia_matrix):
|
27
|
-
return len(A.offsets) == 1 and A.offsets[0] == 0
|
28
|
-
else:
|
29
|
-
return np.allclose((A - sps.spdiags(A.diagonal(), 0, *A.shape)).data, 0.0)
|
30
|
-
elif is_cvxopt_spmatrix(A):
|
31
|
-
return max(abs(A.I - A.J)) == 0
|
32
|
-
else:
|
33
|
-
return np.allclose(A, np.diag(np.diag(A)))
|
34
|
-
|
35
|
-
|
36
|
-
def matrix_is_symmetric(A):
|
37
|
-
""" Checks whether a matrix is numerically symmetric """
|
38
|
-
if sps.issparse(A):
|
39
|
-
return np.allclose((A-A.T).data, 0)
|
40
|
-
elif is_cvxopt_spmatrix(A):
|
41
|
-
return np.isclose(max(abs(A-A.T)), 0.0)
|
42
|
-
else:
|
43
|
-
return np.allclose(A, A.T)
|
44
|
-
|
45
|
-
|
46
|
-
def matrix_is_hermitian(A):
|
47
|
-
""" Checks whether a matrix is numerically Hermitian """
|
48
|
-
if matrix_is_complex(A):
|
49
|
-
if sps.issparse(A):
|
50
|
-
return np.allclose((A-A.T.conj()).data, 0)
|
51
|
-
elif is_cvxopt_spmatrix(A):
|
52
|
-
return np.isclose(max(abs(A-A.ctrans())), 0.0)
|
53
|
-
else:
|
54
|
-
return np.allclose(A, A.T.conj())
|
55
|
-
else:
|
56
|
-
return matrix_is_symmetric(A)
|
57
|
-
|
58
|
-
|
59
|
-
class LinearSolver:
|
60
|
-
""" Base class of all linear solvers
|
61
|
-
|
62
|
-
Keyword Args:
|
63
|
-
A (matrix): Optionally provide a matrix, which is used in :method:`update` right away.
|
64
|
-
|
65
|
-
Attributes:
|
66
|
-
defined (bool): Flag if the solver is able to run, e.g. false if some dependent library is not available
|
67
|
-
"""
|
68
|
-
|
69
|
-
defined = True
|
70
|
-
_err_msg = ""
|
71
|
-
|
72
|
-
def __init__(self, A=None):
|
73
|
-
if A is not None:
|
74
|
-
self.update(A)
|
75
|
-
|
76
|
-
def update(self, A):
|
77
|
-
""" Updates with a new matrix of the same structure
|
78
|
-
|
79
|
-
Args:
|
80
|
-
A (matrix): The new matrix of size ``(N, N)``
|
81
|
-
|
82
|
-
Returns:
|
83
|
-
self
|
84
|
-
"""
|
85
|
-
raise NotImplementedError(f"Solver not implemented {self._err_msg}")
|
86
|
-
|
87
|
-
def solve(self, rhs):
|
88
|
-
r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}`
|
89
|
-
|
90
|
-
Args:
|
91
|
-
rhs: Right hand side :math:`\mathbf{b}` of shape ``(N)`` or ``(N, K)`` for multiple right-hand-sides
|
92
|
-
|
93
|
-
Returns:
|
94
|
-
Solution vector :math:`\mathbf{x}` of same shape as :math:`\mathbf{b}`
|
95
|
-
"""
|
96
|
-
raise NotImplementedError(f"Solver not implemented {self._err_msg}")
|
97
|
-
|
98
|
-
def adjoint(self, rhs):
|
99
|
-
r""" Solves the adjoint linear system of equations
|
100
|
-
|
101
|
-
The system of equations is :math:`\mathbf{A}^\text{H} \mathbf{x} = \mathbf{b}` (conjugate transpose) in case of
|
102
|
-
complex matrix or :math:`\mathbf{A}^\text{T} \mathbf{x} = \mathbf{b}` for a real-valued matrix.
|
103
|
-
|
104
|
-
Args:
|
105
|
-
rhs: Right hand side :math:`\mathbf{b}` of shape ``(N)`` or ``(N, K)`` for multiple right-hand-sides
|
106
|
-
|
107
|
-
Returns:
|
108
|
-
Solution vector :math:`\mathbf{x}` of same shape as :math:`\mathbf{b}`
|
109
|
-
"""
|
110
|
-
raise NotImplementedError(f"Solver not implemented {self._err_msg}")
|
111
|
-
|
112
|
-
@staticmethod
|
113
|
-
def residual(A, x, b):
|
114
|
-
r""" Calculates the (relative) residual of the linear system of equations
|
115
|
-
|
116
|
-
The residual is calculated as
|
117
|
-
:math:`r = \frac{\left| \mathbf{A} \mathbf{x} - \mathbf{b} \right|}{\left| \mathbf{b} \right|}`
|
118
|
-
|
119
|
-
Args:
|
120
|
-
A: The matrix
|
121
|
-
x: Solution vector
|
122
|
-
b: Right-hand side
|
123
|
-
|
124
|
-
Returns:
|
125
|
-
Residual value
|
126
|
-
"""
|
127
|
-
return np.linalg.norm(A@x - b) / np.linalg.norm(b)
|
128
|
-
|
129
|
-
|
130
|
-
class LDAWrapper(LinearSolver):
|
131
|
-
r""" Linear dependency aware solver (LDAS)
|
132
|
-
|
133
|
-
This solver uses previous solutions of the system :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` to reduce computational
|
134
|
-
effort. In case the solution :math:`\mathbf{x}` is linearly dependent on the previous solutions, the solution
|
135
|
-
will be nearly free of cost.
|
136
|
-
|
137
|
-
Args:
|
138
|
-
solver: The internal solver to be used
|
139
|
-
tol (optional): Residual tolerance above which the internal solver is used to add a new solution vector.
|
140
|
-
A (optional): The matrix :math:`\mathbf{A}`
|
141
|
-
|
142
|
-
References:
|
143
|
-
|
144
|
-
Koppen, S., van der Kolk, M., van den Boom, S., & Langelaar, M. (2022).
|
145
|
-
Efficient computation of states and sensitivities for compound structural optimisation problems using a Linear Dependency Aware Solver (LDAS).
|
146
|
-
Structural and Multidisciplinary Optimization, 65(9), 273.
|
147
|
-
DOI: 10.1007/s00158-022-03378-8
|
148
|
-
"""
|
149
|
-
def __init__(self, solver: LinearSolver, tol=1e-8, A=None, hermitian=False, symmetric=False):
|
150
|
-
self.solver = solver
|
151
|
-
self.tol = tol
|
152
|
-
self.x_stored = []
|
153
|
-
self.b_stored = []
|
154
|
-
self.xadj_stored = []
|
155
|
-
self.badj_stored = []
|
156
|
-
self.A = None
|
157
|
-
self._did_solve = False # For debugging purposes
|
158
|
-
self._last_rtol = 0.
|
159
|
-
self.hermitian = hermitian
|
160
|
-
self.symmetric = symmetric
|
161
|
-
super().__init__(A)
|
162
|
-
|
163
|
-
def update(self, A):
|
164
|
-
""" Clear the internal stored solution vectors and update the internal ``solver`` """
|
165
|
-
self.A = A
|
166
|
-
self.x_stored.clear()
|
167
|
-
self.b_stored.clear()
|
168
|
-
self.xadj_stored.clear()
|
169
|
-
self.badj_stored.clear()
|
170
|
-
self.solver.update(A)
|
171
|
-
|
172
|
-
def _do_solve_1rhs(self, A, rhs, x_data, b_data, solve_fn):
|
173
|
-
rhs_loc = rhs.copy()
|
174
|
-
sol = 0
|
175
|
-
|
176
|
-
# Check linear dependencies in the rhs using modified Gram-Schmidt
|
177
|
-
for (x, b) in zip(x_data, b_data):
|
178
|
-
alpha = rhs_loc.conj() @ b / (b.conj() @ b)
|
179
|
-
rhs_loc -= alpha * b
|
180
|
-
sol += alpha * x
|
181
|
-
|
182
|
-
# Check tolerance
|
183
|
-
self._last_rtol = 1.0 if len(x_data) == 0 else self.residual(A, sol, rhs)
|
184
|
-
|
185
|
-
if self._last_rtol > self.tol:
|
186
|
-
# Calculate a new solution
|
187
|
-
xnew = solve_fn(rhs_loc)
|
188
|
-
x_data.append(xnew)
|
189
|
-
b_data.append(rhs_loc)
|
190
|
-
sol += xnew
|
191
|
-
self._did_solve = True
|
192
|
-
else:
|
193
|
-
self._did_solve = False
|
194
|
-
|
195
|
-
return sol
|
196
|
-
|
197
|
-
def _solve_1x(self, b):
|
198
|
-
return self._do_solve_1rhs(self.A, b, self.x_stored, self.b_stored, self.solver.solve)
|
199
|
-
|
200
|
-
def _adjoint_1x(self, b):
|
201
|
-
return self._do_solve_1rhs(self.A.conj().T, b, self.xadj_stored, self.badj_stored, self.solver.adjoint)
|
202
|
-
|
203
|
-
def solve(self, rhs):
|
204
|
-
r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by performing a modified
|
205
|
-
Gram-Schmidt over the previously calculated solutions :math:`\mathbf{U}` and corresponding right-hand-sides
|
206
|
-
:math:`\mathbf{F}`. This is used to construct an approximate solution
|
207
|
-
:math:`\tilde{\mathbf{x}} = \sum_k \alpha_k \mathbf{u}_k` in the subspace of :math:`\mathbf{U}`.
|
208
|
-
If the residual of :math:`\mathbf{A} \tilde{\mathbf{x}} = \mathbf{b}` is above the tolerance, a new solution
|
209
|
-
:math:`\mathbf{u}_{k+1}` will be added to the database such that
|
210
|
-
:math:`\mathbf{x} = \tilde{\mathbf{x}}+\mathbf{u}_{k+1}` is the solution to the system
|
211
|
-
:math:`\mathbf{A} \mathbf{x} = \mathbf{b}`.
|
212
|
-
|
213
|
-
The right-hand-side :math:`\mathbf{b}` can be of size ``(N)`` or ``(N, K)``, where ``N`` is the size of matrix
|
214
|
-
:math:`\mathbf{A}` and ``K`` is the number of right-hand sides.
|
215
|
-
"""
|
216
|
-
if rhs.ndim == 1:
|
217
|
-
return self._solve_1x(rhs)
|
218
|
-
else: # Multiple rhs
|
219
|
-
sol = []
|
220
|
-
for i in range(rhs.shape[-1]):
|
221
|
-
sol.append(self._solve_1x(rhs[..., i]))
|
222
|
-
return np.stack(sol, axis=-1)
|
223
|
-
|
224
|
-
def adjoint(self, rhs):
|
225
|
-
if self.hermitian:
|
226
|
-
return self.solve(rhs)
|
227
|
-
elif self.symmetric:
|
228
|
-
return self.solve(rhs.conj()).conj()
|
229
|
-
else:
|
230
|
-
if rhs.ndim == 1:
|
231
|
-
return self._adjoint_1x(rhs)
|
232
|
-
else: # Multiple rhs
|
233
|
-
sol = []
|
234
|
-
for i in range(rhs.shape[-1]):
|
235
|
-
sol.append(self._adjoint_1x(rhs[..., i]))
|
236
|
-
return np.stack(sol, axis=-1)
|
File without changes
|
File without changes
|
File without changes
|