pyMOTO 1.3.0__py3-none-any.whl → 1.5.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.
@@ -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 .solvers import matrix_is_hermitian, matrix_is_complex, matrix_is_symmetric, LinearSolver
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:
@@ -125,27 +126,33 @@ class SolverSparsePardiso(LinearSolver):
125
126
  self._pardiso_solver = PyPardisoSolver(mtype=self._mtype)
126
127
 
127
128
  self._pardiso_solver.factorize(A)
129
+ self._pardiso_solver.set_phase(33)
128
130
 
129
- def solve(self, b):
131
+ def solve(self, b, x0=None, trans='N'):
130
132
  """ solve Ax=b for x
131
133
 
132
134
  Args:
133
135
  A (scipy.sparse.csr.csr_matrix): sparse square CSR matrix , CSC matrix also possible
134
136
  b (numpy.ndarray): right-hand side(s), b.shape[0] needs to be the same as A.shape[0]
137
+ x0 (unused)
138
+ trans (optional): Indicate which system to solve (Normal, Transposed, or Hermitian transposed)
135
139
 
136
140
  Returns:
137
141
  Solution of the system of linear equations, same shape as input b
138
142
  """
139
- return self._pardiso_solver.solve(self.A, b)
140
-
141
- def adjoint(self, b):
142
- # Cannot use _pardiso_solver.solve because it changes flag 12 internally
143
- iparm_prev = self._pardiso_solver.get_iparm(12)
144
- self._pardiso_solver.set_iparm(12, int(not iparm_prev)) # Adjoint solver (transpose)
145
- b = self._pardiso_solver._check_b(self.A, b)
146
- x = self._pardiso_solver._call_pardiso(self.A, b)
147
- self._pardiso_solver.set_iparm(12, iparm_prev) # Revert back to normal solver
148
- return x
143
+ if trans == 'N':
144
+ return self._pardiso_solver.solve(self.A, b)
145
+ elif trans == 'T' or trans == 'H':
146
+ # T and H are the same, because only real matrix is supported
147
+ # Cannot use _pardiso_solver.solve because it changes flag 12 internally
148
+ iparm_prev = self._pardiso_solver.get_iparm(12)
149
+ self._pardiso_solver.set_iparm(12, int(not iparm_prev)) # Adjoint solver (transpose)
150
+ b = self._pardiso_solver._check_b(self.A, b)
151
+ x = self._pardiso_solver._call_pardiso(self.A, b)
152
+ self._pardiso_solver.set_iparm(12, iparm_prev) # Revert back to normal solver
153
+ return x
154
+ else:
155
+ raise TypeError("Only N, T, or H transposition is possible")
149
156
 
150
157
  def _print_iparm(self):
151
158
  """ Print all iparm settings to console """
@@ -219,17 +226,16 @@ class SolverSparseLU(LinearSolver):
219
226
  self.inv = splu(A)
220
227
  return self
221
228
 
222
- def solve(self, rhs):
229
+ def solve(self, rhs, x0=None, trans='N'):
223
230
  r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by forward and backward
224
231
  substitution of :math:`\mathbf{x} = \mathbf{U}^{-1}\mathbf{L}^{-1}\mathbf{b}`.
225
- """
226
- return self.inv.solve(rhs)
227
232
 
228
- def adjoint(self, rhs):
229
- r""" Solves the linear system of equations :math:`\mathbf{A}^\text{H}\mathbf{x} = \mathbf{b}` by forward and
230
- backward substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{U}^{-\text{H}}\mathbf{b}`.
233
+ Adjoint system solves the linear system of equations :math:`\mathbf{A}^\text{H}\mathbf{x} = \mathbf{b}` by
234
+ forward and backward substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{U}^{-\text{H}}\mathbf{b}`
231
235
  """
232
- return self.inv.solve(rhs, trans=('H' if self.iscomplex else 'T'))
236
+ if trans not in ['N', 'T', 'H']:
237
+ raise TypeError("Only N, T, or H transposition is possible")
238
+ return self.inv.solve(rhs, trans=trans)
233
239
 
234
240
 
235
241
  # ------------------------------------ Cholesky Solver scikit-sparse -----------------------------------
@@ -281,7 +287,7 @@ class SolverSparseCholeskyScikit(LinearSolver):
281
287
 
282
288
  return self
283
289
 
284
- def solve(self, rhs):
290
+ def solve(self, rhs, x0=None, trans='N'):
285
291
  r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by forward and backward
286
292
  substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{L}^{-1}\mathbf{b}` in case of an
287
293
  Hermitian matrix.
@@ -290,10 +296,12 @@ class SolverSparseCholeskyScikit(LinearSolver):
290
296
  :math:`\mathbf{A}` and ``K`` is the number of right-hand sides.
291
297
 
292
298
  """
293
- return self.inv(rhs)
294
-
295
- def adjoint(self, rhs):
296
- return self.solve(rhs)
299
+ if trans not in ['N', 'T', 'H']:
300
+ raise TypeError("Only N, T, or H transposition is possible")
301
+ if trans == 'T':
302
+ return self.inv(rhs.conj()).conj()
303
+ else:
304
+ return self.inv(rhs)
297
305
 
298
306
 
299
307
  # ------------------------------------ Cholesky Solver cvxopt -----------------------------------
@@ -344,15 +352,20 @@ class SolverSparseCholeskyCVXOPT(LinearSolver):
344
352
 
345
353
  return self
346
354
 
347
- def solve(self, rhs):
355
+ def solve(self, rhs, x0=None, trans='N'):
348
356
  r""" Solves the linear system of equations :math:`\mathbf{A} \mathbf{x} = \mathbf{b}` by forward and backward
349
357
  substitution of :math:`\mathbf{x} = \mathbf{L}^{-\text{H}}\mathbf{L}^{-1}\mathbf{b}`. """
358
+ if trans not in ['N', 'T', 'H']:
359
+ raise TypeError("Only N, T, or H transposition is possible")
350
360
  if rhs.dtype != self._dtype:
351
361
  warnings.warn(f"{type(self).__name__}: Type warning: rhs value type ({rhs.dtype}) is converted to {self._dtype}")
352
- B = cvxopt.matrix(rhs.astype(self._dtype))
362
+ if trans == 'T':
363
+ B = cvxopt.matrix(rhs.conj().astype(self._dtype))
364
+ else:
365
+ B = cvxopt.matrix(rhs.astype(self._dtype))
353
366
  nrhs = 1 if rhs.ndim == 1 else rhs.shape[1]
367
+
354
368
  cvxopt.cholmod.solve(self.inv, B, nrhs=nrhs)
355
- return np.array(B).flatten() if nrhs == 1 else np.array(B)
356
369
 
357
- def adjoint(self, rhs):
358
- return self.solve(rhs)
370
+ x = np.array(B).flatten() if rhs.ndim == 1 else np.array(B)
371
+ return x.conj() if trans == 'T' else x
@@ -1,24 +0,0 @@
1
- pymoto/__init__.py,sha256=Uy-TOgGg2Ztv2B-HfznVYApXYyvJ3k6a6gm2o3I_5Rg,2232
2
- pymoto/core_objects.py,sha256=9TjGunvGVwa-LqM2tmE1XlWnqHDscZLeqbKFBZ7ZroU,25111
3
- pymoto/routines.py,sha256=zcwAlq3ytfjhPJqzvHdvW4nTyzmjdgoVSX5j5vDRExw,14638
4
- pymoto/utils.py,sha256=YJ-PNLJLc12Yx6TYCrEechS2aaBRx0o4mTM1soeeyz0,1122
5
- pymoto/common/domain.py,sha256=79BNR3PKOGOJbUZVyKlnQUbctAbHetO3hRV4vPSl17A,15899
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=0jRSFbfhEQtOW7GPLObXAOQEjC91LeZ9spdK25vD6lA,12953
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=y9KO7GVnoxpGpJLTMUNZhK4jVUTpWGTnOWI3RBfBeuE,23942
18
- pymoto/modules/scaling.py,sha256=hK3sfCoAoseabjqdn5VXe6aGA_fV-MRmMtiv4uIg_I4,2252
19
- pyMOTO-1.3.0.dist-info/LICENSE,sha256=ZXMC2Txpzs-dBwz9Me4_1rQCSVl4P1B27MomNi43F30,1072
20
- pyMOTO-1.3.0.dist-info/METADATA,sha256=KJvLKj58DMxrc_mJKTy5ZwyU3reFi76MeILIRynh4JY,4907
21
- pyMOTO-1.3.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
22
- pyMOTO-1.3.0.dist-info/top_level.txt,sha256=EdvAUSmFMaiqhuEZW8jxANMiK-LdPtlmDWL6SfmCdUU,7
23
- pyMOTO-1.3.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
24
- pyMOTO-1.3.0.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)