Coverage for /usr/local/lib/python3.8/site-packages/spam/mesh/tetrahedra.py: 76%

88 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-11-22 13:26 +0000

1# Library of SPAM functions for dealing with assembly maxtrix of unstructured 3D meshes made of tetrahedra 

2# Copyright (C) 2020 SPAM Contributors 

3# 

4# This program is free software: you can redistribute it and/or modify it 

5# under the terms of the GNU General Public License as published by the Free 

6# Software Foundation, either version 3 of the License, or (at your option) 

7# any later version. 

8# 

9# This program is distributed in the hope that it will be useful, but WITHOUT 

10# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

11# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 

12# more details. 

13# 

14# You should have received a copy of the GNU General Public License along with 

15# this program. If not, see <http://www.gnu.org/licenses/>. 

16import numpy 

17 

18 

19def tetCentroid(points): 

20 """Compute coordinates of the centroid of a tetrahedron 

21 

22 Parameters 

23 ---------- 

24 points : 4x3 array 

25 Array of the 3D coordinates of the 4 points 

26 

27 Returns 

28 ------- 

29 3x1 array 

30 the coordinates of the centroid 

31 """ 

32 

33 centroid = numpy.zeros(3) 

34 for i in range(3): 

35 a = points[0][i] 

36 b = points[1][i] 

37 c = points[2][i] 

38 d = points[3][i] 

39 

40 # mid point of AB (1/2 of A -> B) 

41 mid_ab = a + (b - a) / 2.0 

42 

43 # centroid of ABC (1/3 of AB -> C median) 

44 face_centroid = mid_ab + (c - mid_ab) / 3.0 

45 

46 # centroid of ABCD (1/4 of ABC -> D median) 

47 centroid[i] = face_centroid + (d - face_centroid) / 4.0 

48 

49 return centroid 

50 

51 

52def tetVolume(points): 

53 """Compute the volume of a tetrahedron 

54 

55 Parameters 

56 ---------- 

57 points : 4x3 array 

58 Array of the 3D coordinates of the 4 points 

59 

60 Returns 

61 ------- 

62 float 

63 the volume of the tetrahedron 

64 """ 

65 

66 # compute the jacobian matrix by padding the points with a line of 1 

67 pad = numpy.array([[1], [1], [1], [1]]) 

68 jacobian = numpy.hstack([pad, points]) 

69 

70 # the volume is 1/6 of the determinant of the jacobian matrix 

71 return numpy.linalg.det(jacobian) / 6.0 

72 

73 

74def shapeFunctions(points): 

75 """ 

76 This function computes the shape coefficients matrux from the coordinates 

77 of the 4 nodes of a linear tetrahedron (see 4.11 in Zienkiewicz). 

78 

79 .. code-block:: text 

80 

81 coefficients_matrix = [ 

82 a1 b1 c1 d1 

83 a2 b2 c2 d2 

84 a3 b3 c3 d3 

85 a4 b4 c4 d4 

86 ] 

87 with 

88 N1 = a1 + b1x + c1y + d1z 

89 N2 = a2 + b2x + c2y + d2z 

90 N3 = a3 + b3x + c3y + d3z 

91 N4 = a4 + b4x + c4y + d4z 

92 

93 Parameters 

94 ---------- 

95 points : 4x3 array 

96 Array of the 3D coordinates of the 4 points 

97 

98 Returns 

99 ------- 

100 volume : float 

101 The volume of the tetrahedron 

102 coefficients_matrix : 4x4 array 

103 The coefficients 4 coefficients of the 4 shape functions 

104 

105 Note 

106 ----- 

107 Pure python function. 

108 """ 

109 # cast into numpy array and check shape 

110 points = numpy.array(points) 

111 if points.shape != (4, 3): 

112 raise ValueError("Points coordinates in bad format. Can I have a 4x3 please?") 

113 

114 # jacobian matrix 

115 pad = numpy.array([[1], [1], [1], [1]]) 

116 jacobian = numpy.hstack([pad, points]) 

117 six_v = numpy.linalg.det(jacobian) 

118 volume = six_v / 6.0 

119 

120 coefficients_matrix = numpy.zeros((4, 4)) 

121 for l_number in range(4): # loop over the 4 nodes 

122 for c_number in range(4): # loop over the 4 coefficients for 1 node 

123 # create mask for the sub matrix (ie: [1, 2, 3], [0, 2, 3], ...) 

124 lines = [_ for _ in range(4) if _ != l_number] 

125 columns = [_ for _ in range(4) if _ != c_number] 

126 # creates the sub_jacobian (needs two runs for some numpy reason) 

127 sub_jacobian = jacobian[:, columns] 

128 sub_jacobian = sub_jacobian[lines, :] 

129 

130 # compute the determinant and fill coefficients matrix 

131 det = numpy.linalg.det(sub_jacobian) 

132 sign = (-1.0) ** (l_number + c_number) 

133 coefficients_matrix[l_number, c_number] = sign * det / six_v 

134 

135 return volume, coefficients_matrix 

136 

137 

138def elementaryStiffnessMatrix(points, young, poisson): 

139 """ 

140 This function computes elementary stiffness matrix from the coordinates 

141 of the 4 nodes of a linear tetrahedron. 

142 

143 .. code-block:: text 

144 

145 D = [something with E and mu] (6x6) 

146 B = [B1, B2, B3, B4] (4 times 6x3 -> 6x12) 

147 ke = V.BT.D.B (12x12) 

148 

149 Parameters 

150 ---------- 

151 points : 4x3 array 

152 Array of the 3D coordinates of the 4 points 

153 young: float 

154 Young modulus 

155 poisson: float 

156 Poisson ratio 

157 

158 Returns 

159 ------- 

160 stiffness_matrix : 12x12 array 

161 The stiffness matrix of the tetrahedron 

162 

163 Note 

164 ----- 

165 Pure python function. 

166 """ 

167 

168 # get volume and shape functions coefficients 

169 volume, N = shapeFunctions(points) 

170 

171 # compute the full B matrix 

172 def Ba(N, a): 

173 return numpy.array( 

174 [ 

175 [N[a, 1], 0, 0], 

176 [0, N[a, 2], 0], 

177 [0, 0, N[a, 3]], 

178 [0, N[a, 3], N[a, 2]], 

179 [N[a, 3], 0, N[a, 1]], 

180 [N[a, 2], N[a, 1], 0], 

181 ] 

182 ) 

183 

184 # initialise B with first Ba 

185 B = Ba(N, 0) 

186 for a in [1, 2, 3]: 

187 B = numpy.hstack([B, Ba(N, a)]) 

188 

189 # compute D matrix 

190 D = (1.0 - poisson) * numpy.eye(6) 

191 D[0, [1, 2]] = poisson 

192 D[1, [0, 2]] = poisson 

193 D[2, [0, 1]] = poisson 

194 D[3:, 3:] -= 0.5 * numpy.eye(3) 

195 D *= young / ((1 + poisson) * (1 - 2 * poisson)) 

196 

197 # compute stiffness matrix 

198 ke = volume * numpy.matmul(numpy.transpose(B), numpy.matmul(D, B)) 

199 

200 return ke 

201 

202 

203def globalStiffnessMatrix(points, connectivity, young, poisson): 

204 """ 

205 This function assembles elementary stiffness matrices to computes an elastic 

206 (3 dof) global stiffness matrix for a 4 noded tetrahedra mesh 

207 

208 Notations 

209 --------- 

210 neq: number of global equations 

211 nel: number of elements 

212 npo: number of points 

213 neq = 3 x npo 

214 

215 Parameters 

216 ---------- 

217 points : npo x 3 array 

218 Array of the 3D coordinates of the all nodes points 

219 connectivity : nel x 4 array 

220 Connectivity matrix 

221 young: float 

222 Young modulus 

223 poisson: float 

224 Poisson ratio 

225 

226 Returns 

227 ------- 

228 stiffness_matrix : neq x neq array 

229 The stiffness matrix of the tetrahedron 

230 

231 Note 

232 ----- 

233 Pure python function. 

234 """ 

235 # cast to numpy array 

236 points = numpy.array(points) 

237 connectivity = numpy.array(connectivity) 

238 

239 neq = 3 * points.shape[0] 

240 # nel = connectivity.shape[0] 

241 K = numpy.zeros((neq, neq), dtype=float) 

242 

243 for e, element_nodes_number in enumerate(connectivity): 

244 # print(f'Element number {e}: {element_nodes_number}') 

245 

246 # built local stiffness matrix (12x12) 

247 element_points = [points[A] for A in element_nodes_number] 

248 ke = elementaryStiffnessMatrix(element_points, young, poisson) 

249 

250 # loop over stiffness matrix (lines and rows) 

251 for p1 in range(12): 

252 i1 = p1 % 3 # deduce degree of freedom 

253 a1 = (p1 - i1) // 3 # deduce local node number 

254 P1 = 3 * int(connectivity[e, a1]) + i1 # get global equation number 

255 for p2 in range(p1, 12): 

256 i2 = p2 % 3 

257 a2 = (p2 - i2) // 3 

258 P2 = 3 * int(connectivity[e, a2]) + i2 

259 K[P1, P2] += ke[p1, p2] 

260 

261 K = K + K.transpose() 

262 K[numpy.diag_indices(neq)] *= 0.5 

263 

264 return K 

265 

266 

267if __name__ == "__main__": 

268 import matplotlib.pyplot as plt 

269 import spam.mesh 

270 

271 # points = [ 

272 # [0, 0, 0], 

273 # [0, 1, 0], 

274 # [0, 0, 1], 

275 # [1, 0, 0] 

276 # ] 

277 # 

278 # coefficients_matrix = shapeFunctions(points) 

279 # ke = elementaryStiffnessMatrix(points, 1, 0.3) 

280 # print(f'Elementary connectivity matrix: {ke.shape}') 

281 # # print(ke) 

282 

283 lengths = [1, 1, 1] 

284 lc = 0.2 

285 points, connectivity = spam.mesh.createCuboid( 

286 lengths, 

287 lc, 

288 origin=[0.0, 0.0, 0.0], 

289 periodicity=False, 

290 verbosity=1, 

291 gmshFile=None, 

292 vtkFile=None, 

293 binary=False, 

294 skipOutput=False, 

295 ) 

296 

297 K = globalStiffnessMatrix(points, connectivity, 1, 0.3) 

298 

299 plt.imshow(numpy.log(K[:100, :100])) 

300 plt.show() 

301 

302 # ID function 

303 def ID(i: int, A: int): 

304 """ 

305 Defined as in Hughes: The Finite Element Method eq. 2.8.3 

306 WARNING: without the condition of dirichlet boundary conditions 

307 Takes 

308 - the degree of freedom: i = 0, 1 or 2 

309 - the node global number: A = 0, 1, 2, ... n_nodes 

310 Returns 

311 - the global equation number: P = 0, 1, 2 ... 3 * n_nodes 

312 """ 

313 P = 3 * A + i 

314 return int(P) 

315 

316 # for A, point in enumerate(points): 

317 # for i in range(3): 

318 # print(f'Node number {A} dof {i}: {ID(i, A)}') 

319 

320 # IEN function 

321 def IEN(a: int, e: int, connectivity): 

322 """ 

323 Defined as in Hughes: The Finite Element Method eq. 2.6.1 

324 Takes 

325 - the local node number: a = 0, 1, 2 or 3 

326 - the element number: e = 0, 1, 2, ... n_elem 

327 Returns 

328 - the node global number: A = 0, 1, 2, ... n_nodes 

329 Uses the connectivity matrix 

330 """ 

331 # connectivity matrix shape: n_elem x 4 

332 A = connectivity[e, a] 

333 return int(A) 

334 

335 # for e, _ in enumerate(connectivity): 

336 # for a in range(4): 

337 # print(f'Element number {e} node number {a}: {IEN(a, e, connectivity)}') 

338 

339 # LM function 

340 def LM(i: int, a: int, e: int, connectivity): 

341 """ 

342 Defined as in Hughes: The Finite Element Method eq. 2.10.1 

343 Takes 

344 - the degree of freedom: i = 0, 1 or 2 

345 - the local node number: a = 0, 1, 2 or 3 

346 - the element number: e = 0, 1, 2, ... n_elem 

347 Returns 

348 - the global equation number: P = 0, 1, 2 ... 3 * n_nodes 

349 Uses the connectivity matrix 

350 """ 

351 P = ID(i, IEN(a, e, connectivity)) 

352 return P 

353 

354 # for e, _ in enumerate(connectivity): 

355 # for a in range(4): 

356 # for i in range(3): 

357 # P = 3 * int(connectivity[e, a]) + i 

358 # print(f'Element number {e} node number {a} dof {i}: {LM(i, a, e, connectivity)}') 

359 # 

360 # 

361 # if e == 3: 

362 # break