Coverage for /usr/local/lib/python3.10/site-packages/spam/deformation/deformationField.py: 99%

175 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-24 17:26 +0000

1# Library of SPAM functions for dealing with fields of Phi or fields of F 

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/>. 

16 

17import multiprocessing 

18 

19try: 

20 multiprocessing.set_start_method("fork") 

21except RuntimeError: 

22 pass 

23 

24import numpy 

25import progressbar 

26 

27# 2020-02-24 Olga Stamati and Edward Ando 

28import spam.deformation 

29import spam.label 

30 

31# Global number of processes 

32nProcessesDefault = multiprocessing.cpu_count() 

33 

34 

35def FfieldRegularQ8(displacementField, nodeSpacing, nProcesses=nProcessesDefault, verbose=False): 

36 """ 

37 This function computes the transformation gradient field F from a given displacement field. 

38 Please note: the transformation gradient tensor: F = I + du/dx. 

39 

40 This function computes du/dx in the centre of an 8-node cell (Q8 in Finite Elements terminology) using order one (linear) shape functions. 

41 

42 Parameters 

43 ---------- 

44 displacementField : 4D array of floats 

45 The vector field to compute the derivatives. 

46 #Its shape is (nz, ny, nx, 3) 

47 

48 nodeSpacing : 3-component list of floats 

49 Length between two nodes in every direction (*i.e.,* size of a cell) 

50 

51 nProcesses : integer, optional 

52 Number of processes for multiprocessing 

53 Default = number of CPUs in the system 

54 

55 verbose : boolean, optional 

56 Print progress? 

57 Default = True 

58 

59 Returns 

60 ------- 

61 F : (nz-1) x (ny-1) x (nx-1) x 3x3 array of n cells 

62 The field of the transformation gradient tensors 

63 """ 

64 # import spam.DIC.deformationFunction 

65 # import spam.mesh.strain 

66 

67 # Define dimensions 

68 dims = list(displacementField.shape[0:3]) 

69 

70 # Q8 has 1 element fewer than the number of displacement points 

71 cellDims = [n - 1 for n in dims] 

72 

73 # Check if a 2D field is passed 

74 if dims[0] == 1: 

75 # Add a ficticious layer of nodes and cells in Z direction 

76 dims[0] += 1 

77 cellDims[0] += 1 

78 nodeSpacing[0] += 1 

79 

80 # Add a ficticious layer of equal displacements so that the strain in z is null 

81 displacementField = numpy.concatenate((displacementField, displacementField)) 

82 

83 numberOfCells = cellDims[0] * cellDims[1] * cellDims[2] 

84 dims = tuple(dims) 

85 cellDims = tuple(cellDims) 

86 

87 # Transformation gradient tensor F = du/dx +I 

88 Ffield = numpy.zeros((cellDims[0], cellDims[1], cellDims[2], 3, 3)) 

89 FfieldFlat = Ffield.reshape((numberOfCells, 3, 3)) 

90 

91 # Define the coordinates of the Parent Element 

92 # we're using isoparametric Q8 elements 

93 lid = numpy.zeros((8, 3)).astype("<u1") # local index 

94 lid[0] = [0, 0, 0] 

95 lid[1] = [0, 0, 1] 

96 lid[2] = [0, 1, 0] 

97 lid[3] = [0, 1, 1] 

98 lid[4] = [1, 0, 0] 

99 lid[5] = [1, 0, 1] 

100 lid[6] = [1, 1, 0] 

101 lid[7] = [1, 1, 1] 

102 

103 # Calculate the derivatives of the shape functions 

104 # Since the center is equidistant from all 8 nodes, each one gets equal weighting 

105 SFderivative = numpy.zeros((8, 3)) 

106 for node in range(8): 

107 # (local nodes coordinates) / weighting of each node 

108 SFderivative[node, 0] = (2.0 * (float(lid[node, 0]) - 0.5)) / 8.0 

109 SFderivative[node, 1] = (2.0 * (float(lid[node, 1]) - 0.5)) / 8.0 

110 SFderivative[node, 2] = (2.0 * (float(lid[node, 2]) - 0.5)) / 8.0 

111 

112 # Compute the jacobian to go from local(Parent Element) to global base 

113 jacZ = 2.0 / float(nodeSpacing[0]) 

114 jacY = 2.0 / float(nodeSpacing[1]) 

115 jacX = 2.0 / float(nodeSpacing[2]) 

116 

117 if verbose: 

118 pbar = progressbar.ProgressBar(maxval=numberOfCells).start() 

119 finishedCells = 0 

120 

121 # Loop over the cells 

122 global _multiprocessingComputeOneQ8 

123 

124 def _multiprocessingComputeOneQ8(cell): 

125 zCell, yCell, xCell = numpy.unravel_index(cell, cellDims) 

126 

127 # Check for nans in one of the 8 nodes of the cell 

128 if not numpy.all(numpy.isfinite(displacementField[zCell : zCell + 2, yCell : yCell + 2, xCell : xCell + 2])): 

129 F = numpy.zeros((3, 3)) * numpy.nan 

130 

131 # If no nans start the strain calculation 

132 else: 

133 # Initialise the gradient of the displacement tensor 

134 dudx = numpy.zeros((3, 3)) 

135 

136 # Loop over each node of the cell 

137 for node in range(8): 

138 # Get the displacement value 

139 d = displacementField[ 

140 int(zCell + lid[node, 0]), 

141 int(yCell + lid[node, 1]), 

142 int(xCell + lid[node, 2]), 

143 :, 

144 ] 

145 

146 # Compute the influence of each node to the displacement gradient tensor 

147 dudx[0, 0] += jacZ * SFderivative[node, 0] * d[0] 

148 dudx[1, 1] += jacY * SFderivative[node, 1] * d[1] 

149 dudx[2, 2] += jacX * SFderivative[node, 2] * d[2] 

150 dudx[1, 0] += jacY * SFderivative[node, 1] * d[0] 

151 dudx[0, 1] += jacZ * SFderivative[node, 0] * d[1] 

152 dudx[2, 1] += jacX * SFderivative[node, 2] * d[1] 

153 dudx[1, 2] += jacY * SFderivative[node, 1] * d[2] 

154 dudx[2, 0] += jacX * SFderivative[node, 2] * d[0] 

155 dudx[0, 2] += jacZ * SFderivative[node, 0] * d[2] 

156 # Adding a transpose to dudx, it's ugly but allows us to pass #142 

157 F = numpy.eye(3) + dudx.T 

158 return cell, F 

159 

160 # Run multiprocessing 

161 with multiprocessing.Pool(processes=nProcesses) as pool: 

162 for returns in pool.imap_unordered(_multiprocessingComputeOneQ8, range(numberOfCells)): 

163 if verbose: 

164 finishedCells += 1 

165 pbar.update(finishedCells) 

166 FfieldFlat[returns[0]] = returns[1] 

167 pool.close() 

168 pool.join() 

169 

170 if verbose: 

171 pbar.finish() 

172 

173 return Ffield 

174 

175 

176def FfieldRegularGeers( 

177 displacementField, 

178 nodeSpacing, 

179 neighbourRadius=1.5, 

180 nProcesses=nProcessesDefault, 

181 verbose=False, 

182): 

183 """ 

184 This function computes the transformation gradient field F from a given displacement field. 

185 Please note: the transformation gradient tensor: F = I + du/dx. 

186 

187 This function computes du/dx as a weighted function of neighbouring points. 

188 Here is implemented the linear model proposed in: 

189 "Computing strain fields from discrete displacement fields in 2D-solids", Geers et al., 1996 

190 

191 Parameters 

192 ---------- 

193 displacementField : 4D array of floats 

194 The vector field to compute the derivatives. 

195 Its shape is (nz, ny, nx, 3). 

196 

197 nodeSpacing : 3-component list of floats 

198 Length between two nodes in every direction (*i.e.,* size of a cell) 

199 

200 neighbourRadius : float, optional 

201 Distance in nodeSpacings to include neighbours in the strain calcuation. 

202 Default = 1.5*nodeSpacing which will give radius = 1.5*min(nodeSpacing) 

203 

204 mask : bool, optional 

205 Avoid non-correlated NaN points in the displacement field? 

206 Default = True 

207 

208 nProcesses : integer, optional 

209 Number of processes for multiprocessing 

210 Default = number of CPUs in the system 

211 

212 verbose : boolean, optional 

213 Print progress? 

214 Default = True 

215 

216 Returns 

217 ------- 

218 Ffield: nz x ny x nx x 3x3 array of n cells 

219 The field of the transformation gradient tensors 

220 

221 Note 

222 ---- 

223 Taken from the implementation in "TomoWarp2: A local digital volume correlation code", Tudisco et al., 2017 

224 """ 

225 import scipy.spatial 

226 

227 # Define dimensions 

228 dims = displacementField.shape[0:3] 

229 nNodes = dims[0] * dims[1] * dims[2] 

230 displacementFieldFlat = displacementField.reshape(nNodes, 3) 

231 

232 # Check if a 2D field is passed 

233 twoD = False 

234 if dims[0] == 1: 

235 twoD = True 

236 

237 # Deformation gradient tensor F = du/dx +I 

238 # Ffield = numpy.zeros((dims[0], dims[1], dims[2], 3, 3)) 

239 FfieldFlat = numpy.zeros((nNodes, 3, 3)) 

240 

241 if twoD: 

242 fieldCoordsFlat = ( 

243 numpy.mgrid[ 

244 0:1:1, 

245 nodeSpacing[1] : dims[1] * nodeSpacing[1] + nodeSpacing[1] : nodeSpacing[1], 

246 nodeSpacing[2] : dims[2] * nodeSpacing[2] + nodeSpacing[2] : nodeSpacing[2], 

247 ] 

248 .reshape(3, nNodes) 

249 .T 

250 ) 

251 else: 

252 fieldCoordsFlat = ( 

253 numpy.mgrid[ 

254 nodeSpacing[0] : dims[0] * nodeSpacing[0] + nodeSpacing[0] : nodeSpacing[0], 

255 nodeSpacing[1] : dims[1] * nodeSpacing[1] + nodeSpacing[1] : nodeSpacing[1], 

256 nodeSpacing[2] : dims[2] * nodeSpacing[2] + nodeSpacing[2] : nodeSpacing[2], 

257 ] 

258 .reshape(3, nNodes) 

259 .T 

260 ) 

261 

262 # Get non-nan displacements 

263 goodPointsMask = numpy.isfinite(displacementField[:, :, :, 0].reshape(nNodes)) 

264 badPointsMask = numpy.isnan(displacementField[:, :, :, 0].reshape(nNodes)) 

265 # Flattened variables 

266 fieldCoordsFlatGood = fieldCoordsFlat[goodPointsMask] 

267 displacementFieldFlatGood = displacementFieldFlat[goodPointsMask] 

268 # set bad points to nan 

269 FfieldFlat[badPointsMask] = numpy.eye(3) * numpy.nan 

270 

271 # build KD-tree for neighbour identification 

272 treeCoord = scipy.spatial.KDTree(fieldCoordsFlatGood) 

273 

274 # Output array for good points 

275 FfieldFlatGood = numpy.zeros_like(FfieldFlat[goodPointsMask]) 

276 

277 # Function for parallel mode 

278 global _multiprocessingGeersOnePoint 

279 

280 def _multiprocessingGeersOnePoint(goodPoint): 

281 # This is for the linear model, equation 15 in Geers 

282 centralNodePosition = fieldCoordsFlatGood[goodPoint] 

283 centralNodeDisplacement = displacementFieldFlatGood[goodPoint] 

284 sX0X0 = numpy.zeros((3, 3)) 

285 sX0Xt = numpy.zeros((3, 3)) 

286 m0 = numpy.zeros(3) 

287 mt = numpy.zeros(3) 

288 

289 # Option 2: KDTree on distance 

290 # KD-tree will always give the current point as zero-distance 

291 ind = treeCoord.query_ball_point(centralNodePosition, neighbourRadius * max(nodeSpacing)) 

292 

293 # We know that the current point will also be included, so remove it from the index list. 

294 ind = numpy.array(ind) 

295 ind = ind[ind != goodPoint] 

296 nNeighbours = len(ind) 

297 nodalRelativePositionsRef = numpy.zeros((nNeighbours, 3)) # Delta_X_0 in paper 

298 nodalRelativePositionsDef = numpy.zeros((nNeighbours, 3)) # Delta_X_t in paper 

299 

300 for neighbour, i in enumerate(ind): 

301 # Relative position in reference configuration 

302 # absolute position of this neighbour node 

303 # minus abs position of central node 

304 nodalRelativePositionsRef[neighbour, :] = fieldCoordsFlatGood[i] - centralNodePosition 

305 

306 # Relative position in deformed configuration (i.e., plus displacements) 

307 # absolute position of this neighbour node 

308 # plus displacement of this neighbour node 

309 # minus abs position of central node 

310 # minus displacement of central node 

311 nodalRelativePositionsDef[neighbour, :] = fieldCoordsFlatGood[i] + displacementFieldFlatGood[i] - centralNodePosition - centralNodeDisplacement 

312 

313 for u in range(3): 

314 for v in range(3): 

315 # sX0X0[u, v] += nodalRelativePositionsRef[neighbour, u] * nodalRelativePositionsRef[neighbour, v] 

316 # sX0Xt[u, v] += nodalRelativePositionsRef[neighbour, u] * nodalRelativePositionsDef[neighbour, v] 

317 # Proposed solution for #142 for direction of rotation 

318 sX0X0[v, u] += nodalRelativePositionsRef[neighbour, u] * nodalRelativePositionsRef[neighbour, v] 

319 sX0Xt[v, u] += nodalRelativePositionsRef[neighbour, u] * nodalRelativePositionsDef[neighbour, v] 

320 

321 m0 += nodalRelativePositionsRef[neighbour, :] 

322 mt += nodalRelativePositionsDef[neighbour, :] 

323 

324 sX0X0 = nNeighbours * sX0X0 

325 sX0Xt = nNeighbours * sX0Xt 

326 

327 A = sX0X0 - numpy.dot(m0, m0) 

328 C = sX0Xt - numpy.dot(m0, mt) 

329 F = numpy.zeros((3, 3)) 

330 

331 if twoD: 

332 A = A[1:, 1:] 

333 C = C[1:, 1:] 

334 try: 

335 F[1:, 1:] = numpy.dot(numpy.linalg.inv(A), C) 

336 F[0, 0] = 1.0 

337 except numpy.linalg.LinAlgError: 

338 # print("spam.deformation.deformationField.FfieldRegularGeers(): LinAlgError: A", A) 

339 pass 

340 else: 

341 try: 

342 F = numpy.dot(numpy.linalg.inv(A), C) 

343 except numpy.linalg.LinAlgError: 

344 # print("spam.deformation.deformationField.FfieldRegularGeers(): LinAlgError: A", A) 

345 pass 

346 

347 return goodPoint, F 

348 

349 # Iterate through flat field of Fs 

350 if verbose: 

351 pbar = progressbar.ProgressBar(maxval=fieldCoordsFlatGood.shape[0]).start() 

352 finishedPoints = 0 

353 

354 # Run multiprocessing filling in FfieldFlatGood, which will then update FfieldFlat 

355 with multiprocessing.Pool(processes=nProcesses) as pool: 

356 for returns in pool.imap_unordered(_multiprocessingGeersOnePoint, range(fieldCoordsFlatGood.shape[0])): 

357 if verbose: 

358 finishedPoints += 1 

359 pbar.update(finishedPoints) 

360 FfieldFlatGood[returns[0]] = returns[1] 

361 

362 if verbose: 

363 pbar.finish() 

364 

365 FfieldFlat[goodPointsMask] = FfieldFlatGood 

366 return FfieldFlat.reshape(dims[0], dims[1], dims[2], 3, 3) 

367 

368 

369def FfieldBagi(points, connectivity, displacements, nProcesses=nProcessesDefault, verbose=False): 

370 """ 

371 Calculates transformation gradient function using Bagi's 1996 paper, especially equation 3 on page 174. 

372 Required inputs are connectivity matrix for tetrahedra (for example from spam.mesh.triangulate) and 

373 nodal positions in reference and deformed configurations. 

374 

375 Parameters 

376 ---------- 

377 points : m x 3 numpy array 

378 M Particles' points in reference configuration 

379 

380 connectivity : n x 4 numpy array 

381 Delaunay triangulation connectivity generated by spam.mesh.triangulate for example 

382 

383 displacements : m x 3 numpy array 

384 M Particles' displacement 

385 

386 nProcesses : integer, optional 

387 Number of processes for multiprocessing 

388 Default = number of CPUs in the system 

389 

390 verbose : boolean, optional 

391 Print progress? 

392 Default = True 

393 

394 Returns 

395 ------- 

396 Ffield: nx3x3 array of n cells 

397 The field of the transformation gradient tensors 

398 """ 

399 import spam.mesh 

400 

401 Ffield = numpy.zeros([connectivity.shape[0], 3, 3], dtype="<f4") 

402 

403 connectivity = connectivity.astype(numpy.uint) 

404 

405 # define dimension 

406 # D = 3.0 

407 

408 # Import modules 

409 

410 # Construct 4-list of 3-lists of combinations constituting a face of the tet 

411 combs = [[0, 1, 2], [1, 2, 3], [2, 3, 0], [0, 1, 3]] 

412 unode = [3, 0, 1, 2] 

413 

414 # Precompute tetrahedron Volumes 

415 tetVolumes = spam.mesh.tetVolumes(points, connectivity) 

416 

417 # Initialize arrays for tet strains 

418 # print("spam.mesh.bagiStrain(): Constructing strain from Delaunay and Displacements") 

419 

420 # Loop through tetrahdra to get avec1, uPos1 

421 global _multiprocessingComputeOneTet 

422 

423 def _multiprocessingComputeOneTet(tet): 

424 # Get the list of IDs, centroids, center of tet 

425 tetIDs = connectivity[tet, :] 

426 # 2019-10-07 EA: Skip references to missing particles 

427 # if max(tetIDs) >= points.shape[0]: 

428 # print("spam.mesh.unstructured.bagiStrain(): this tet has node > points.shape[0], skipping") 

429 # pass 

430 # else: 

431 if True: 

432 tetCoords = points[tetIDs, :] 

433 tetDisp = displacements[tetIDs, :] 

434 tetCen = numpy.average(tetCoords, axis=0) 

435 if numpy.isfinite(tetCoords).sum() + numpy.isfinite(tetDisp).sum() != 3 * 4 * 2: 

436 if verbose: 

437 print("spam.mesh.unstructured.bagiStrain(): nans in position or displacement, skipping") 

438 # Compute strains 

439 F = numpy.zeros((3, 3)) * numpy.nan 

440 else: 

441 # Loop through each face of tet to get avec, upos (Bagi, 1996, pg. 172) 

442 # aVec1 = numpy.zeros([4, 3], dtype='<f4') 

443 # uPos1 = numpy.zeros([4, 3], dtype='<f4') 

444 # uPos2 = numpy.zeros([4, 3], dtype='<f4') 

445 dudx = numpy.zeros((3, 3), dtype="<f4") 

446 

447 for face in range(4): 

448 faceNorm = numpy.cross( 

449 tetCoords[combs[face][0]] - tetCoords[combs[face][1]], 

450 tetCoords[combs[face][0]] - tetCoords[combs[face][2]], 

451 ) 

452 

453 # Get a norm vector to face point towards center of tet 

454 faceCen = numpy.average(tetCoords[combs[face]], axis=0) 

455 tmpnorm = faceNorm / (numpy.linalg.norm(faceNorm)) 

456 facetocen = tetCen - faceCen 

457 if numpy.dot(facetocen, tmpnorm) < 0: 

458 tmpnorm = -tmpnorm 

459 

460 # Divide by 6 (1/3 for 1/Dimension; 1/2 for area from cross product) 

461 # See first eqn., Bagi, 1996, pg. 172. 

462 # aVec1[face] = tmpnorm*numpy.linalg.norm(faceNorm)/6 

463 

464 # Undeformed positions 

465 # uPos1[face] = tetCoords[unode[face]] 

466 # Deformed positions 

467 # uPos2[face] = tetComs2[unode[face]] 

468 

469 dudx += numpy.tensordot( 

470 tetDisp[unode[face]], 

471 tmpnorm * numpy.linalg.norm(faceNorm) / 6, 

472 axes=0, 

473 ) 

474 

475 dudx /= float(tetVolumes[tet]) 

476 

477 F = numpy.eye(3) + dudx 

478 return tet, F 

479 

480 # Iterate through flat field of Fs 

481 if verbose: 

482 pbar = progressbar.ProgressBar(maxval=connectivity.shape[0]).start() 

483 finishedTets = 0 

484 

485 # Run multiprocessing 

486 with multiprocessing.Pool(processes=nProcesses) as pool: 

487 for returns in pool.imap_unordered(_multiprocessingComputeOneTet, range(connectivity.shape[0])): 

488 if verbose: 

489 finishedTets += 1 

490 pbar.update(finishedTets) 

491 Ffield[returns[0]] = returns[1] 

492 pool.close() 

493 pool.join() 

494 

495 if verbose: 

496 pbar.finish() 

497 

498 return Ffield 

499 

500 

501def decomposeFfield(Ffield, components, twoD=False, nProcesses=nProcessesDefault, verbose=False): 

502 """ 

503 This function takes in an F field (from either FfieldRegularQ8, FfieldRegularGeers, FfieldBagi) and 

504 returns fields of desired transformation components. 

505 

506 Parameters 

507 ---------- 

508 Ffield : multidimensional x 3 x 3 numpy array of floats 

509 Spatial field of Fs 

510 

511 components : list of strings 

512 These indicate the desired components consistent with spam.deformation.decomposeF or decomposePhi 

513 

514 twoD : bool, optional 

515 Is the Ffield in 2D? This changes the strain calculation. 

516 Default = False 

517 

518 nProcesses : integer, optional 

519 Number of processes for multiprocessing 

520 Default = number of CPUs in the system 

521 

522 verbose : boolean, optional 

523 Print progress? 

524 Default = True 

525 

526 Returns 

527 ------- 

528 Dictionary containing appropriately reshaped fields of the transformation components requested. 

529 

530 Keys: 

531 vol, dev, volss, devss are scalars 

532 r, z, Up are 3-component vectors 

533 e and U are 3x3 tensors 

534 """ 

535 # The last two are for the 3x3 F field 

536 fieldDimensions = Ffield.shape[0:-2] 

537 fieldRavelLength = numpy.prod(numpy.array(fieldDimensions)) 

538 FfieldFlat = Ffield.reshape(fieldRavelLength, 3, 3) 

539 

540 output = {} 

541 for component in components: 

542 if component == "vol" or component == "dev" or component == "volss" or component == "devss": 

543 output[component] = numpy.zeros(fieldRavelLength) 

544 if component == "r" or component == "z" or component == "Up": 

545 output[component] = numpy.zeros((fieldRavelLength, 3)) 

546 if component == "U" or component == "e": 

547 output[component] = numpy.zeros((fieldRavelLength, 3, 3)) 

548 

549 # Function for parallel mode 

550 global _multiprocessingDecomposeOneF 

551 

552 def _multiprocessingDecomposeOneF(n): 

553 F = FfieldFlat[n] 

554 if numpy.isfinite(F).sum() == 9: 

555 decomposedF = spam.deformation.decomposeF(F, twoD=twoD) 

556 return n, decomposedF 

557 else: 

558 return n, { 

559 "r": numpy.array([numpy.nan] * 3), 

560 "z": numpy.array([numpy.nan] * 3), 

561 "Up": numpy.array([numpy.nan] * 3), 

562 "U": numpy.eye(3) * numpy.nan, 

563 "e": numpy.eye(3) * numpy.nan, 

564 "vol": numpy.nan, 

565 "dev": numpy.nan, 

566 "volss": numpy.nan, 

567 "devss": numpy.nan, 

568 } 

569 

570 # Iterate through flat field of Fs 

571 if verbose: 

572 pbar = progressbar.ProgressBar(maxval=fieldRavelLength).start() 

573 finishedPoints = 0 

574 

575 # Run multiprocessing 

576 with multiprocessing.Pool(processes=nProcesses) as pool: 

577 for returns in pool.imap_unordered(_multiprocessingDecomposeOneF, range(fieldRavelLength)): 

578 if verbose: 

579 finishedPoints += 1 

580 pbar.update(finishedPoints) 

581 for component in components: 

582 output[component][returns[0]] = returns[1][component] 

583 pool.close() 

584 pool.join() 

585 

586 if verbose: 

587 pbar.finish() 

588 

589 # Reshape on the output 

590 for component in components: 

591 if component == "vol" or component == "dev" or component == "volss" or component == "devss": 

592 output[component] = numpy.array(output[component]).reshape(fieldDimensions) 

593 

594 if component == "r" or component == "z" or component == "Up": 

595 output[component] = numpy.array(output[component]).reshape(Ffield.shape[0:-1]) 

596 

597 if component == "U" or component == "e": 

598 output[component] = numpy.array(output[component]).reshape(Ffield.shape) 

599 

600 return output 

601 

602 

603def decomposePhiField(PhiField, components, twoD=False, nProcesses=nProcessesDefault, verbose=False): 

604 """ 

605 This function takes in a Phi field (from readCorrelationTSV?) and 

606 returns fields of desired transformation components. 

607 

608 Parameters 

609 ---------- 

610 PhiField : multidimensional x 4 x 4 numpy array of floats 

611 Spatial field of Phis 

612 

613 components : list of strings 

614 These indicate the desired components consistent with spam.deformation.decomposePhi 

615 

616 twoD : bool, optional 

617 Is the PhiField in 2D? This changes the strain calculation. 

618 Default = False 

619 

620 nProcesses : integer, optional 

621 Number of processes for multiprocessing 

622 Default = number of CPUs in the system 

623 

624 verbose : boolean, optional 

625 Print progress? 

626 Default = True 

627 

628 Returns 

629 ------- 

630 Dictionary containing appropriately reshaped fields of the transformation components requested. 

631 

632 Keys: 

633 vol, dev, volss, devss are scalars 

634 t, r, z and Up are 3-component vectors 

635 e and U are 3x3 tensors 

636 """ 

637 # The last two are for the 4x4 Phi field 

638 fieldDimensions = PhiField.shape[0:-2] 

639 fieldRavelLength = numpy.prod(numpy.array(fieldDimensions)) 

640 PhiFieldFlat = PhiField.reshape(fieldRavelLength, 4, 4) 

641 

642 output = {} 

643 for component in components: 

644 if component == "vol" or component == "dev" or component == "volss" or component == "devss": 

645 output[component] = numpy.zeros(fieldRavelLength) 

646 if component == "t" or component == "r" or component == "z" or component == "Up": 

647 output[component] = numpy.zeros((fieldRavelLength, 3)) 

648 if component == "U" or component == "e": 

649 output[component] = numpy.zeros((fieldRavelLength, 3, 3)) 

650 

651 # Function for parallel mode 

652 global _multiprocessingDecomposeOnePhi 

653 

654 def _multiprocessingDecomposeOnePhi(n): 

655 Phi = PhiFieldFlat[n] 

656 if numpy.isfinite(Phi).sum() == 16: 

657 decomposedPhi = spam.deformation.decomposePhi(Phi, twoD=twoD) 

658 return n, decomposedPhi 

659 else: 

660 return n, { 

661 "t": numpy.array([numpy.nan] * 3), 

662 "r": numpy.array([numpy.nan] * 3), 

663 "z": numpy.array([numpy.nan] * 3), 

664 "Up": numpy.array([numpy.nan] * 3), 

665 "U": numpy.eye(3) * numpy.nan, 

666 "e": numpy.eye(3) * numpy.nan, 

667 "vol": numpy.nan, 

668 "dev": numpy.nan, 

669 "volss": numpy.nan, 

670 "devss": numpy.nan, 

671 } 

672 

673 # Iterate through flat field of Fs 

674 if verbose: 

675 pbar = progressbar.ProgressBar(maxval=fieldRavelLength).start() 

676 finishedPoints = 0 

677 

678 # Run multiprocessing 

679 with multiprocessing.Pool(processes=nProcesses) as pool: 

680 for returns in pool.imap_unordered(_multiprocessingDecomposeOnePhi, range(fieldRavelLength)): 

681 if verbose: 

682 finishedPoints += 1 

683 pbar.update(finishedPoints) 

684 for component in components: 

685 output[component][returns[0]] = returns[1][component] 

686 pool.close() 

687 pool.join() 

688 

689 if verbose: 

690 pbar.finish() 

691 

692 # Reshape on the output 

693 for component in components: 

694 if component == "vol" or component == "dev" or component == "volss" or component == "devss": 

695 output[component] = numpy.array(output[component]).reshape(*PhiField.shape[0:-2]) 

696 

697 if component == "t" or component == "r" or component == "z": 

698 output[component] = numpy.array(output[component]).reshape(*PhiField.shape[0:-2], 3) 

699 

700 if component == "U" or component == "e": 

701 output[component] = numpy.array(output[component]).reshape(*PhiField.shape[0:-2], 3, 3) 

702 

703 return output