Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/pysagas/geometry/cell.py: 95%

192 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-30 04:27 +0000

1from __future__ import annotations 

2import numpy as np 

3import pysagas.flow 

4from pysagas.geometry import Vector 

5from numpy.typing import ArrayLike 

6from typing import Union, List, Optional 

7 

8 

9class Cell: 

10 """A triangular cell object. 

11 

12 Attributes 

13 ----------- 

14 p0 : Vector 

15 The first vertex of the cell. 

16 

17 p1 : Vector 

18 The second vertex of the cell. 

19 

20 p2 : Vector 

21 The third vertex of the cell. 

22 

23 A : float 

24 The cell face area. 

25 

26 n : Vector 

27 The cell normal. 

28 

29 c : Vector 

30 The cell centroid. 

31 

32 dndv : np.array 

33 The sensitivity of the cell's normal vector to each vertex. 

34 

35 dAdv : np.array 

36 The sensitivity of the cell's area to each vertex. 

37 

38 dvdp : np.array 

39 The sensitivity of the cell's vertices to each geometric parameter. 

40 

41 dndp : np.array 

42 The sensitivity of the cell's normal vector to each geometric parameter. 

43 

44 dAdp : np.array 

45 The sensitivity of the cell's area to each geometric parameter. 

46 

47 dcdp : np.array 

48 The sensitivity of the centroid to each geometric parameter. 

49 

50 flowstate : FlowState 

51 The flowstate associated with the cell. 

52 

53 sensitivities : np.array 

54 An array containing the [x,y,z] force sensitivities of the cell. 

55 """ 

56 

57 def __init__( 

58 self, p0: Vector, p1: Vector, p2: Vector, face_ids: Optional[list[int]] = None 

59 ): 

60 """Constructs a cell, defined by three points. 

61 

62 Parameters 

63 ---------- 

64 p0 : Vector 

65 The first point defining the cell. 

66 

67 p1 : Vector 

68 The second point defining the cell. 

69 

70 p2 : Vector 

71 The third point defining the cell. 

72 """ 

73 # Save points 

74 self.p0 = p0 

75 self.p1 = p1 

76 self.p2 = p2 

77 

78 # Save connectivity information (PyMesh) 

79 self._face_ids = face_ids 

80 

81 # Calculate cell properites 

82 self.n = -self.calc_normal(p0, p1, p2) 

83 self.A = self.calc_area(p0, p1, p2) 

84 self.c = self.calc_centroid(p0, p1, p2) 

85 

86 # Calculate geometric sensitivities 

87 self._dndv: ArrayLike = None 

88 self._dAdv: ArrayLike = None 

89 self._dcdv: ArrayLike = None 

90 

91 # Parameter sensitivities 

92 self.dvdp: ArrayLike = None # vertex-parameter sensitivities 

93 self.dndp: ArrayLike = None # normal-parameter sensitivities 

94 self.dAdp: ArrayLike = None # area-parameter sensitivities 

95 self.dcdp: ArrayLike = None # centroid-parameter sensitivities 

96 

97 # FlowState 

98 self.flowstate: pysagas.flow.FlowState = None 

99 

100 # Sensitivities 

101 self.sensitivities = None 

102 self.moment_sensitivities = None 

103 

104 # Cell attributes 

105 self.attributes = {} 

106 

107 @property 

108 def dndv(self): 

109 if self._dndv is not None: 

110 # Already exists, return it 

111 return self._dndv 

112 else: 

113 # Calculate and return it 

114 self._dndv = self.n_sensitivity(self.p0, self.p1, self.p2) 

115 return self._dndv 

116 

117 @property 

118 def dAdv(self): 

119 if self._dAdv is not None: 

120 # Already exists, return it 

121 return self._dAdv 

122 else: 

123 # Calculate and return it 

124 self._dAdv = self.A_sensitivity(self.p0, self.p1, self.p2) 

125 return self._dAdv 

126 

127 @property 

128 def dcdv(self): 

129 if self._dcdv is not None: 

130 # Already exists, return it 

131 return self._dcdv 

132 else: 

133 # Calculate and return it 

134 self._dcdv = self.c_sensitivity(self.p0, self.p1, self.p2) 

135 return self._dcdv 

136 

137 @property 

138 def vertices(self): 

139 """The cell vertices.""" 

140 return np.array([getattr(self, p).vec for p in ["p0", "p1", "p2"]]) 

141 

142 def to_dict(self): 

143 """Returns the Cell as a dictionary.""" 

144 # TODO - need to think about representing the cells 

145 # vs. the points 

146 # The values at the points will need to be averaged 

147 # across cells 

148 pass 

149 

150 @classmethod 

151 def from_points( 

152 cls, points: Union[List[Vector], np.array[Vector]], **kwargs 

153 ) -> Cell: 

154 """Constructs a Vector object from an array of coordinates. 

155 

156 Parameters 

157 ---------- 

158 points : Union[List[Vector], np.array[Vector]] 

159 The points defining the cell. 

160 

161 Returns 

162 ------- 

163 Cell 

164 """ 

165 return cls(*points, **kwargs) 

166 

167 def __repr__(self) -> str: 

168 return f"Cell({self.p0}, {self.p1}, {self.p2})" 

169 

170 def __str__(self) -> str: 

171 return "A Cell" 

172 

173 def _add_sensitivities(self, dvdp: np.array) -> None: 

174 """Adds the cell sensitivities to the cell. 

175 

176 Parameters 

177 ----------- 

178 dvdp : array 

179 The sensitivity of each vertex x, y and z, with respect to each 

180 parameter. The dimensions of dvdp are (9, p), for p parameters. 

181 Each 3 rows corresponds to the x, y and z sensitivities for each 

182 vertex of the cell. Each column is for the relevant parameter. 

183 """ 

184 self.dvdp = dvdp 

185 self.dndp = np.dot(self.dndv, self.dvdp) 

186 self.dAdp = np.dot(self.dAdv, self.dvdp) 

187 self.dcdp = np.dot(self.dcdv, self.dvdp) 

188 

189 @staticmethod 

190 def calc_normal(p0: Vector, p1: Vector, p2: Vector) -> Vector: 

191 """Calculates the normal vector of a cell defined by three points. 

192 

193 Parameters 

194 ---------- 

195 p0 : Vector 

196 The first point defining the cell. 

197 

198 p1 : Vector 

199 The second point defining the cell. 

200 

201 p2 : Vector 

202 The third point defining the cell. 

203 

204 Returns 

205 -------- 

206 normal : Vector 

207 The unit normal vector of the cell defined by the points. 

208 

209 References 

210 ----------- 

211 https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal 

212 """ 

213 # Define cell edges 

214 A = p1 - p0 

215 B = p2 - p0 

216 

217 # Calculate normal components 

218 Nx = A.y * B.z - A.z * B.y 

219 Ny = A.z * B.x - A.x * B.z 

220 Nz = A.x * B.y - A.y * B.x 

221 

222 # Construct normal vector 

223 normal = Vector(x=Nx, y=Ny, z=Nz) 

224 

225 if normal.norm == 0: 

226 # Degenerate cell 

227 raise DegenerateCell() 

228 

229 # Convert to unit vector 

230 unit_normal = normal / np.linalg.norm(normal.vec) 

231 

232 return unit_normal 

233 

234 @staticmethod 

235 def calc_area(p0: Vector, p1: Vector, p2: Vector) -> float: 

236 """Calculates the area of a cell defined by three points. 

237 

238 Parameters 

239 ---------- 

240 p0 : Vector 

241 The first point defining the cell. 

242 

243 p1 : Vector 

244 The second point defining the cell. 

245 

246 p2 : Vector 

247 The third point defining the cell. 

248 

249 Returns 

250 -------- 

251 area : float 

252 The area of the cell defined by the points. 

253 

254 References 

255 ----------- 

256 https://en.wikipedia.org/wiki/Cross_product 

257 """ 

258 # Define cell edges 

259 A = p1 - p0 

260 B = p2 - p0 

261 

262 # Calculate area 

263 S = 0.5 * np.linalg.norm(np.cross(A.vec, B.vec)) 

264 return S 

265 

266 @staticmethod 

267 def calc_centroid(p0: Vector, p1: Vector, p2: Vector) -> Vector: 

268 """Calculates the centroid of a cell defined by three points. 

269 

270 Parameters 

271 ---------- 

272 p0 : Vector 

273 The first point defining the cell. 

274 

275 p1 : Vector 

276 The second point defining the cell. 

277 

278 p2 : Vector 

279 The third point defining the cell. 

280 

281 Returns 

282 -------- 

283 c : Vector 

284 The centroid of the cell defined by the points. 

285 

286 References 

287 ----------- 

288 https://en.wikipedia.org/wiki/Centroid 

289 """ 

290 cx = (p0.x + p1.x + p2.x) / 3 

291 cy = (p0.y + p1.y + p2.y) / 3 

292 cz = (p0.z + p1.z + p2.z) / 3 

293 return Vector(cx, cy, cz) 

294 

295 @staticmethod 

296 def n_sensitivity(p0: Vector, p1: Vector, p2: Vector) -> np.array: 

297 """Calculates the sensitivity of a cell's normal vector 

298 to the points defining the cell analytically. 

299 

300 Parameters 

301 ---------- 

302 p0 : Vector 

303 The first point defining the cell. 

304 

305 p1 : Vector 

306 The second point defining the cell. 

307 

308 p2 : Vector 

309 The third point defining the cell. 

310 

311 Returns 

312 ------- 

313 sensitivity : np.array 

314 The sensitivity matrix with size m x n x p. Rows m refer to 

315 the vertices, columns n refer to the vertex coordinates, and 

316 slices p refer to the components of the normal vector. 

317 """ 

318 g = ( 

319 ((p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y)) ** 2 

320 + ((p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z)) ** 2 

321 + ((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) ** 2 

322 ) 

323 

324 # n = np.empty(3) 

325 # A = p0 

326 # B = p1 

327 # C = p2 

328 # n[0] = ((B.y-A.y)*(C.z-A.z) - (B.z-A.z)*(C.y-A.y)) / g**0.5 

329 # n[1] = ((B.z-A.z)*(C.x-A.x) - (B.x-A.x)*(C.z-A.z)) / g**0.5 

330 # n[2] = ((B.x-A.x)*(C.y-A.y) - (B.y-A.y)*(C.x-A.x)) / g**0.5 

331 

332 M_sense = np.empty((3, 9)) 

333 for row in range(3): 

334 if row == 0: 

335 f = (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) # f_x 

336 elif row == 1: 

337 f = (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) # f_y 

338 elif row == 2: 

339 f = (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) # f_z 

340 

341 for col in range(9): 

342 if col == 0: # d/dp0.x 

343 g_dash = 2 * ( 

344 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

345 ) * (-(p1.z - p0.z) + (p2.z - p0.z)) + 2 * ( 

346 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

347 ) * ( 

348 -(p2.y - p0.y) + (p1.y - p0.y) 

349 ) 

350 elif col == 1: # d/dp0.y 

351 g_dash = 2 * ( 

352 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

353 ) * (-(p2.z - p0.z) + (p1.z - p0.z)) + 2 * ( 

354 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

355 ) * ( 

356 -(p1.x - p0.x) + (p2.x - p0.x) 

357 ) 

358 elif col == 2: # d/dp0.z 

359 g_dash = 2 * ( 

360 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

361 ) * (-(p1.y - p0.y) + (p2.y - p0.y)) + 2 * ( 

362 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

363 ) * ( 

364 -(p2.x - p0.x) + (p1.x - p0.x) 

365 ) 

366 elif col == 3: # d/dp1.x 

367 g_dash = 2 * ( 

368 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

369 ) * (-(p2.z - p0.z)) + 2 * ( 

370 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

371 ) * ( 

372 (p2.y - p0.y) 

373 ) 

374 elif col == 4: # d/dp1.y 

375 g_dash = 2 * ( 

376 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

377 ) * ((p2.z - p0.z)) + 2 * ( 

378 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

379 ) * ( 

380 -(p2.x - p0.x) 

381 ) 

382 elif col == 5: # d/dp1.z 

383 g_dash = 2 * ( 

384 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

385 ) * (-(p2.y - p0.y)) + 2 * ( 

386 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

387 ) * ( 

388 (p2.x - p0.x) 

389 ) 

390 elif col == 6: # d/dp2.x 

391 g_dash = 2 * ( 

392 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

393 ) * ((p1.z - p0.z)) + 2 * ( 

394 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

395 ) * ( 

396 -(p1.y - p0.y) 

397 ) 

398 elif col == 7: # d/dp2.y 

399 g_dash = 2 * ( 

400 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

401 ) * (-(p1.z - p0.z)) + 2 * ( 

402 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

403 ) * ( 

404 (p1.x - p0.x) 

405 ) 

406 elif col == 8: # d/dp2.z 

407 g_dash = 2 * ( 

408 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

409 ) * ((p1.y - p0.y)) + 2 * ( 

410 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

411 ) * ( 

412 -(p1.x - p0.x) 

413 ) 

414 

415 if row == 0: # for n..x 

416 # ((p1.y-p0.y)*(p2.z-p0.z) - (p1.z-p0.z)*(p2.y-p0.y)) # f_x 

417 if col == 1: # d/dp0.y 

418 f_dash = -(p2.z - p0.z) + (p1.z - p0.z) 

419 elif col == 2: # d/dp0.z 

420 f_dash = -(p1.y - p0.y) + (p2.y - p0.y) 

421 elif col == 4: # d/dp1.y 

422 f_dash = p2.z - p0.z 

423 elif col == 5: # d/dp1.z 

424 f_dash = -(p2.y - p0.y) 

425 elif col == 7: # d/dp2.y 

426 f_dash = -(p1.z - p0.z) 

427 elif col == 8: # d/dp2.z 

428 f_dash = p1.y - p0.y 

429 else: 

430 f_dash = 0 

431 if row == 1: # for n.y # CHECK 

432 # f = ((p1.z-p0.z)*(p2.x-p0.x) - (p1.x-p0.x)*(p2.z-p0.z)) # f_y 

433 if col == 0: # d/dp0.x 

434 f_dash = -(p1.z - p0.z) + (p2.z - p0.z) 

435 elif col == 2: # d/dp0.z 

436 f_dash = -(p2.x - p0.x) + (p1.x - p0.x) # CHECK 

437 elif col == 3: # d/dp1.x 

438 f_dash = -(p2.z - p0.z) 

439 elif col == 5: # d/dp1.z 

440 f_dash = p2.x - p0.x 

441 elif col == 6: # d/dp2.x 

442 f_dash = p1.z - p0.z 

443 elif col == 8: # d/dp2.z 

444 f_dash = -(p1.x - p0.x) 

445 else: 

446 f_dash = 0 

447 if row == 2: # for n.z # CHECK 

448 # f = ((p1.x-p0.x)*(p2.y-p0.y) - (p1.y-p0.y)*(p2.x-p0.x)) # f_z 

449 if col == 0: # d/dp0.x 

450 f_dash = -(p2.y - p0.y) + (p1.y - p0.y) 

451 elif col == 1: # d/dp0.y 

452 f_dash = -(p1.x - p0.x) + (p2.x - p0.x) 

453 elif col == 3: # d/dp1.x 

454 f_dash = p2.y - p0.y 

455 elif col == 4: # d/dp1.y 

456 f_dash = -(p2.x - p0.x) 

457 elif col == 6: # d/dp2.x 

458 f_dash = -(p1.y - p0.y) 

459 elif col == 7: # d/dp2.y 

460 f_dash = p1.x - p0.x 

461 else: 

462 f_dash = 0 

463 

464 # Assign sensitivity 

465 M_sense[row, col] = ( 

466 f_dash * (g ** (-0.5)) + f * (-1 / 2) * g ** (-3 / 2) * g_dash 

467 ) 

468 return M_sense 

469 

470 @staticmethod 

471 def A_sensitivity(p0: Vector, p1: Vector, p2: Vector) -> np.array: 

472 """Calculates the sensitivity of a cell's area to the points 

473 defining the cell analytically. 

474 

475 Parameters 

476 ---------- 

477 p0 : Vector 

478 The first point defining the cell. 

479 

480 p1 : Vector 

481 The second point defining the cell. 

482 

483 p2 : Vector 

484 The third point defining the cell. 

485 

486 Returns 

487 ------- 

488 sensitivity : np.array 

489 The sensitivity matrix with size m x n. Rows m refer to 

490 the vertices, columns n refer to the vertex coordinates. 

491 """ 

492 g = ( 

493 ((p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y)) ** 2 

494 + ((p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z)) ** 2 

495 + ((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) ** 2 

496 ) 

497 

498 A_sense = np.empty(9) 

499 for col in range(9): 

500 if col == 0: # d/dp0.x 

501 g_dash = 2 * ( 

502 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

503 ) * (-(p1.z - p0.z) + (p2.z - p0.z)) + 2 * ( 

504 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

505 ) * ( 

506 -(p2.y - p0.y) + (p1.y - p0.y) 

507 ) 

508 elif col == 1: # d/dp0.y 

509 g_dash = 2 * ( 

510 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

511 ) * (-(p2.z - p0.z) + (p1.z - p0.z)) + 2 * ( 

512 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

513 ) * ( 

514 -(p1.x - p0.x) + (p2.x - p0.x) 

515 ) 

516 elif col == 2: # d/dp0.z 

517 g_dash = 2 * ( 

518 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

519 ) * (-(p1.y - p0.y) + (p2.y - p0.y)) + 2 * ( 

520 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

521 ) * ( 

522 -(p2.x - p0.x) + (p1.x - p0.x) 

523 ) 

524 elif col == 3: # d/dp1.x 

525 g_dash = 2 * ( 

526 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

527 ) * (-(p2.z - p0.z)) + 2 * ( 

528 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

529 ) * ( 

530 (p2.y - p0.y) 

531 ) 

532 elif col == 4: # d/dp1.y 

533 g_dash = 2 * ( 

534 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

535 ) * ((p2.z - p0.z)) + 2 * ( 

536 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

537 ) * ( 

538 -(p2.x - p0.x) 

539 ) 

540 elif col == 5: # d/dp1.z 

541 g_dash = 2 * ( 

542 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

543 ) * (-(p2.y - p0.y)) + 2 * ( 

544 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

545 ) * ( 

546 (p2.x - p0.x) 

547 ) 

548 elif col == 6: # d/dp2.x 

549 g_dash = 2 * ( 

550 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

551 ) * ((p1.z - p0.z)) + 2 * ( 

552 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

553 ) * ( 

554 -(p1.y - p0.y) 

555 ) 

556 elif col == 7: # d/dp2.y 

557 g_dash = 2 * ( 

558 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

559 ) * (-(p1.z - p0.z)) + 2 * ( 

560 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x) 

561 ) * ( 

562 (p1.x - p0.x) 

563 ) 

564 elif col == 8: # d/dp2.z 

565 g_dash = 2 * ( 

566 (p1.y - p0.y) * (p2.z - p0.z) - (p1.z - p0.z) * (p2.y - p0.y) 

567 ) * ((p1.y - p0.y)) + 2 * ( 

568 (p1.z - p0.z) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.z - p0.z) 

569 ) * ( 

570 -(p1.x - p0.x) 

571 ) 

572 

573 A_sense[col] = 0.5 * (1 / 2) * g ** (-1 / 2) * g_dash 

574 

575 return A_sense 

576 

577 @staticmethod 

578 def c_sensitivity(p0: Vector, p1: Vector, p2: Vector) -> np.array: 

579 """Calculates the sensitivity of a cell's centroid to the 

580 points defining the cell. 

581 

582 Parameters 

583 ---------- 

584 p0 : Vector 

585 The first point defining the cell. 

586 

587 p1 : Vector 

588 The second point defining the cell. 

589 

590 p2 : Vector 

591 The third point defining the cell. 

592 

593 Returns 

594 ------- 

595 sensitivity : np.array 

596 The sensitivity matrix with size m x n x p. Rows m refer to 

597 the vertices, columns n refer to the vertex coordinates, and 

598 slices p refer to the components of the centroid point. 

599 """ 

600 c_sens = np.array( 

601 [ 

602 [1 / 3, 0, 0, 1 / 3, 0, 0, 1 / 3, 0, 0], 

603 [0, 1 / 3, 0, 0, 1 / 3, 0, 0, 1 / 3, 0], 

604 [0, 0, 1 / 3, 0, 0, 1 / 3, 0, 0, 1 / 3], 

605 ] 

606 ) 

607 return c_sens 

608 

609 

610class DegenerateCell(Exception): 

611 """Exception raised for degenerate cells.""" 

612 

613 def __init__(self, message="Degenerate cell"): 

614 self.message = message 

615 super().__init__(self.message)