forked from 1iyiwei/topopt
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathfesolvers.py
312 lines (260 loc) · 11 KB
/
fesolvers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
"""
Finite element solvers for the displacement from stiffness matrix, force and
adjoint vector. This version of the code is meant for stress intensity
minimization.
Bram Lagerweij
Aerospace Structures and Materials Department TU Delft
2018
"""
# Importing modules for parent class
import numpy as np
from scipy.sparse import coo_matrix
# Importing linear algabra solver for the CvxFEA class
import cvxopt
import cvxopt.cholmod
# Importing linear algabra solver for the SciPyFEA class
from scipy.sparse.linalg import spsolve
# Imporning linear algabla conjugate gradient solver
from scipy.sparse.linalg import cg
from scipy.sparse import diags
# coo_matrix should be faster
class FESolver(object):
"""
This parent FEA class can only assemble the global stiffness matrix and
exclude all fixed degrees of freedom from it. This stiffenss csc-sparse
stiffness matrix is assebled in the gk_freedof method. This
class solves the FE problem with a sparse LU-solver based upon umfpack.
This solver is slow and inefficient. It is however more robust.
For this local compliance (actuator) maximization this solver solves two
problems, the equalibrum and the adjoint problem which will be
required to compute the gradients.
Parameters
----------
verbose : bool, optional
False if the FEA should not print updates
Attributes
----------
verbose : bool
False if the FEA should not print updates.
"""
def __init__(self, verbose=False):
self.verbose = verbose
# finite element computation for displacement adjoint
def displace(self, load, x, ke, kmin, penal):
"""
FE solver based upon the sparse SciPy solver that uses umfpack.
Parameters
----------
load : object, child of the Loads class
The loadcase(s) considerd for this optimisation problem.
x : 2-D array size(nely, nelx)
Current density distribution.
ke : 2-D array size(8, 8)
Local fully dense stiffnes matrix.
kmin : 2-D array size(8, 8)
Local stiffness matrix for an empty element.
penal : float
Material model penalisation (SIMP).
Returns
-------
u : 1-D column array shape(max(edof), 1)
The displacement vector.
lamba : 1-D column array shape(max(edof), 1)
Adjoint equation solution.
"""
freedofs = np.array(load.freedofs())
nely, nelx = x.shape
f = load.force()
l = load.kiloc()
f_free = np.hstack((f[freedofs], l[freedofs]))
k_free = self.gk_freedofs(load, x, ke, kmin, penal)
# solving the system f = Ku with scipy
num_dof = load.num_dofs
u = np.zeros((num_dof, 1))
lamba = np.zeros((num_dof, 1))
res = spsolve(k_free, f_free)
u[freedofs] = res[:, 0].reshape(len(freedofs), 1)
lamba[freedofs] = res[:, 1].reshape(len(freedofs), 1)
return u, lamba
# sparce stiffness matrix assembly
def gk_freedofs(self, load, x, ke, kmin, penal):
"""
Generates the global stiffness matrix with deleted fixed degrees of
freedom. It generates a list with stiffness values and their x and y
indices in the global stiffness matrix. Some combination of x and y
appear multiple times as the degree of freedom might apear in multiple
elements of the FEA. The SciPy coo_matrix function adds them up at the
background. At the location of the force introduction and displacement
output an external stiffness is added due to stability reasons.
Parameters
----------
load : object, child of the Loads class
The loadcase(s) considerd for this optimisation problem.
x : 2-D array size(nely, nelx)
Current density distribution.
ke : list len(nelx*nely)
List with all element stiffness matrixes for full dense material.
kmin : list len(nelx*nely)
List with all element stiffness matrixes for empty material.
penal : float
Material model penalisation (SIMP).
Returns
-------
k : 2-D sparse csc matrix
Global stiffness matrix without fixed degrees of freedom.
"""
freedofs = np.array(load.freedofs())
nelx = load.nelx
nely = load.nely
# SIMP - Ee(xe) = Emin + x^p (E-Emin)
kd = x.T.reshape(nelx*nely) ** penal # knockdown factor
value_list = [kmini + kdi*(kei - kmini) for kei, kmini, kdi in zip(ke, kmin, kd)]
value_list = [item for sublist in value_list for subsublist in sublist for item in subsublist]
value_list = np.array(value_list)
# coo_matrix sums duplicated entries and sipmlyies slicing
dof = load.num_dofs
k = coo_matrix((value_list, (load.y_list, load.x_list)), shape=(dof, dof)).tocsc()
# adding external spring stiffness to load and actuator locations
loc_force = np.where(load.force() != 0)[0]
loc_ki = np.where(load.kiloc() != 0)[0]
k[loc_force, loc_force] = load.ext_stiff + np.float64(k[loc_force, loc_force])
k[loc_ki, loc_ki] = load.ext_stiff + np.float64(k[loc_ki, loc_ki])
# selecting only the free directions of the siffness matirx
k = k[freedofs, :][:, freedofs]
return k
class CvxFEA(FESolver):
"""
This parent FEA class can assemble the global stiffness matrix and solve
the FE problem with a Supernodal Sparse Cholesky Factorization. It solves
for both the equalibrium and adjoint problems.
Attributes
----------
verbose : bool
False if the FEA should not print updates.
"""
def __init__(self, verbose=False):
super().__init__(verbose)
# finite element computation for displacement
def displace(self, load, x, ke, kmin, penal):
"""
FE solver based upon a Supernodal Sparse Cholesky Factorization. It
requires the instalation of the cvx module. It solves both the FEA
equalibrium and adjoint problems. [1]_
Parameters
----------
load : object, child of the Loads class
The loadcase(s) considerd for this optimisation problem.
x : 2-D array size(nely, nelx)
Current density distribution.
ke : 2-D array size(8, 8)
Local fully dense stiffnes matrix.
kmin : 2-D array size(8, 8)
Local stiffness matrix for an empty element.
penal : float
Material model penalisation (SIMP).
Returns
-------
u : 1-D column array shape(max(edof), 1)
The displacement vector.
lamba : 1-D column array shape(max(edof), 1)
Adjoint equation solution.
References
----------
.. [1] Y. Chen, T. A. Davis, W. W. Hager, S. Rajamanickam, "Algorithm
887: CHOLMOD, Supernodal Sparse Cholesky Factorization and
Update/Downdate", ACM Transactions on Mathematical Software, 35(3),
22:1-22:14, 2008.
"""
freedofs = np.array(load.freedofs())
nely, nelx = x.shape
f = load.force()
l = load.kiloc()
B_free = cvxopt.matrix(np.hstack((f[freedofs], l[freedofs])))
k_free = self.gk_freedofs(load, x, ke, kmin, penal).tocoo()
k_free = cvxopt.spmatrix(k_free.data, k_free.row, k_free.col)
num_dof = load.num_dofs
u = np.zeros((num_dof, 1))
lamba = np.zeros((num_dof, 1))
# setting up a fast cholesky decompositon solver
cvxopt.cholmod.linsolve(k_free, B_free)
u[freedofs] = np.array(B_free[:, 0])
lamba[freedofs] = np.array(B_free[:, 1])
return u, lamba
class CGFEA(FESolver):
"""
This parent FEA class can assemble the global stiffness matrix and solve
the FE problem with a sparse solver based upon a preconditioned conjugate
gradient solver. The preconditioning is based upon the inverse of the
diagonal of the stiffness matrix.
Recomendations
- Make the tolerance change over the iterations, low accuracy is
required for first itteration, more accuracy for the later ones.
- Add more advanced preconditioner.
- Add gpu accerelation.
Attributes
----------
verbose : bool
False if the FEA should not print updates.
ufree_old : array len(freedofs)
Displacement field of previous iteration.
lambafree_old : array len(freedofs)
Ajoint equation result of previous iteration.
"""
def __init__(self, verbose=False):
super().__init__(verbose)
self.ufree_old = None
self.lambafree_old = None
# finite element computation for displacement
def displace(self, load, x, ke, kmin, penal):
"""
FE solver based upon the sparse SciPy solver that uses a preconditioned
conjugate gradient solver, preconditioning is based upon the inverse
of the diagonal of the stiffness matrix. Currently the relative
tolerance is hardcoded as 1e-5. It solves both the equalibrium and
adjoint problems.
Parameters
-------
load : object, child of the Loads class
The loadcase(s) considerd for this optimisation problem.
x : 2-D array size(nely, nelx)
Current density distribution.
ke : 2-D array size(8, 8)
Local fully dense stiffnes matrix.
kmin : 2-D array size(8, 8)
Local stiffness matrix for an empty element.
penal : float
Material model penalisation (SIMP).
Returns
-------
u : 1-D array len(max(edof)+1)
Displacement of all degrees of freedom
lamba : 1-D column array shape(max(edof), 1)
Adjoint equation solution.
"""
freedofs = np.array(load.freedofs())
nely, nelx = x.shape
num_dof = load.num_dofs
f_free = load.force()[freedofs]
l_free = load.kiloc()[freedofs]
k_free = self.gk_freedofs(load, x, ke, kmin, penal)
# Preconditioning
L = diags(1/k_free.diagonal())
# solving the system f = Ku with a cg implementation
u = np.zeros((num_dof, 1))
u[freedofs, 0], info1 = cg(k_free, f_free, x0=self.ufree_old, tol=1e-5, M=L)
# solving adjoint problem l = Klamba with cg
lamba = np.zeros((num_dof, 1))
lamba[freedofs, 0], info2 = cg(k_free, l_free, x0=self.lambafree_old, tol=1e-5, M=L)
# update uold
self.ufree_old = u[freedofs]
self.lamdafree_old = lamba[freedofs]
if self.verbose is True:
if info1 > 0:
print('Convergence tolerance FEA not achieved after ', info1, ' itrations')
if info1 < 0:
print('Illegal input or breakdown FEA', info1)
if info2 > 0:
print('Convergence tolerance adjoint problem not achieved after ', info2, ' itrations')
if info2 < 0:
print('Illegal input or breakdown adjoint problem', info2)
return u, lamba