Coverage for src/sdynpy/fem/sdynpy_exodus.py: 8%

1773 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-18 21:32 +0000

1# -*- coding: utf-8 -*- 

2""" 

3Import data from and save data to Exodus files. 

4""" 

5""" 

6Copyright 2022 National Technology & Engineering Solutions of Sandia, 

7LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. 

8Government retains certain rights in this software. 

9 

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

11it under the terms of the GNU General Public License as published by 

12the Free Software Foundation, either version 3 of the License, or 

13(at your option) any later version. 

14 

15This program is distributed in the hope that it will be useful, 

16but WITHOUT ANY WARRANTY; without even the implied warranty of 

17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18GNU General Public License for more details. 

19 

20You should have received a copy of the GNU General Public License 

21along with this program. If not, see <https://www.gnu.org/licenses/>. 

22""" 

23 

24import netCDF4 

25import numpy as np 

26from types import SimpleNamespace 

27import datetime 

28from importlib.metadata import version 

29import copy 

30from ..core.sdynpy_geometry import global_coord, local_coord, global_deflection, _exodus_elem_type_map 

31from ..core.sdynpy_shape import ShapeArray 

32from ..core.sdynpy_data import TimeHistoryArray 

33 

34_inverse_exodus_elem_type_map = {val: key for key, val in _exodus_elem_type_map.items()} 

35_inverse_exodus_elem_type_map[61] = "tri3" 

36_inverse_exodus_elem_type_map[91] = "tri3" 

37_inverse_exodus_elem_type_map[41] = "tri3" 

38_inverse_exodus_elem_type_map[64] = "quad4" 

39_inverse_exodus_elem_type_map[94] = "quad4" 

40_inverse_exodus_elem_type_map[44] = "quad4" 

41 

42__version__ = '0.99' 

43 

44 

45def face_connectivity(reduced_element_type, keep_midside_nodes): 

46 if reduced_element_type.lower() == 'tetra10' and keep_midside_nodes: 

47 return (('tri6', np.array([[0, 1, 3, 4, 8, 7], 

48 [1, 2, 3, 5, 9, 8], 

49 [2, 0, 3, 6, 7, 9], 

50 [0, 2, 1, 6, 5, 4]])),) 

51 elif reduced_element_type.lower() == 'tetra4' or (reduced_element_type == 'tetra10' and not keep_midside_nodes): 

52 return (('tri3', np.array([[0, 1, 3], 

53 [1, 2, 3], 

54 [2, 0, 3], 

55 [0, 2, 1]])),) 

56 elif reduced_element_type.lower() == 'hex20' and keep_midside_nodes: 

57 return (('quad8', np.array([[0, 1, 5, 4, 8, 13, 16, 12], 

58 [1, 2, 6, 5, 9, 14, 17, 13], 

59 [2, 3, 7, 6, 10, 15, 18, 14], 

60 [3, 0, 4, 7, 11, 12, 19, 15], 

61 [3, 2, 1, 0, 11, 8, 9, 10], 

62 [4, 5, 6, 7, 16, 17, 18, 19]])),) 

63 elif reduced_element_type.lower() == 'hex8' or reduced_element_type.lower() == 'hex' or (reduced_element_type == 'hex20' and not keep_midside_nodes): 

64 return (('quad4', np.array([[0, 1, 5, 4], 

65 [1, 2, 6, 5], 

66 [2, 3, 7, 6], 

67 [3, 0, 4, 7], 

68 [3, 2, 1, 0], 

69 [4, 5, 6, 7]])),) 

70 else: 

71 return ((None, None),) 

72 

73 

74def mesh_triangulation_array(element_type): 

75 if element_type == 'quad4': 

76 triangles = np.array([[0, 1, 2], 

77 [0, 2, 3]]) 

78 elif element_type == 'quad8': 

79 triangles = np.array([[0, 4, 7], 

80 [4, 1, 5], 

81 [5, 2, 6], 

82 [7, 6, 3], 

83 [4, 6, 7], 

84 [4, 5, 6]]) 

85 else: 

86 triangles = None 

87 return triangles 

88 

89 

90class ExodusError(Exception): 

91 pass 

92 

93 

94class Exodus: 

95 '''Read or write exodus files. 

96 

97 This class creates functionality to read or write exodus files using the 

98 netCDF4 python module. 

99 

100 Parameters 

101 ---------- 

102 filename : str 

103 The path string to the file that will be opened 

104 mode : str 

105 Mode with which the file is opened, 'r' - Read, 'w' - Write, 

106 'a' - Append. Default is 'r'. 

107 title : str 

108 Title of the exodus file, only required if mode='w'. 

109 num_dims : int 

110 Number of dimensions in the exodus file, only required if mode='w'. 

111 num_nodes : int 

112 Number of nodes in the exodus file, only required if mode='w'. 

113 num_elem : int 

114 Number of elements in the exodus file, only required if mode='w'. 

115 num_blocks : int 

116 Number of blocks in the exodus file, only required if mode='w'. 

117 num_node_sets : int 

118 Number of node sets in the exodus file, only required if mode = 'w'. 

119 num_side_sets : int 

120 Number of side sets in the exodus file, only required if mode='w'. 

121 ''' 

122 

123 def __init__(self, filename, mode='r', 

124 title=None, num_dims=None, num_nodes=None, num_elem=None, 

125 num_blocks=None, num_node_sets=None, num_side_sets=None, 

126 clobber=False): 

127 self.filename = filename 

128 self.mode_character = mode 

129 if mode in ['r', 'a']: 

130 # Here we read in a file that already exists 

131 self._ncdf_handle = netCDF4.Dataset(filename, mode) 

132 else: 

133 # Here we have to create one first 

134 self._ncdf_handle = netCDF4.Dataset(filename, mode, clobber=clobber, 

135 format='NETCDF3_64BIT_OFFSET') 

136 # Assign attributes 

137 self._ncdf_handle.api_version = 5.22 

138 self._ncdf_handle.version = 5.22 

139 self._ncdf_handle.floating_point_word_size = 8 

140 self._ncdf_handle.file_size = 1 

141 self._ncdf_handle.int64_status = 0 

142 if title is None: 

143 title = '' 

144 self._ncdf_handle.title = title 

145 

146 # Assign Dimensions 

147 self._ncdf_handle.createDimension('len_string', 33) 

148 self._ncdf_handle.createDimension('len_name', 33) 

149 self._ncdf_handle.createDimension('len_line', 81) 

150 self._ncdf_handle.createDimension('four', 4) 

151 if num_dims is None: 

152 raise ValueError("num_dims must be assigned for mode 'w'") 

153 self._ncdf_handle.createDimension('num_dim', num_dims) 

154 if num_nodes is None: 

155 raise ValueError("num_nodes must be assigned for mode 'w'") 

156 self._ncdf_handle.createDimension('num_nodes', num_nodes) 

157 if num_elem is None: 

158 raise ValueError("num_elem must be assigned for mode 'w'") 

159 self._ncdf_handle.createDimension('num_elem', num_elem) 

160 if num_blocks is None: 

161 raise ValueError("num_blocks must be assigned for mode 'w'") 

162 self._ncdf_handle.createDimension('num_el_blk', num_blocks) 

163 if num_node_sets is None: 

164 raise ValueError("num_node_sets must be assigned for mode 'w'") 

165 if num_node_sets > 0: 

166 self._ncdf_handle.createDimension('num_node_sets', num_node_sets) 

167 if num_side_sets is None: 

168 raise ValueError("num_side_sets must be assigned for mode 'w'") 

169 if num_side_sets > 0: 

170 self._ncdf_handle.createDimension('num_side_sets', num_side_sets) 

171 

172 # Create convenience variables 

173 if self._ncdf_handle.floating_point_word_size == 8: 

174 self.floating_point_type = 'f8' 

175 else: 

176 self.floating_point_type = 'f4' 

177 

178 # Initialize time 

179 # The time dimension is expandable in the Exodus format so we will 

180 # set the size of the dimension to none, which means unlimited. 

181 self._ncdf_handle.createDimension('time_step', None) 

182 # Initialize the time variable 

183 self._ncdf_handle.createVariable('time_whole', self.floating_point_type, 

184 ('time_step',)) 

185 

186 @property 

187 def title(self): 

188 '''Get the title of the exodus file''' 

189 return self._ncdf_handle.title 

190 

191 def get_qa_records(self): 

192 '''Get the quality assurance records in the exodus file 

193 

194 Returns 

195 ------- 

196 qa_records : tuple 

197 Returns a nested tuple of strings where the first index is the 

198 record number, and the second is the line in the record. 

199 

200 ''' 

201 try: 

202 raw_records = self._ncdf_handle.variables['qa_records'][:] 

203 except KeyError: 

204 raise ExodusError('QA Records are not yet defined!') 

205 qa_records = tuple(tuple(''.join(value.decode() for value in line) 

206 for line in record) for record in raw_records.data) 

207 return qa_records 

208 

209 def put_qa_records(self, records): 

210 '''Puts the quality assurance records in the exodus file 

211 

212 Parameters 

213 ---------- 

214 qa_records : sequence of sequence of strings 

215 A nested sequence (list/tuple/etc.) containing the quality assurance 

216 records. 

217 

218 

219 Notes 

220 ----- 

221 Each index in qa_records should consist of a length-4 tuple of strings: 

222 

223 1. Analysis Code Name 

224 2. Analysis Code Version 

225 3. Analysis Date 

226 4. Analysis Time 

227 ''' 

228 if 'num_qa_rec' in self._ncdf_handle.dimensions: 

229 raise ExodusError('QA Records have already been put to the exodus file') 

230 # Make sure that it is the correct shape 

231 num_qa_rec = len(records) 

232 len_string = self._ncdf_handle.dimensions['len_string'].size 

233 array = np.zeros((num_qa_rec, 4, len_string), dtype='|S1') 

234 for i, record in enumerate(records): 

235 if len(record) > 4: 

236 raise ValueError('QA Records can only have 4 entries per record!') 

237 for j, line in enumerate(record): 

238 if len(line) > len_string: 

239 print('Warning:When setting QA records, entry "{}" (record {} entry {}) will be truncated'.format( 

240 line, i, j)) 

241 line = line[:len_string] 

242 listed_string = [val for i, val in enumerate(line)] 

243 array[i, j, :len(listed_string)] = listed_string 

244 # Create the dimension 

245 self._ncdf_handle.createDimension('num_qa_rec', num_qa_rec) 

246 # Create the variable 

247 self._ncdf_handle.createVariable('qa_records', 'S1', ('num_qa_rec', 'four', 'len_string')) 

248 # Assign to it 

249 self._ncdf_handle.variables['qa_records'][:] = array[:] 

250 

251 def get_info_records(self): 

252 '''Get the information records in the exodus file 

253 

254 Returns 

255 ------- 

256 info_records : tuple 

257 Returns a tuple of strings where the index is the 

258 record number. 

259 ''' 

260 try: 

261 raw_records = self._ncdf_handle.variables['info_records'][:] 

262 except KeyError: 

263 raise ExodusError('Information Records are not yet defined!') 

264 info_records = tuple(''.join(value.decode() for value in line) for line in raw_records.data) 

265 return info_records 

266 

267 def put_info_records(self, records): 

268 '''Puts the information records in the exodus file 

269 

270 Parameters 

271 ---------- 

272 info_records : sequence of strings 

273 A sequence (list/tuple/etc.) containing the information records. 

274 ''' 

275 if 'num_info' in self._ncdf_handle.dimensions: 

276 raise ExodusError('Information Records have already been put to the exodus file') 

277 # Make sure that it is the correct shape 

278 num_info = len(records) 

279 len_line = self._ncdf_handle.dimensions['len_line'].size 

280 array = np.zeros((num_info, len_line), dtype='|S1') 

281 for i, line in enumerate(records): 

282 if len(line) > len_line: 

283 print('Warning:When setting info records, entry "{}" (record {}) will be truncated'.format(line, i)) 

284 line = line[:len_line] 

285 listed_string = [val for i, val in enumerate(line)] 

286 array[i, :len(listed_string)] = listed_string 

287 # Create the dimension 

288 self._ncdf_handle.createDimension('num_info', num_info) 

289 # Create the variable 

290 self._ncdf_handle.createVariable('info_records', 'S1', ('num_info', 'len_line')) 

291 # Assign to it 

292 self._ncdf_handle.variables['info_records'][:] = array[:] 

293 

294 @property 

295 def num_times(self): 

296 return self._ncdf_handle.dimensions['time_step'].size 

297 

298 def get_times(self, indices=None): 

299 '''Gets the time values from the exodus file 

300 

301 Returns 

302 ------- 

303 time_array : np.masked_array 

304 A masked_array containing the time values. 

305 ''' 

306 if indices is None: 

307 indices = slice(None) 

308 elif np.size(indices) == 0: 

309 return np.ma.zeros((0,)) 

310 return self._ncdf_handle.variables['time_whole'][indices] 

311 

312 def set_time(self, step, value): 

313 '''Sets the time value of a given step in the exodus file 

314 

315 Parameters 

316 ---------- 

317 step : int 

318 The index in the time vector to set to value 

319 value : float 

320 A real number to set the value of the specified index in the time 

321 vector to. 

322 

323 Notes 

324 ----- 

325 If step is not a valid index for the time vector, the time vector will 

326 be expanded so that it is. 

327 ''' 

328 self._ncdf_handle.variables['time_whole'][step] = value 

329 

330 def set_times(self, values): 

331 '''Sets the time vector for the exodus file 

332 

333 Parameters 

334 ---------- 

335 values : array-like 

336 A 1-dimensional array that has the time step values as it's entries 

337 

338 Notes 

339 ----- 

340 If the vector is longer than the current time step vector, it will be 

341 expanded. If the vector is shorter than the current time step vector, 

342 only the first len(values) entries of the time vector will be assigned. 

343 ''' 

344 self._ncdf_handle.variables['time_whole'][:] = values 

345 

346 @property 

347 def num_dimensions(self): 

348 return self._ncdf_handle.dimensions['num_dim'].size 

349 

350 r''' 

351 _ _ _ 

352 | \ | | ___ __| | ___ ___ 

353 | \| |/ _ \ / _` |/ _ \/ __| 

354 | |\ | (_) | (_| | __/\__ \ 

355 |_| \_|\___/ \__,_|\___||___/ 

356 ''' 

357 

358 @property 

359 def num_nodes(self): 

360 return self._ncdf_handle.dimensions['num_nodes'].size 

361 

362 def get_coord_names(self): 

363 '''Retrieve the coordinate names in the exodus file. 

364 

365 Returns 

366 ------- 

367 coord_names : tuple 

368 Returns a tuple of strings containing the coordinate names in the 

369 exodus file. 

370 ''' 

371 try: 

372 raw_records = self._ncdf_handle.variables['coor_names'] 

373 except KeyError: 

374 raise ExodusError('Coordinate Names are not yet defined!') 

375 coord_names = tuple(''.join(value.decode() for value in line if not isinstance( 

376 value, np.ma.core.MaskedConstant)) for line in raw_records) 

377 

378 return coord_names 

379 

380 def put_coord_names(self, coord_names): 

381 '''Puts the coordinate names into the exodus file 

382 

383 Parameters 

384 ---------- 

385 coord_names : sequence of strings 

386 A sequence (list/tuple/etc.) containing the coordinate names. 

387 ''' 

388 if 'coor_names' in self._ncdf_handle.variables: 

389 raise ExodusError('Coordinate Names have already been put to the exodus file') 

390 # Make sure that it is the correct shape 

391 num_dim = self.num_dimensions # self._ncdf_handle.dimensions['num_dim'].size 

392 len_name = self._ncdf_handle.dimensions['len_name'].size 

393 array = np.zeros((num_dim, len_name), dtype='|S1') 

394 for i, line in enumerate(coord_names): 

395 if len(line) > len_name: 

396 print('Warning:When setting info records, entry "{}" (record {}) will be truncated'.format(line, i)) 

397 line = line[:len_name] 

398 listed_string = [val for i, val in enumerate(line)] 

399 # Create the coordinate variables 

400 array[i, :len(listed_string)] = listed_string 

401 # Create the variable 

402 self._ncdf_handle.createVariable('coor_names', 'S1', ('num_dim', 'len_name')) 

403 # Assign to it 

404 self._ncdf_handle.variables['coor_names'][:] = array[:] 

405 

406 def get_coords(self): 

407 '''Retrieve the coordinates of the nodes in the exodus file. 

408 

409 Returns 

410 ------- 

411 coords : np.array 

412 Returns a 2D array with size num_dims x num_nodes 

413 ''' 

414 # TODO Add error checking 

415 coord_names = ('coordx', 'coordy', 'coordz')[:self.num_dimensions] 

416 raw_list = [self._ncdf_handle.variables[name][:] for name in coord_names] 

417 coords = np.array(raw_list) 

418 return coords 

419 

420 def get_coord(self, index): 

421 '''Retrieve the coordinates of the specified node in the exodus file. 

422 

423 Parameters 

424 ---------- 

425 index : int 

426 The global node index (not node number) of the node of which the 

427 coordinates are desired 

428 

429 Returns 

430 ------- 

431 coords : np.array 

432 Returns an array with size num_dims 

433 ''' 

434 # TODO Add Error Checking 

435 coord_names = ('coordx', 'coordy', 'coordz')[:self.num_dimensions] 

436 raw_list = [self._ncdf_handle.variables[name][index] for name in coord_names] 

437 coords = np.array(raw_list) 

438 return coords 

439 

440 def put_coords(self, coords): 

441 '''Puts the coordinate values into the exodus file 

442 

443 Parameters 

444 ---------- 

445 coords : np.ndarray 

446 A 2d array containing coordinate values. 

447 ''' 

448 coord_names = ('coordx', 'coordy', 'coordz')[:self._ncdf_handle.dimensions['num_dim'].size] 

449 if coord_names[0] in self._ncdf_handle.variables: 

450 raise ExodusError('Coordinates have already been put to the exodus file') 

451 coords = np.array(coords) 

452 if coords.shape != (self.num_dimensions, self.num_nodes): 

453 raise ExodusError('coords.shape should be (self.num_dimensions,self.num_nodes)') 

454 for i, name in enumerate(coord_names): 

455 self._ncdf_handle.createVariable(name, self.floating_point_type, ('num_nodes',)) 

456 self._ncdf_handle.variables[name][:] = coords[i] 

457 

458 def get_node_num_map(self): 

459 '''Retrieve the list of local node IDs from the exodus file. 

460 

461 Returns 

462 ------- 

463 node_num_map : np.array 

464 Returns a 1D array with size num_nodes, denoting the node number 

465 for the node in each index 

466 

467 Notes 

468 ----- 

469 If there is no node_num_map in the exodus file, this function simply 

470 returns an array from 1 to self.num_nodes 

471 ''' 

472 if 'node_num_map' in self._ncdf_handle.variables: 

473 return self._ncdf_handle.variables['node_num_map'][:] 

474 else: 

475 return np.ma.MaskedArray(np.arange(self.num_nodes) + 1) 

476 

477 def put_node_num_map(self, node_num_map): 

478 '''Puts a list of local node IDs into the exodus file. 

479 

480 Parameters 

481 ---------- 

482 node_num_map : np.array 

483 A 1D array with size num_nodes, denoting the node number 

484 for the node in each index 

485 

486 ''' 

487 if 'node_num_map' in self._ncdf_handle.variables: 

488 raise ExodusError('node_num_map has already been put to the exodus file.') 

489 # Create the variable 

490 self._ncdf_handle.createVariable('node_num_map', 'int32', ('num_nodes',)) 

491 self._ncdf_handle.variables['node_num_map'][:] = node_num_map 

492 

493 def get_node_variable_names(self): 

494 '''Gets a tuple of nodal variable names from the exodus file 

495 

496 Returns 

497 ------- 

498 node_var_names : tuple of strings 

499 Returns a tuple containing the names of the nodal variables in the 

500 exodus file. 

501 ''' 

502 try: 

503 raw_records = self._ncdf_handle.variables['name_nod_var'] 

504 except KeyError: 

505 raise ExodusError('Node Variable Names are not defined!') 

506 node_var_names = tuple(''.join(value.decode() for value in line if not isinstance( 

507 value, np.ma.core.MaskedConstant)) for line in raw_records) 

508 return node_var_names 

509 

510 def put_node_variable_names(self, node_var_names): 

511 '''Puts the specified variable names in the exodus file 

512 

513 Parameters 

514 ---------- 

515 node_var_names : tuple of strings 

516 A tuple containing the names of the nodal variables in the model 

517 ''' 

518 if 'name_nod_var' in self._ncdf_handle.variables: 

519 raise ExodusError('Nodal Variable Names have already been put to the exodus file') 

520 # Make sure that it is the correct shape 

521 num_names = len(node_var_names) 

522 string_length = self._ncdf_handle.dimensions['len_name'].size 

523 array = np.zeros((num_names, string_length), dtype='|S1') 

524 for i, line in enumerate(node_var_names): 

525 if len(line) > string_length: 

526 print('Warning:When setting nodal variable names, entry "{}" (record {}) will be truncated'.format( 

527 line, i)) 

528 line = line[:string_length] 

529 listed_string = [val for i, val in enumerate(line)] 

530 # Create the coordinate variables 

531 array[i, :len(listed_string)] = listed_string 

532 self._ncdf_handle.createVariable('vals_nod_var{:d}'.format( 

533 i + 1), self.floating_point_type, ('time_step', 'num_nodes')) 

534 # Create the dimension 

535 self._ncdf_handle.createDimension('num_nod_var', num_names) 

536 # Create the variable 

537 self._ncdf_handle.createVariable('name_nod_var', 'S1', ('num_nod_var', 'len_name')) 

538 # Assign to it 

539 self._ncdf_handle.variables['name_nod_var'][:] = array[:] 

540 

541 @property 

542 def num_node_variables(self): 

543 try: 

544 return self._ncdf_handle.dimensions['num_nod_var'].size 

545 except KeyError: 

546 raise ExodusError('Number of Node Variables is not defined!') 

547 

548 def get_node_variable_values(self, name_or_index, step=None): 

549 '''Gets the node variable values for the specified timestep 

550 

551 Parameters 

552 ---------- 

553 name_or_index : str or int 

554 Name or Index of the nodal variable that is desired. If 

555 type(name_or_index) == str, then it is assumed to be the name. If 

556 type(name_or_index) == int, then it is assumed to be the index. 

557 step : int 

558 Time step at which to recover the nodal variable 

559 

560 Returns 

561 ------- 

562 node_variable_values : maskedarray 

563 A 1d array consisting of the variable values for each node at the 

564 specified time step. 

565 ''' 

566 if isinstance(name_or_index, (int, np.integer)): 

567 index = name_or_index 

568 elif isinstance(name_or_index, (str, np.character)): 

569 try: 

570 index = self.get_node_variable_names().index(name_or_index) 

571 except ValueError: 

572 raise ExodusError('Name {} not found in self.get_node_variable_names(). Options are {}'.format( 

573 name_or_index, self.get_node_variable_names())) 

574 vals_nod_var_name = 'vals_nod_var{:d}'.format(index + 1) 

575 if step is not None: 

576 if step >= self.num_times: 

577 raise ExodusError('Invalid Time Step') 

578 return self._ncdf_handle.variables[vals_nod_var_name][step, :] 

579 else: 

580 return self._ncdf_handle.variables[vals_nod_var_name][:] 

581 

582 def get_node_variable_value(self, name_or_index, node_index, step=None): 

583 '''Gets a node variable value for the specified timestep 

584 

585 Parameters 

586 ---------- 

587 name_or_index : str or int 

588 Name or Index of the nodal variable that is desired. If 

589 type(name_or_index) == str, then it is assumed to be the name. If 

590 type(name_or_index) == int, then it is assumed to be the index. 

591 node_index : int 

592 Node index at which to recover the nodal variable 

593 step : int 

594 Time step at which to recover the nodal variable 

595 Returns 

596 ------- 

597 node_variable_value : float 

598 The variable values for the specified node at the specified time step. 

599 ''' 

600 if isinstance(name_or_index, (int, np.integer)): 

601 index = name_or_index 

602 elif isinstance(name_or_index, (str, np.character)): 

603 try: 

604 index = self.get_node_variable_names().index(name_or_index) 

605 except ValueError: 

606 raise ExodusError('Name {} not found in self.get_node_variable_names(). Options are {}'.format( 

607 name_or_index, self.get_node_variable_names())) 

608 vals_nod_var_name = 'vals_nod_var{:d}'.format(index + 1) 

609 if step is not None: 

610 if step >= self.num_times: 

611 raise ExodusError('Invalid Time Step') 

612 return self._ncdf_handle.variables[vals_nod_var_name][step, node_index] 

613 else: 

614 return self._ncdf_handle.variables[vals_nod_var_name][:, node_index] 

615 

616 def set_node_variable_values(self, name_or_index, step, values): 

617 '''Sets the node variable values for the specified timestep 

618 

619 Parameters 

620 ---------- 

621 name_or_index : str or int 

622 Name or Index of the nodal variable that is desired. If 

623 type(name_or_index) == str, then it is assumed to be the name. If 

624 type(name_or_index) == int, then it is assumed to be the index. 

625 step : int 

626 Time step at which to recover the nodal variable 

627 values : array-like 

628 A 1d array consisting of the variable values for each node at the 

629 specified time step. 

630 

631 Notes 

632 ----- 

633 If step is not a valid index for the time vector, the time vector will 

634 be expanded so that it is. 

635 ''' 

636 if isinstance(name_or_index, (int, np.integer)): 

637 index = name_or_index 

638 elif isinstance(name_or_index, (str, np.character)): 

639 try: 

640 index = self.get_node_variable_names().index(name_or_index) 

641 except ValueError: 

642 raise ValueError('Name {} not found in self.get_node_variable_names(). Options are {}'.format( 

643 name_or_index, self.get_node_variable_names())) 

644 vals_nod_var_name = 'vals_nod_var{:d}'.format(index + 1) 

645 self._ncdf_handle.variables[vals_nod_var_name][step, :] = values 

646 

647 def set_node_variable_value(self, name_or_index, node_index, step, value): 

648 '''Sets the node variable values for the specified timestep 

649 

650 Parameters 

651 ---------- 

652 name_or_index : str or int 

653 Name or Index of the nodal variable that is desired. If 

654 type(name_or_index) == str, then it is assumed to be the name. If 

655 type(name_or_index) == int, then it is assumed to be the index. 

656 node_index : int 

657 Node index at which to recover the nodal variable 

658 step : int 

659 Time step at which to recover the nodal variable 

660 value : float 

661 The variable value for the specified node at the specified time step. 

662 

663 Notes 

664 ----- 

665 If step is not a valid index for the time vector, the time vector will 

666 be expanded so that it is. 

667 ''' 

668 if isinstance(name_or_index, (int, np.integer)): 

669 index = name_or_index 

670 elif isinstance(name_or_index, (str, np.character)): 

671 try: 

672 index = self.get_node_variable_names().index(name_or_index) 

673 except ValueError: 

674 raise ValueError('Name {} not found in self.get_node_variable_names(). Options are {}'.format( 

675 name_or_index, self.get_node_variable_names())) 

676 vals_nod_var_name = 'vals_nod_var{:d}'.format(index + 1) 

677 self._ncdf_handle.variables[vals_nod_var_name][step, node_index] = value 

678 

679 def get_displacements(self, displacement_name='Disp', capital_coordinates=True): 

680 return np.array([self.get_node_variable_values(displacement_name + (val.upper() if capital_coordinates else val.lower())) 

681 for val in 'xyz']) 

682 

683 r''' 

684 _____ _ _ 

685 | ____| | ___ _ __ ___ ___ _ __ | |_ ___ 

686 | _| | |/ _ \ '_ ` _ \ / _ \ '_ \| __/ __| 

687 | |___| | __/ | | | | | __/ | | | |_\__ \ 

688 |_____|_|\___|_| |_| |_|\___|_| |_|\__|___/ 

689 ''' 

690 

691 @property 

692 def num_elems(self): 

693 if 'num_elem' in self._ncdf_handle.dimensions: 

694 return self._ncdf_handle.dimensions['num_elem'].size 

695 else: 

696 return 0 

697 

698 def get_elem_num_map(self): 

699 '''Retrieve the list of local element IDs from the exodus file. 

700 

701 Returns 

702 ------- 

703 elem_num_map : np.array 

704 Returns a 1D array with size num_elems, denoting the element number 

705 for the element in each index 

706 

707 Notes 

708 ----- 

709 If there is no elem_num_map in the exodus file, this function simply 

710 returns an array from 1 to self.num_elems 

711 ''' 

712 if 'elem_num_map' in self._ncdf_handle.variables: 

713 return self._ncdf_handle.variables['elem_num_map'][:] 

714 else: 

715 return np.ma.MaskedArray(np.arange(self.num_elems) + 1) 

716 

717 def put_elem_num_map(self, elem_num_map): 

718 '''Puts a list of local element IDs into the exodus file. 

719 

720 Parameters 

721 ---------- 

722 elem_num_map : np.array 

723 A 1D array with size num_elems, denoting the element number 

724 for the element in each index 

725 

726 ''' 

727 if 'elem_num_map' in self._ncdf_handle.variables: 

728 raise ExodusError('elem_num_map has already been put to the exodus file.') 

729 # Create the variable 

730 self._ncdf_handle.createVariable('elem_num_map', 'int32', ('num_elem',)) 

731 self._ncdf_handle.variables['elem_num_map'][:] = elem_num_map 

732 

733 @property 

734 def num_blks(self): 

735 return self._ncdf_handle.dimensions['num_el_blk'].size 

736 

737 def get_elem_blk_ids(self): 

738 ''' Gets a list of the element block ID numbers in the exodus file 

739 

740 Returns 

741 ------- 

742 block_ids : tuple 

743 A tuple containing the block ID numbers 

744 ''' 

745 try: 

746 return tuple(self._ncdf_handle.variables['eb_prop1'][:]) 

747 except KeyError: 

748 raise ExodusError('Element Block IDs are not defined!') 

749 

750 def put_elem_blk_ids(self, block_ids): 

751 '''Puts a list of the element block ID numbers in the exodus file 

752 

753 Parameters 

754 ---------- 

755 block_ids : array-like 

756 A sequency of integers specifying the block id numbers 

757 ''' 

758 if 'eb_prop1' in self._ncdf_handle.variables: 

759 raise ExodusError('Element Block IDs have already been put to the exodus file.') 

760 block_ids = np.array(block_ids).flatten() 

761 if len(block_ids) != self._ncdf_handle.dimensions['num_el_blk'].size: 

762 raise ExodusError('Invalid Number of Block IDs Specified') 

763 self._ncdf_handle.createVariable('eb_prop1', 'int32', ('num_el_blk',)) 

764 self._ncdf_handle.variables['eb_prop1'][:] = block_ids 

765 self._ncdf_handle.variables['eb_prop1'].setncattr('name', 'ID') 

766 # Create block names too 

767 block_names = ['block_{:d}'.format(id) for id in block_ids] 

768 num_blk, len_name = (self._ncdf_handle.dimensions['num_el_blk'].size, 

769 self._ncdf_handle.dimensions['len_name'].size) 

770 array = np.zeros((num_blk, len_name), dtype='|S1') 

771 for i, line in enumerate(block_names): 

772 if len(line) > len_name: 

773 print('Warning:When setting block names, entry "{}" (record {}) will be truncated'.format(line, i)) 

774 line = line[:len_name] 

775 listed_string = [val for i, val in enumerate(line)] 

776 # Create the coordinate variables 

777 array[i, :len(listed_string)] = listed_string 

778 # Create the variable 

779 self._ncdf_handle.createVariable('eb_names', 'S1', ('num_el_blk', 'len_name')) 

780 # Assign to it 

781 self._ncdf_handle.variables['eb_names'][:] = array[:] 

782 # Create eb_status as well 

783 self._ncdf_handle.createVariable('eb_status', 'int32', ('num_el_blk',)) 

784 self._ncdf_handle.variables['eb_status'][:] = 0 

785 

786 def get_elem_blk_info(self, id): 

787 ''' Gets the element block information for the specified element block ID 

788 

789 Parameters 

790 ---------- 

791 id : int 

792 Element Block ID number 

793 

794 Returns 

795 ------- 

796 element_type : str 

797 Name of the Element Type 

798 elements_in_block : int 

799 Number of elements in the block 

800 nodes_per_element : int 

801 Number of nodes per element in the block 

802 attributes_per_element : int 

803 Number of attributes per element 

804 ''' 

805 block_ids = self.get_elem_blk_ids() 

806 try: 

807 block_index = block_ids.index(id) + 1 

808 except ValueError: 

809 raise ExodusError('Invalid Block ID') 

810 try: 

811 num_el_in_blk = self._ncdf_handle.dimensions['num_el_in_blk{:d}'.format( 

812 block_index)].size 

813 num_nod_per_el = self._ncdf_handle.dimensions['num_nod_per_el{:d}'.format( 

814 block_index)].size 

815 element_type = self._ncdf_handle.variables['connect{:d}'.format( 

816 block_index)].getncattr('elem_type') 

817 if 'num_att_in_blk{:d}'.format(block_index) in self._ncdf_handle.dimensions: 

818 attr_per_element = self._ncdf_handle.dimensions['num_att_in_blk{:d}'.format( 

819 block_index)].size 

820 else: 

821 attr_per_element = 0 

822 except KeyError: 

823 raise ExodusError('Block {:d} not initialized correctly') 

824 return element_type, num_el_in_blk, num_nod_per_el, attr_per_element 

825 

826 def put_elem_blk_info(self, id, elem_type, num_elements, num_nodes_per_element, num_attrs_per_elem=0): 

827 '''Puts the element block information for an element block ID into the exodus file 

828 

829 Parameters 

830 ---------- 

831 id : int 

832 The block ID (not index) that the information is being specified for 

833 elem_type : str 

834 The element type ('SHELL4','HEX8', etc.) in the block 

835 num_elements : int 

836 The number of elements in the block 

837 num_nodes_per_element : int 

838 The number of nodes per element in the block 

839 num_attrs_per_element : int 

840 The number of attributes per element in the block 

841 ''' 

842 block_ids = self.get_elem_blk_ids() 

843 try: 

844 block_index = block_ids.index(id) + 1 

845 except ValueError: 

846 raise ExodusError('Invalid Block ID') 

847 num_el_in_blk_name = 'num_el_in_blk{:d}'.format(block_index) 

848 num_nod_per_el_name = 'num_nod_per_el{:d}'.format(block_index) 

849 if num_el_in_blk_name in self._ncdf_handle.dimensions: 

850 raise ExodusError( 

851 'Information for block {:d} has already been put to the exodus file.'.format(id)) 

852 connect_name = 'connect{:d}'.format(block_index) 

853 # Create the dimensions 

854 self._ncdf_handle.createDimension(num_el_in_blk_name, num_elements) 

855 self._ncdf_handle.createDimension(num_nod_per_el_name, num_nodes_per_element) 

856 # Create the connectivity matrix 

857 self._ncdf_handle.createVariable( 

858 connect_name, 'int32', (num_el_in_blk_name, num_nod_per_el_name)) 

859 self._ncdf_handle.variables[connect_name].setncattr('elem_type', elem_type) 

860 # Create attributes if necessary 

861 if num_attrs_per_elem > 0: 

862 print('Warning:Element attribute functionality has not been thoroughly checked out, use with caution.') 

863 num_att_in_blk_name = 'num_att_in_blk{:d}'.format(block_index) 

864 attrib_name = 'attrib{:d}'.format(block_index) 

865 self._ncdf_handle.createDimension(num_att_in_blk_name, num_attrs_per_elem) 

866 self._ncdf_handle.createVariable( 

867 attrib_name, self.floating_point_type, (num_el_in_blk_name, num_att_in_blk_name)) 

868 # Set the eb_status to 1 when it is defined (I'm not sure this is 

869 # actually what it should be, just every exodus file I've looked at has 

870 # the eb_status as all ones...) 

871 self._ncdf_handle.variables['eb_status'][block_index - 1] = 1 

872 

873 def get_elem_connectivity(self, id): 

874 '''Gets the element connectivity matrix for a given block 

875 

876 Parameters 

877 ---------- 

878 id : int 

879 The block id for which the element connectivity matrix is desired. 

880 

881 Returns 

882 ------- 

883 connectivity : np.masked_array 

884 The 2d connectivity matrix for the block with dimensions num_elem x 

885 num_nodes_per_element. The returned value is converted from the 

886 1-based Exodus indexing to 0-based Python/NumPy indexing. 

887 ''' 

888 block_ids = self.get_elem_blk_ids() 

889 try: 

890 block_index = block_ids.index(id) + 1 

891 except ValueError: 

892 raise ExodusError('Invalid Block ID') 

893 conn_name = 'connect{:d}'.format(block_index) 

894 if not self.num_elems == 0: 

895 try: 

896 return self._ncdf_handle.variables[conn_name][:] - 1 

897 except KeyError: 

898 raise ExodusError('Element Block {:d} has not been defined yet') 

899 

900 def set_elem_connectivity(self, id, connectivity): 

901 '''Sets the element connectivity matrix for a given block 

902 

903 Parameters 

904 ---------- 

905 id : int 

906 The block id for which the element connectivity is being assigned 

907 connectivity : 2D array-like 

908 A 2D array of dimension num_elements x num_nodes_per_element 

909 defining the element connectivity. Note that the connectivity 

910 matrix should be 0-based (first node index is zero) per Python 

911 conventions. It will be converted to one-based when it is written 

912 to the Exodus file. 

913 

914 ''' 

915 block_ids = self.get_elem_blk_ids() 

916 try: 

917 block_index = block_ids.index(id) + 1 

918 except ValueError: 

919 raise ExodusError('Invalid Block ID') 

920 conn_name = 'connect{:d}'.format(block_index) 

921 if conn_name not in self._ncdf_handle.variables: 

922 raise ExodusError('Element Block {:d} has not been defined yet') 

923 connectivity = np.array(connectivity) 

924 if self._ncdf_handle.variables[conn_name][:].shape != connectivity.shape: 

925 raise ExodusError('Shape of connectivity matrix should be {} (recieved {})'.format(self._ncdf_handle.variables[conn_name][:].shape, 

926 connectivity.shape)) 

927 self._ncdf_handle.variables[conn_name][:] = connectivity + 1 

928 

929 def num_attr(self, id): 

930 '''Gets the number of attributes per element for an element block 

931 

932 Parameters 

933 ---------- 

934 d : int 

935 The block id for which the number of attributes is desired 

936 

937 Returns 

938 ------- 

939 attr_per_element : int 

940 The number of attributes per element in a block 

941 ''' 

942 block_ids = self.get_elem_blk_ids() 

943 try: 

944 block_index = block_ids.index(id) + 1 

945 except ValueError: 

946 raise ExodusError('Invalid Block ID') 

947 if 'num_att_in_blk{:d}'.format(block_index) in self._ncdf_handle.dimensions: 

948 attr_per_element = self._ncdf_handle.dimensions['num_att_in_blk{:d}'.format( 

949 block_index)].size 

950 else: 

951 attr_per_element = 0 

952 return attr_per_element 

953 

954 def num_elems_in_blk(self, id): 

955 '''Gets the number of elements in an element block 

956 

957 Parameters 

958 ---------- 

959 d : int 

960 The block id for which the number of attributes is desired 

961 

962 Returns 

963 ------- 

964 elem_per_element : int 

965 The number of elements in the block 

966 ''' 

967 block_ids = self.get_elem_blk_ids() 

968 try: 

969 block_index = block_ids.index(id) + 1 

970 except ValueError: 

971 raise ExodusError('Invalid Block ID') 

972 try: 

973 return self._ncdf_handle.dimensions['num_el_in_blk{:d}'.format(block_index)].size 

974 except KeyError: 

975 raise ExodusError('Block {:d} not initialized correctly'.format(id)) 

976 

977 def num_nodes_per_elem(self, id): 

978 '''Gets the number of nodes per element in an element block 

979 

980 Parameters 

981 ---------- 

982 d : int 

983 The block id for which the number of attributes is desired 

984 

985 Returns 

986 ------- 

987 nodes_per_elem : int 

988 The number of nodes per element in the block 

989 ''' 

990 block_ids = self.get_elem_blk_ids() 

991 try: 

992 block_index = block_ids.index(id) + 1 

993 except ValueError: 

994 raise ExodusError('Invalid Block ID') 

995 try: 

996 return self._ncdf_handle.dimensions['num_nod_per_el{:d}'.format(block_index)].size 

997 except KeyError: 

998 raise ExodusError('Block {:d} not initialized correctly'.format(id)) 

999 

1000 def get_elem_attr(self, id): 

1001 '''Gets the element attributes for a given block 

1002 

1003 Parameters 

1004 ---------- 

1005 id : int 

1006 The block id for which the element connectivity matrix is desired. 

1007 

1008 Returns 

1009 ------- 

1010 attributes : np.masked_array 

1011 The 2d attribute matrix for the block with dimensions num_elem x 

1012 num_attributes 

1013 ''' 

1014 block_ids = self.get_elem_blk_ids() 

1015 try: 

1016 block_index = block_ids.index(id) + 1 

1017 except ValueError: 

1018 raise ExodusError('Invalid Block ID') 

1019 try: 

1020 return self._ncdf_handle.variables['attrib{:d}'.format(block_index)][:] 

1021 except KeyError: 

1022 raise ExodusError('No attributes defined for Block {:d}'.format(id)) 

1023 

1024 def set_elem_attr(self, id, attributes): 

1025 '''Sets the element attributes for a given block 

1026 

1027 Parameters 

1028 ---------- 

1029 id : int 

1030 The block id for which the element connectivity matrix is desired. 

1031 attributes : 2D array-like 

1032 The 2d attribute matrix for the block with dimensions num_elem x 

1033 num_attributes 

1034 ''' 

1035 block_ids = self.get_elem_blk_ids() 

1036 try: 

1037 block_index = block_ids.index(id) + 1 

1038 except ValueError: 

1039 raise ExodusError('Invalid Block ID') 

1040 attr_name = 'attrib{:d}'.format(block_index) 

1041 if attr_name not in self._ncdf_handle.variables: 

1042 raise ExodusError('Element Block {:d} has no defined attributes') 

1043 attributes = np.array(attributes) 

1044 if self._ncdf_handle.variables[attr_name][:].shape != attributes.shape: 

1045 raise ExodusError('Shape of attributes matrix should be {} (recieved {})'.format(self._ncdf_handle.variables[attr_name][:].shape, 

1046 attributes.shape)) 

1047 self._ncdf_handle.variables[attr_name][:] = attributes 

1048 

1049 def get_elem_type(self, id): 

1050 '''Gets the element type for a given block 

1051 

1052 Parameters 

1053 ---------- 

1054 id : int 

1055 The block id for which the element connectivity matrix is desired. 

1056 

1057 Returns 

1058 ------- 

1059 type : str 

1060 The element type of the block 

1061 ''' 

1062 block_ids = self.get_elem_blk_ids() 

1063 try: 

1064 block_index = block_ids.index(id) + 1 

1065 except ValueError: 

1066 raise ExodusError('Invalid Block ID') 

1067 try: 

1068 return self._ncdf_handle.variables['connect{:d}'.format(block_index)].getncattr('elem_type') 

1069 except KeyError: 

1070 raise ExodusError('Element Block {:d} has not been defined yet') 

1071 

1072 def get_elem_variable_names(self): 

1073 '''Gets a tuple of element variable names from the exodus file 

1074 

1075 Returns 

1076 ------- 

1077 elem_var_names : tuple of strings 

1078 Returns a tuple containing the names of the element variables in the 

1079 exodus file. 

1080 ''' 

1081 try: 

1082 raw_records = self._ncdf_handle.variables['name_elem_var'] 

1083 except KeyError: 

1084 raise ExodusError('Element Variable Names are not defined!') 

1085 elem_var_names = tuple(''.join(value.decode() for value in line if not isinstance( 

1086 value, np.ma.core.MaskedConstant)) for line in raw_records) 

1087 return elem_var_names 

1088 

1089 def put_elem_variable_names(self, elem_var_names, elem_var_table=None): 

1090 '''Puts the specified variable names in the exodus file 

1091 

1092 Parameters 

1093 ---------- 

1094 elem_var_names : tuple of strings 

1095 A tuple containing the names of the element variables in the exodus 

1096 file 

1097 elem_var_table : 2d array-like 

1098 A 2d array of shape num_el_blk x num_elem_var defining which 

1099 element variables are defined for which element blocks 

1100 elem_var_table[i,j] should be True if the j-th element variable is 

1101 defined for the i-th block (index, not id number) in the model. 

1102 If not specified, it is assumed that all variables are defined for 

1103 all blocks. 

1104 ''' 

1105 if 'name_elem_var' in self._ncdf_handle.variables: 

1106 raise ExodusError('Element Variable Names have already been put to the exodus file') 

1107 # Make sure that it is the correct shape 

1108 num_names = len(elem_var_names) 

1109 if elem_var_table is None: 

1110 elem_var_table = np.ones((self.num_blks, num_names), dtype=bool) 

1111 elem_var_table = np.array(elem_var_table) 

1112 if elem_var_table.shape != (self.num_blks, num_names): 

1113 raise ExodusError('Shape of elem_var_table matrix should be {} (recieved {})'.format((self.num_blks, num_names), 

1114 elem_var_table.shape)) 

1115 string_length = self._ncdf_handle.dimensions['len_name'].size 

1116 array = np.zeros((num_names, string_length), dtype='|S1') 

1117 for i, line in enumerate(elem_var_names): 

1118 if len(line) > string_length: 

1119 print('Warning:When setting nodal variable names, entry "{}" (record {}) will be truncated'.format( 

1120 line, i)) 

1121 line = line[:string_length] 

1122 listed_string = [val for i, val in enumerate(line)] 

1123 # Create the coordinate variables 

1124 array[i, :len(listed_string)] = listed_string 

1125 # Create the element variables if they exist 

1126 for j, blkid in enumerate(self.get_elem_blk_ids()): 

1127 if elem_var_table[j, i]: 

1128 self._ncdf_handle.createVariable('vals_elem_var{:d}eb{:d}'.format( 

1129 i + 1, j + 1), self.floating_point_type, ('time_step', 'num_el_in_blk{:d}'.format(j + 1))) 

1130 # Create the dimension 

1131 self._ncdf_handle.createDimension('num_elem_var', num_names) 

1132 # Create the variable 

1133 self._ncdf_handle.createVariable('name_elem_var', 'S1', ('num_elem_var', 'len_name')) 

1134 self._ncdf_handle.createVariable('elem_var_tab', 'int32', ('num_el_blk', 'num_elem_var')) 

1135 # Assign to it 

1136 self._ncdf_handle.variables['name_elem_var'][:] = array[:] 

1137 self._ncdf_handle.variables['elem_var_tab'][:] = elem_var_table 

1138 

1139 def get_elem_variable_table(self): 

1140 '''Gets the element variable table 

1141 

1142 Gets the element variable table showing which elements are defined for 

1143 which blocks. 

1144 

1145 Returns 

1146 ------- 

1147 elem_var_table : np.masked_array 

1148 A 2D array with dimension num_blocks x num_element_variables. If 

1149 the jth element variable is defined for the ith block, 

1150 elem_var_table[i,j] == True 

1151 ''' 

1152 try: 

1153 return self._ncdf_handle.variables['elem_var_tab'][:] 

1154 except KeyError: 

1155 raise ExodusError('Element Variable Table has not been defined in the exodus file') 

1156 

1157 @property 

1158 def num_elem_variables(self): 

1159 try: 

1160 return self._ncdf_handle.dimensions['num_elem_var'].size 

1161 except KeyError: 

1162 raise ExodusError('Number of element variables is not defined') 

1163 

1164 def get_elem_variable_values(self, block_id, name_or_index, step=None): 

1165 '''Gets a block's element variable values for the specified timestep 

1166 

1167 Parameters 

1168 ---------- 

1169 block_id : int 

1170 Block id number for the block from which element variable values 

1171 are desired. 

1172 name_or_index : str or int 

1173 Name or Index of the element variable that is desired. If 

1174 type(name_or_index) == str, then it is assumed to be the name. If 

1175 type(name_or_index) == int, then it is assumed to be the index. 

1176 step : int 

1177 Time step at which to recover the element variable 

1178 

1179 Returns 

1180 ------- 

1181 elem_variable_values : maskedarray 

1182 A 1d array consisting of the variable values for each element in 

1183 the specified block at the specified time step. 

1184 ''' 

1185 block_ids = self.get_elem_blk_ids() 

1186 try: 

1187 block_index = block_ids.index(block_id) 

1188 except ValueError: 

1189 raise ExodusError('Invalid Block ID') 

1190 var_names = self.get_elem_variable_names() 

1191 if isinstance(name_or_index, (int, np.integer)): 

1192 var_index = name_or_index 

1193 var_name = var_names[var_index] 

1194 elif isinstance(name_or_index, (str, np.character)): 

1195 try: 

1196 var_name = name_or_index 

1197 var_index = var_names.index(var_name) 

1198 except ValueError: 

1199 raise ExodusError( 

1200 'Name {} not found in self.get_elem_variable_names(). Options are {}'.format(var_name, var_names)) 

1201 if not self.get_elem_variable_table()[block_index, var_index]: 

1202 raise ExodusError('Variable {} is not defined for block {}'.format(var_name, block_id)) 

1203 variable_value_name = 'vals_elem_var{:d}eb{:d}'.format(var_index + 1, block_index + 1) 

1204 if step is not None: 

1205 if step >= self.num_times: 

1206 raise ExodusError('Invalid Timestep') 

1207 return self._ncdf_handle.variables[variable_value_name][step, :] 

1208 else: 

1209 return self._ncdf_handle.variables[variable_value_name][:, :] 

1210 

1211 def get_elem_variable_value(self, block_id, name_or_index, element_index, step=None): 

1212 '''Gets an element's variable value for the specified timestep 

1213 

1214 Parameters 

1215 ---------- 

1216 block_id : int 

1217 Block id number for the block from which element variable value 

1218 is desired. 

1219 name_or_index : str or int 

1220 Name or Index of the element variable that is desired. If 

1221 type(name_or_index) == str, then it is assumed to be the name. If 

1222 type(name_or_index) == int, then it is assumed to be the index. 

1223 element_index : int 

1224 element index at which to recover the nodal variable 

1225 step : int 

1226 Time step at which to recover the nodal variable 

1227 Returns 

1228 ------- 

1229 elem_variable_value : float 

1230 The variable values for the specified element at the specified time 

1231 step. 

1232 ''' 

1233 block_ids = self.get_elem_blk_ids() 

1234 try: 

1235 block_index = block_ids.index(block_id) 

1236 except ValueError: 

1237 raise ExodusError('Invalid Block ID') 

1238 var_names = self.get_elem_variable_names() 

1239 if isinstance(name_or_index, (int, np.integer)): 

1240 var_index = name_or_index 

1241 var_name = var_names[var_index] 

1242 elif isinstance(name_or_index, (str, np.character)): 

1243 try: 

1244 var_name = name_or_index 

1245 var_index = var_names.index(var_name) 

1246 except ValueError: 

1247 raise ExodusError( 

1248 'Name {} not found in self.get_elem_variable_names(). Options are {}'.format(var_name, var_names)) 

1249 if not self.get_elem_variable_table()[block_index, var_index]: 

1250 raise ExodusError('Variable {} is not defined for block {}'.format(var_name, block_id)) 

1251 variable_value_name = 'vals_elem_var{:d}eb{:d}'.format(var_index + 1, block_index + 1) 

1252 if step is not None: 

1253 if step >= self.num_times: 

1254 raise ExodusError('Invalid Timestep') 

1255 return self._ncdf_handle.variables[variable_value_name][step, element_index] 

1256 else: 

1257 return self._ncdf_handle.variables[variable_value_name][:, element_index] 

1258 

1259 def set_elem_variable_values(self, block_id, name_or_index, step, values): 

1260 '''Sets a block's element variable values for the specified timestep 

1261 

1262 Parameters 

1263 ---------- 

1264 block_id : int 

1265 Block id number for the block from which element variable values 

1266 are desired. 

1267 name_or_index : str or int 

1268 Name or Index of the element variable that is desired. If 

1269 type(name_or_index) == str, then it is assumed to be the name. If 

1270 type(name_or_index) == int, then it is assumed to be the index. 

1271 step : int 

1272 Time step at which to recover the element variable 

1273 values : array-like 

1274 A 1d array consisting of the variable values for each element in 

1275 the specified block at the specified time step. 

1276 

1277 Notes 

1278 ----- 

1279 If step is not a valid index for the time vector, the time vector will 

1280 be expanded so that it is. 

1281 ''' 

1282 block_ids = self.get_elem_blk_ids() 

1283 try: 

1284 block_index = block_ids.index(block_id) 

1285 except ValueError: 

1286 raise ExodusError('Invalid Block ID') 

1287 var_names = self.get_elem_variable_names() 

1288 if isinstance(name_or_index, (int, np.integer)): 

1289 var_index = name_or_index 

1290 var_name = var_names[var_index] 

1291 elif isinstance(name_or_index, (str, np.character)): 

1292 try: 

1293 var_name = name_or_index 

1294 var_index = var_names.index(var_name) 

1295 except ValueError: 

1296 raise ExodusError( 

1297 'Name {} not found in self.get_elem_variable_names(). Options are {}'.format(var_name, var_names)) 

1298 if not self.get_elem_variable_table()[block_index, var_index]: 

1299 raise ExodusError('Variable {} is not defined for block {}'.format(var_name, block_id)) 

1300 variable_value_name = 'vals_elem_var{:d}eb{:d}'.format(var_index + 1, block_index + 1) 

1301# values = np.array(values).flatten() 

1302# if values.shape != (self.num_elems_in_blk(block_id),): 

1303# raise ExodusError('values should have {:d} elements'.format(self.num_elems_in_blk(block_id))) 

1304 self._ncdf_handle.variables[variable_value_name][step, :] = values 

1305 

1306 def set_elem_variable_value(self, block_id, name_or_index, element_index, 

1307 step, value): 

1308 '''Sets an element variable value for the specified timestep 

1309 

1310 Parameters 

1311 ---------- 

1312 block_id : int 

1313 Block id number for the block from which element variable values 

1314 are desired. 

1315 name_or_index : str or int 

1316 Name or Index of the element variable that is desired. If 

1317 type(name_or_index) == str, then it is assumed to be the name. If 

1318 type(name_or_index) == int, then it is assumed to be the index. 

1319 step : int 

1320 Time step at which to recover the element variable 

1321 value : float 

1322 The variable values for the specified element in the specified 

1323 block at the specified time step. 

1324 

1325 Notes 

1326 ----- 

1327 If step is not a valid index for the time vector, the time vector will 

1328 be expanded so that it is. 

1329 ''' 

1330 block_ids = self.get_elem_blk_ids() 

1331 try: 

1332 block_index = block_ids.index(block_id) 

1333 except ValueError: 

1334 raise ExodusError('Invalid Block ID') 

1335 var_names = self.get_elem_variable_names() 

1336 if isinstance(name_or_index, (int, np.integer)): 

1337 var_index = name_or_index 

1338 var_name = var_names[var_index] 

1339 elif isinstance(name_or_index, (str, np.character)): 

1340 try: 

1341 var_name = name_or_index 

1342 var_index = var_names.index(var_name) 

1343 except ValueError: 

1344 raise ExodusError( 

1345 'Name {} not found in self.get_elem_variable_names(). Options are {}'.format(var_name, var_names)) 

1346 if not self.get_elem_variable_table()[block_index, var_index]: 

1347 raise ExodusError('Variable {} is not defined for block {}'.format(var_name, block_id)) 

1348 variable_value_name = 'vals_elem_var{:d}eb{:d}'.format(var_index + 1, block_index + 1) 

1349 self._ncdf_handle.variables[variable_value_name][step, element_index] = value 

1350 

1351 def get_element_property_names(self): 

1352 raise NotImplementedError 

1353 

1354 def get_element_property_value(self): 

1355 raise NotImplementedError 

1356 

1357 def put_element_property_names(self): 

1358 raise NotImplementedError 

1359 

1360 def put_element_property_value(self): 

1361 raise NotImplementedError 

1362 r''' 

1363 _ _ _ _ 

1364 | \ | | ___ __| | ___ ___ ___| |_ ___ 

1365 | \| |/ _ \ / _` |/ _ \/ __|/ _ \ __/ __| 

1366 | |\ | (_) | (_| | __/\__ \ __/ |_\__ \ 

1367 |_| \_|\___/ \__,_|\___||___/\___|\__|___/ 

1368 ''' 

1369 

1370 @property 

1371 def num_node_sets(self): 

1372 return self._ncdf_handle.dimensions['num_node_sets'].size 

1373 

1374 def get_node_set_names(self): 

1375 '''Retrieve the node set names in the exodus file. 

1376 

1377 Returns 

1378 ------- 

1379 ns_names : tuple 

1380 Returns a tuple of strings containing the node set names in the 

1381 exodus file. 

1382 ''' 

1383 try: 

1384 raw_records = self._ncdf_handle.variables['ns_names'] 

1385 except KeyError: 

1386 raise ExodusError('Node set names are not yet defined!') 

1387 ns_names = tuple(''.join(value.decode() for value in line if not isinstance( 

1388 value, np.ma.core.MaskedConstant)) for line in raw_records) 

1389 return ns_names 

1390 

1391 def put_node_set_names(self, ns_names): 

1392 '''Puts the node set names into the exodus file 

1393 

1394 Parameters 

1395 ---------- 

1396 ns_names : sequence of strings 

1397 A sequence (list/tuple/etc.) containing the node set names. 

1398 ''' 

1399 if 'ns_names' in self._ncdf_handle.variables: 

1400 raise ExodusError('Node set names have already been put to the exodus file') 

1401 # Make sure that it is the correct shape 

1402 num_ns = self.num_node_sets # self._ncdf_handle.dimensions['num_dim'].size 

1403 if num_ns != len(ns_names): 

1404 raise ExodusError('Length of ns_names should be same as self.num_node_sets') 

1405 len_name = self._ncdf_handle.dimensions['len_name'].size 

1406 array = np.zeros((num_ns, len_name), dtype='|S1') 

1407 for i, line in enumerate(ns_names): 

1408 if len(line) > len_name: 

1409 print('Warning:When setting info records, entry "{}" (record {}) will be truncated'.format(line, i)) 

1410 line = line[:len_name] 

1411 listed_string = [val for i, val in enumerate(line)] 

1412 # Create the coordinate variables 

1413 array[i, :len(listed_string)] = listed_string 

1414 # Create the variable 

1415 self._ncdf_handle.createVariable('ns_names', 'S1', ('num_node_sets', 'len_name')) 

1416 # Assign to it 

1417 self._ncdf_handle.variables['ns_names'][:] = array[:] 

1418 

1419 def get_node_set_ids(self): 

1420 ''' Gets a list of the node set ID numbers in the exodus file 

1421 

1422 Returns 

1423 ------- 

1424 ns_ids : tuple 

1425 A tuple containing the node set ID numbers 

1426 ''' 

1427 try: 

1428 return tuple(self._ncdf_handle.variables['ns_prop1'][:]) 

1429 except KeyError: 

1430 raise ExodusError('Node set IDs are not defined!') 

1431 

1432 def put_node_set_ids(self, ns_ids): 

1433 '''Puts a list of the node set ID numbers in the exodus file 

1434 

1435 Parameters 

1436 ---------- 

1437 ns_ids : array-like 

1438 A sequency of integers specifying the node set id numbers 

1439 ''' 

1440 if 'ns_prop1' in self._ncdf_handle.variables: 

1441 raise ExodusError('Node set IDs have already been put to the exodus file.') 

1442 ns_ids = np.array(ns_ids).flatten() 

1443 if len(ns_ids) != self._ncdf_handle.dimensions['num_node_sets'].size: 

1444 raise ExodusError('Invalid number of Node set IDs specified') 

1445 self._ncdf_handle.createVariable('ns_prop1', 'int32', ('num_node_sets',)) 

1446 self._ncdf_handle.variables['ns_prop1'][:] = ns_ids 

1447 self._ncdf_handle.variables['ns_prop1'].setncattr('name', 'ID') 

1448 # Create ns_status as well 

1449 self._ncdf_handle.createVariable('ns_status', 'int32', ('num_node_sets',)) 

1450 self._ncdf_handle.variables['ns_status'][:] = 0 

1451 

1452 def get_node_set_num_nodes(self, id): 

1453 '''Get the number of nodes in the specified node set 

1454 

1455 Parameters 

1456 ---------- 

1457 id : int 

1458 Node set ID (not index) 

1459 

1460 Returns 

1461 ------- 

1462 num_nodes : int 

1463 The number of nodes in the node set. 

1464 ''' 

1465 ns_ids = self.get_node_set_ids() 

1466 try: 

1467 ns_index = ns_ids.index(id) + 1 

1468 except ValueError: 

1469 raise ExodusError('Invalid node set ID') 

1470 try: 

1471 return self._ncdf_handle.dimensions['num_nod_ns{:d}'.format(ns_index)].size 

1472 except KeyError: 

1473 raise ExodusError('Node set {:d} not initialized correctly'.format(id)) 

1474 

1475 def get_node_set_dist_factors(self, id): 

1476 '''Get the distribution factors of the specified node set 

1477 

1478 Parameters 

1479 ---------- 

1480 id : int 

1481 Node set ID (not index) 

1482 

1483 Returns 

1484 ------- 

1485 dist_factor : np.array 

1486 The distribution factors in the node set 

1487 ''' 

1488 ns_ids = self.get_node_set_ids() 

1489 try: 

1490 ns_index = ns_ids.index(id) + 1 

1491 except ValueError: 

1492 raise ExodusError('Invalid node set ID') 

1493 try: 

1494 return self._ncdf_handle.variables['dist_fact_ns{:d}'.format(ns_index)][:] 

1495 except KeyError: 

1496 raise ExodusError('No distribution factors defined for node set {:d}'.format(id)) 

1497 

1498 def get_node_set_nodes(self, id): 

1499 '''Get the nodes in the specified node set 

1500 

1501 Parameters 

1502 ---------- 

1503 id : int 

1504 Node set ID (not index) 

1505 

1506 Returns 

1507 ------- 

1508 nodes : np.array 

1509 The node indices of nodes in the node set. Note that while the 

1510 Exodus file format uses 1-based indexing, the returned nodes array 

1511 is converted so it is 0-based. 

1512 ''' 

1513 ns_ids = self.get_node_set_ids() 

1514 try: 

1515 ns_index = ns_ids.index(id) + 1 

1516# print(ns_index) 

1517 except ValueError: 

1518 raise ExodusError('Invalid node set ID') 

1519 try: 

1520 return self._ncdf_handle.variables['node_ns{:d}'.format(ns_index)][:] - 1 

1521 except KeyError: 

1522 raise ExodusError('Node set {:d} not initialized correctly'.format(id)) 

1523 

1524 def put_node_set_info(self, id, nodes, dist_fact=None): 

1525 '''Puts the node set information for a node set ID into the exodus file 

1526 

1527 Parameters 

1528 ---------- 

1529 id : int 

1530 The node set ID (not index) 

1531 nodes : array-like 

1532 A 1d array containing the node indices. Note the array should be 

1533 zero-based, it will be converted to one based when written to the 

1534 exodus file. 

1535 dist_fact : array-like 

1536 A 1d array containing the node set distribution factors. If not 

1537 specified, the distribution factors will be assumed to be 1. 

1538 ''' 

1539 ns_ids = self.get_node_set_ids() 

1540 try: 

1541 ns_index = ns_ids.index(id) + 1 

1542 except ValueError: 

1543 raise ExodusError('Invalid node set ID') 

1544 num_node_in_ns_name = 'num_nod_ns{:d}'.format(ns_index) 

1545 if num_node_in_ns_name in self._ncdf_handle.dimensions: 

1546 raise ExodusError( 

1547 'Information for node set {:d} has already been put to the exodus file.'.format(id)) 

1548 nodes = np.array(nodes).flatten() 

1549 if dist_fact is not None: 

1550 dist_fact = np.array(dist_fact).flatten() 

1551 if nodes.shape != dist_fact.shape: 

1552 raise ExodusError('dist_fact must be the same size as nodes') 

1553 # Create dimension 

1554 self._ncdf_handle.createDimension(num_node_in_ns_name, len(nodes)) 

1555 # Create variables 

1556 node_name = 'node_ns{:d}'.format(ns_index) 

1557 self._ncdf_handle.createVariable(node_name, 'int32', (num_node_in_ns_name,)) 

1558 self._ncdf_handle.variables[node_name][:] = nodes + 1 

1559 if dist_fact is not None: 

1560 distfact_name = 'dist_fact_ns{:d}'.format(ns_index) 

1561 self._ncdf_handle.createVariable( 

1562 distfact_name, self.floating_point_type, (num_node_in_ns_name,)) 

1563 self._ncdf_handle.variables[distfact_name][:] = dist_fact 

1564 # Set the ns_status to 1 when it is defined (I'm not sure this is 

1565 # actually what it should be, just every exodus file I've looked at has 

1566 # the eb_status as all ones...) 

1567 self._ncdf_handle.variables['ns_status'][ns_index - 1] = 1 

1568 r''' 

1569 _____ _ _ _ 

1570 / ____(_) | | | | 

1571 | (___ _ __| | ___ ___ ___| |_ ___ 

1572 \___ \| |/ _` |/ _ \/ __|/ _ \ __/ __| 

1573 ____) | | (_| | __/\__ \ __/ |_\__ \ 

1574 |_____/|_|\__,_|\___||___/\___|\__|___/ 

1575 ''' 

1576 

1577 @property 

1578 def num_side_sets(self): 

1579 return self._ncdf_handle.dimensions['num_side_sets'].size 

1580 

1581 def get_side_set_names(self): 

1582 '''Retrieve the side set names in the exouds file. 

1583 

1584 Returns 

1585 ------- 

1586 ss_names : tuple 

1587 Returns a tuple of strings containing the side set names in the 

1588 exodus file 

1589 ''' 

1590 try: 

1591 raw_records = self._ncdf_handle.variables['ss_names'] 

1592 except KeyError: 

1593 raise ExodusError('Side set names are not yet defined!') 

1594 ss_names = tuple(''.join(value.decode() for value in line if not isinstance( 

1595 value, np.ma.core.MaskedConstant)) for line in raw_records) 

1596 return ss_names 

1597 

1598 def put_side_set_names(self, ss_names): 

1599 '''Puts the side set names into the exodus file 

1600 

1601 Parameters 

1602 ---------- 

1603 ss_names : sequence of strings 

1604 A sequence (list/tuple/etc.) containing the side set names. 

1605 ''' 

1606 if 'xs_names' in self._ncdf_handle.variables: 

1607 raise ExodusError('Side set names have already been put to the exodus file') 

1608 # Make sure that it is the correct shape 

1609 num_ss = self.num_side_sets 

1610 if num_ss != len(ss_names): 

1611 raise ExodusError('Length of ss_names should be same as self.num_side_sets') 

1612 len_name = self._ncdf_handle.dimensions['len_name'].size 

1613 array = np.zeros((num_ss, len_name), dtype='|S1') 

1614 for i, line in enumerate(ss_names): 

1615 if len(line) > len_name: 

1616 print('Warning:When setting info records, entry "{}" (record {}) will be truncated'.format(line, i)) 

1617 line = line[:len_name] 

1618 listed_string = [val for i, val in enumerate(line)] 

1619 # Create the coordinate variables 

1620 array[i, :len(listed_string)] = listed_string 

1621 # Create the variable 

1622 self._ncdf_handle.createVariable('ss_names', 'S1', ('num_side_sets', 'len_name')) 

1623 # Assign to it 

1624 self._ncdf_handle.variables['ss_names'][:] = array[:] 

1625 

1626 def get_side_set_ids(self): 

1627 ''' Gets a list of the side set ID numbers in the exodus file 

1628 

1629 Returns 

1630 ------- 

1631 ss_ids : tuple 

1632 A tuple containing the side set ID numbers 

1633 ''' 

1634 try: 

1635 return tuple(self._ncdf_handle.variables['ss_prop1'][:]) 

1636 except KeyError: 

1637 raise ExodusError('Side set IDs are not defined!') 

1638 

1639 def put_side_set_ids(self, ss_ids): 

1640 '''Puts a list of the side set ID numbers in the exodus file 

1641 

1642 Parameters 

1643 ---------- 

1644 ss_ids : array-like 

1645 A sequency of integers specifying the side set id numbers 

1646 ''' 

1647 if 'ss_prop1' in self._ncdf_handle.variables: 

1648 raise ExodusError('Side set IDs have already been put to the exodus file.') 

1649 ss_ids = np.array(ss_ids).flatten() 

1650 if len(ss_ids) != self._ncdf_handle.dimensions['num_side_sets'].size: 

1651 raise ExodusError('Invalid number of side set IDs specified') 

1652 self._ncdf_handle.createVariable('ss_prop1', 'int32', ('num_side_sets',)) 

1653 self._ncdf_handle.variables['ss_prop1'][:] = ss_ids 

1654 self._ncdf_handle.variables['ss_prop1'].setncattr('name', 'ID') 

1655 # Create ns_status as well 

1656 self._ncdf_handle.createVariable('ss_status', 'int32', ('num_side_sets',)) 

1657 self._ncdf_handle.variables['ss_status'][:] = 0 

1658 

1659 def get_side_set_num_faces(self, id): 

1660 '''Get the number of faces in the specified side set 

1661 

1662 Parameters 

1663 ---------- 

1664 id : int 

1665 Side set ID (not index) 

1666 

1667 Returns 

1668 ------- 

1669 num_faces : int 

1670 The number of faces in the side set. 

1671 ''' 

1672 ss_ids = self.get_side_set_ids() 

1673 try: 

1674 ss_index = ss_ids.index(id) + 1 

1675 except ValueError: 

1676 raise ExodusError('Invalid side set ID') 

1677 try: 

1678 return self._ncdf_handle.dimensions['num_side_ss{:d}'.format(ss_index)].size 

1679 except KeyError: 

1680 raise ExodusError('Side set {:d} not initialized correctly'.format(id)) 

1681 

1682 def get_side_set_dist_factors(self, id): 

1683 '''Get the distribution factors of the specified side set 

1684 

1685 Parameters 

1686 ---------- 

1687 id : int 

1688 Side set ID (not index) 

1689 

1690 Returns 

1691 ------- 

1692 dist_factor : np.array 

1693 The distribution factors in the node set 

1694 ''' 

1695 ss_ids = self.get_side_set_ids() 

1696 try: 

1697 ss_index = ss_ids.index(id) + 1 

1698 except ValueError: 

1699 raise ExodusError('Invalid side set ID') 

1700 try: 

1701 return self._ncdf_handle.variables['dist_fact_ss{:d}'.format(ss_index)][:] 

1702 except KeyError: 

1703 raise ExodusError('No distribution factors defined for side set {:d}'.format(id)) 

1704 

1705 def get_side_set_faces(self, id): 

1706 '''Get the faces in the specified side set 

1707 

1708 Parameters 

1709 ---------- 

1710 id : int 

1711 Side set ID (not index) 

1712 

1713 Returns 

1714 ------- 

1715 elements : np.array 

1716 The element of each face in the sideset (converted from 1-based 

1717 exodus indexing to 0-based python indexing) 

1718 sides : np.array 

1719 The element side of each face in the sideset (converted from 

1720 1-based exodus indexing to 0-based python indexing) 

1721 ''' 

1722 ss_ids = self.get_side_set_ids() 

1723 try: 

1724 ss_index = ss_ids.index(id) + 1 

1725# print(ss_index) 

1726 except ValueError: 

1727 raise ExodusError('Invalid side set ID') 

1728 try: 

1729 return (self._ncdf_handle.variables['elem_ss{:d}'.format(ss_index)][:] - 1, 

1730 self._ncdf_handle.variables['side_ss{:d}'.format(ss_index)][:] - 1) 

1731 except KeyError: 

1732 raise ExodusError('Side set {:d} not initialized correctly'.format(id)) 

1733 

1734 def put_side_set_info(self, id, elements, sides, dist_fact=None): 

1735 '''Puts the side set information for a side set ID into the exodus file 

1736 

1737 Parameters 

1738 ---------- 

1739 id : int 

1740 The side set ID (not index) 

1741 elements : np.array 

1742 The element of each face in the sideset (converted from 1-based 

1743 exodus indexing to 0-based python indexing) 

1744 sides : np.array 

1745 The element side of each face in the sideset (converted from 

1746 1-based exodus indexing to 0-based python indexing) 

1747 dist_fact : array-like 

1748 A 1d array containing the node set distribution factors. If not 

1749 specified, the distribution factors will be assumed to be 1. 

1750 ''' 

1751 ss_ids = self.get_side_set_ids() 

1752 try: 

1753 ss_index = ss_ids.index(id) + 1 

1754 except ValueError: 

1755 raise ExodusError('Invalid side set ID') 

1756 num_side_in_ss_name = 'num_side_ss{:d}'.format(ss_index) 

1757 if num_side_in_ss_name in self._ncdf_handle.dimensions: 

1758 raise ExodusError( 

1759 'Information for side set {:d} has already been put to the exodus file.'.format(id)) 

1760 elements = np.array(elements).flatten() 

1761 sides = np.array(sides).flatten() 

1762 if dist_fact is not None: 

1763 print('Warning:Distribution factors for sidesets have not been thoroughly checked! Use with Caution!') 

1764 dist_fact = np.array(dist_fact).flatten() 

1765 # TODO: Check if the size of dist_fact is correct 

1766# if nodes.shape != dist_fact.shape: 

1767# raise ExodusError('dist_fact must be the same size as nodes') 

1768 # Create dimensions 

1769 self._ncdf_handle.createDimension(num_side_in_ss_name, len(elements)) 

1770 # Create variables 

1771 elem_name = 'elem_ss{:d}'.format(ss_index) 

1772 side_name = 'side_ss{:d}'.format(ss_index) 

1773 self._ncdf_handle.createVariable(elem_name, 'int32', (num_side_in_ss_name,)) 

1774 self._ncdf_handle.createVariable(side_name, 'int32', (num_side_in_ss_name,)) 

1775 self._ncdf_handle.variables[elem_name][:] = elements + 1 

1776 self._ncdf_handle.variables[side_name][:] = sides + 1 

1777 if dist_fact is not None: 

1778 distfact_name = 'dist_fact_ss{:d}'.format(ss_index) 

1779 distfact_dim_name = 'num_df_ss{:d}'.format(ss_index) 

1780 self._ncdf_handle.createDimension(distfact_dim_name, len(dist_fact)) 

1781 self._ncdf_handle.createVariable( 

1782 distfact_name, self.floating_point_type, (distfact_dim_name,)) 

1783 self._ncdf_handle.variables[distfact_name][:] = dist_fact 

1784 # Set the ns_status to 1 when it is defined (I'm not sure this is 

1785 # actually what it should be, just every exodus file I've looked at has 

1786 # the eb_status as all ones...) 

1787 self._ncdf_handle.variables['ss_status'][ss_index - 1] = 1 

1788 

1789 def get_side_set_node_list(self, id): 

1790 raise NotImplementedError 

1791 

1792 r''' 

1793 _____ _ _ _ __ __ _ _ _ 

1794 / ____| | | | | | \ \ / / (_) | | | | 

1795 | | __| | ___ | |__ __ _| | \ \ / /_ _ _ __ _ __ _| |__ | | ___ ___ 

1796 | | |_ | |/ _ \| '_ \ / _` | | \ \/ / _` | '__| |/ _` | '_ \| |/ _ \/ __| 

1797 | |__| | | (_) | |_) | (_| | | \ / (_| | | | | (_| | |_) | | __/\__ \ 

1798 \_____|_|\___/|_.__/ \__,_|_| \/ \__,_|_| |_|\__,_|_.__/|_|\___||___/ 

1799 ''' 

1800 

1801 @property 

1802 def num_global_variables(self): 

1803 try: 

1804 return self._ncdf_handle.dimensions['num_glo_var'].size 

1805 except KeyError: 

1806 raise ExodusError('Number of global variables is not defined') 

1807 

1808 def put_global_variable_names(self, global_var_names): 

1809 '''Puts the specified global variable names in the exodus file 

1810 

1811 Parameters 

1812 ---------- 

1813 global_var_names : tuple of strings 

1814 A tuple containing the names of the global variables in the model 

1815 ''' 

1816 if 'name_glo_var' in self._ncdf_handle.variables: 

1817 raise ExodusError('Global Variable Names have already been put to the exodus file') 

1818 # Make sure that it is the correct shape 

1819 num_names = len(global_var_names) 

1820 string_length = self._ncdf_handle.dimensions['len_name'].size 

1821 array = np.zeros((num_names, string_length), dtype='|S1') 

1822 for i, line in enumerate(global_var_names): 

1823 if len(line) > string_length: 

1824 print('Warning:When setting global variable names, entry "{}" (record {}) will be truncated'.format( 

1825 line, i)) 

1826 line = line[:string_length] 

1827 listed_string = [val for i, val in enumerate(line)] 

1828 # Create the coordinate variables 

1829 array[i, :len(listed_string)] = listed_string 

1830 # Create the dimension 

1831 self._ncdf_handle.createDimension('num_glo_var', num_names) 

1832 # Create the variable 

1833 self._ncdf_handle.createVariable('name_glo_var', 'S1', ('num_glo_var', 'len_name')) 

1834 self._ncdf_handle.createVariable( 

1835 'vals_glo_var', self.floating_point_type, ('time_step', 'num_glo_var')) 

1836 # Assign to it 

1837 self._ncdf_handle.variables['name_glo_var'][:] = array[:] 

1838 

1839 def get_global_variable_names(self): 

1840 '''Gets a tuple of global variable names from the exodus file 

1841 

1842 Returns 

1843 ------- 

1844 global_var_names : tuple of strings 

1845 Returns a tuple containing the names of the global variables in the 

1846 exodus file. 

1847 ''' 

1848 try: 

1849 raw_records = self._ncdf_handle.variables['name_glo_var'] 

1850 except KeyError: 

1851 raise ExodusError('Global Variable Names are not defined!') 

1852 global_var_names = tuple(''.join(value.decode() for value in line if not isinstance( 

1853 value, np.ma.core.MaskedConstant)) for line in raw_records) 

1854 return global_var_names 

1855 

1856 def get_global_variable_values(self, name_or_index, step=None): 

1857 '''Gets the global variable value for the specified timesteps 

1858 

1859 Parameters 

1860 ---------- 

1861 name_or_index : str or int 

1862 Name or Index of the global variable that is desired. If 

1863 type(name_or_index) == str, then it is assumed to be the name. If 

1864 type(name_or_index) == int, then it is assumed to be the index. 

1865 step : int 

1866 Time step at which to recover the nodal variable 

1867 

1868 Returns 

1869 ------- 

1870 global_variable_values : maskedarray or float 

1871 A 1d array or floating point number consisting of the variable 

1872 values at the specified time steps. 

1873 ''' 

1874 if isinstance(name_or_index, (int, np.integer)): 

1875 index = name_or_index 

1876 elif isinstance(name_or_index, (str, np.character)): 

1877 try: 

1878 index = self.get_global_variable_names().index(name_or_index) 

1879 except ValueError: 

1880 raise ExodusError('Name {} not found in self.get_global_variable_names(). Options are {}'.format( 

1881 name_or_index, self.get_node_variable_names())) 

1882 vals_glo_var_name = 'vals_glo_var' 

1883 if step is not None: 

1884 if step >= self.num_times: 

1885 raise ExodusError('Invalid Time Step') 

1886 return self._ncdf_handle.variables[vals_glo_var_name][step, index] 

1887 else: 

1888 return self._ncdf_handle.variables[vals_glo_var_name][:, index] 

1889 

1890 def set_global_variable_values(self, name_or_index, step, value): 

1891 '''Sets the global variable values for the specified timestep 

1892 

1893 Parameters 

1894 ---------- 

1895 name_or_index : str or int 

1896 Name or Index of the nodal variable that is desired. If 

1897 type(name_or_index) == str, then it is assumed to be the name. If 

1898 type(name_or_index) == int, then it is assumed to be the index. 

1899 step : int 

1900 Time step at which to recover the nodal variable 

1901 value : array-like 

1902 A 1d array consisting of the variable values for each node at the 

1903 specified time step. 

1904 

1905 Notes 

1906 ----- 

1907 If step is not a valid index for the time vector, the time vector will 

1908 be expanded so that it is. 

1909 ''' 

1910 if isinstance(name_or_index, (int, np.integer)): 

1911 index = name_or_index 

1912 elif isinstance(name_or_index, (str, np.character)): 

1913 try: 

1914 index = self.get_global_variable_names().index(name_or_index) 

1915 except ValueError: 

1916 raise ExodusError('Name {} not found in self.get_global_variable_names(). Options are {}'.format( 

1917 name_or_index, self.get_node_variable_names())) 

1918 vals_glo_var_name = 'vals_glo_var' 

1919 self._ncdf_handle.variables[vals_glo_var_name][step, index] = value 

1920 

1921 # TODO: Still need to do Nodeset and Sideset Variables; 

1922 # Coordinate Frames (see ExodusIO.m), Element Block Attribute Names 

1923 

1924 def close(self): 

1925 self._ncdf_handle.close() 

1926 

1927 def load_into_memory(self, close=True, variables=None, timesteps=None, blocks=None): 

1928 '''Loads the exodus file into an ExodusInMemory object 

1929 

1930 This function loads the exodus file into memory in an ExodusInMemory 

1931 format. Not for use with large files. 

1932 

1933 Parameters 

1934 ---------- 

1935 close : bool 

1936 Close the netcdf file upon loading into memory. Optional argument, 

1937 default is true. 

1938 variables : iterable 

1939 A list of variable names that are loaded into memory. Default is 

1940 to load all variables 

1941 timesteps : iterable 

1942 A list of timestep indices that are loaded into memory. Default is 

1943 to load all timesteps. 

1944 blocks : iterable 

1945 A list of block ids that are loaded into memory. Default is to 

1946 load all blocks. 

1947 

1948 Returns 

1949 ------- 

1950 fexo : ExodusInMemory 

1951 The exodus file in an ExodusInMemory format 

1952 

1953 ''' 

1954 fexo = ExodusInMemory(self, variables=variables, timesteps=timesteps, blocks=blocks) 

1955 if close: 

1956 self.close() 

1957 return fexo 

1958 

1959 def get_block_surface(self, block_id, keep_midside_nodes=False, warn=True): 

1960 '''Gets the node indices and element connectivity of surface elements 

1961 

1962 This function "skins" the element block, returning a list of node 

1963 indices and a surface connectivity matrix. 

1964 

1965 Parameters 

1966 ---------- 

1967 block_id : int 

1968 The ID number of the block that will be skinned. 

1969 keep_midside_nodes : bool 

1970 Specifies whether or not to keep midside nodes in the surface mesh. 

1971 Default is False. 

1972 warn : bool 

1973 Specifies whether or not to warn the user if the block ID doesn't 

1974 have a skinning method defined for its element type. Default is 

1975 True. 

1976 

1977 Returns 

1978 ------- 

1979 element_block_information : list 

1980 A list of tuples of element information. These data are 

1981 element_type, node_indices, block_surface_connectivity, and 

1982 block_surface_original_elements. The element_type is a string 

1983 representing the new block element type ('quad4','tri3',etc.). The 

1984 node_indices can be used as an index into the coordinate or nodal 

1985 variable arrays to select nodes corresponding to this block. The 

1986 block_surface_connectivity represents the connectivity array of the 

1987 surface faces of the block. Values in this array correspond to 

1988 indices into the node_indices array. To recover the connectivity 

1989 array in the original node indices of the exodus file, it can be 

1990 passed through the node_indices as 

1991 node_indices[block_surface_connectivity]. The 

1992 block_surface_original_elements array shows the original element 

1993 indices of the block that each surface came from. This can be 

1994 used to map element variables to the new surface mesh. This list 

1995 will normally be length 1 unless an element type is processed that 

1996 has two different surface elements in it (e.g. wedges have 

1997 tris and quads) 

1998 ''' 

1999 elem_type = self.get_elem_type(block_id) 

2000 connectivity = self.get_elem_connectivity(block_id) 

2001 face_connectivity_array = face_connectivity(elem_type, keep_midside_nodes) 

2002 element_block_information = [] 

2003 for reduced_elem_type, reduced_connectivity in face_connectivity_array: 

2004 if reduced_elem_type is None: 

2005 if warn: 

2006 print('Warning: Element Type {:} has no defined face reduction. Passing connectivity unchanged.'.format( 

2007 elem_type)) 

2008 block_face_connectivity = connectivity 

2009 block_elem_type = elem_type 

2010 block_face_original_elements = np.arange(connectivity.shape[0]) 

2011 else: 

2012 block_face_connectivity = connectivity[:, 

2013 reduced_connectivity].reshape(-1, reduced_connectivity.shape[-1]) 

2014 block_face_original_elements = np.repeat(np.arange(connectivity.shape[0]), 

2015 reduced_connectivity.shape[0]) 

2016 block_elem_type = reduced_elem_type 

2017 # Remove faces that are duplicates 

2018 (unique_rows, unique_row_indices, unique_inverse, unique_counts) = np.unique(np.sort( 

2019 block_face_connectivity, axis=1), return_index=True, return_inverse=True, return_counts=True, axis=0) 

2020 original_unique_counts = unique_counts[unique_inverse] 

2021 nondupe_faces = original_unique_counts == 1 

2022 block_face_connectivity = block_face_connectivity[nondupe_faces, :] 

2023 block_face_original_elements = block_face_original_elements[nondupe_faces] 

2024 

2025 node_indices = np.unique(block_face_connectivity) 

2026 node_map = np.zeros(self.num_nodes, dtype=int) 

2027 node_map[node_indices] = np.arange(len(node_indices)) 

2028 block_face_connectivity = node_map[block_face_connectivity] 

2029 element_block_information.append((block_elem_type, 

2030 node_indices, 

2031 block_face_connectivity, 

2032 block_face_original_elements)) 

2033 return element_block_information 

2034 

2035 def triangulate_surface_mesh(self): 

2036 '''Triangulate a surface mesh for plotting patches 

2037 

2038 This function generates a triangle mesh for each block in the model if 

2039 it can. If there are more than 3 nodes per element in a block, and the 

2040 triangulation scheme hasn't been defined in 

2041 pyexodus.mesh_triangulation_array, it will be skipped. 

2042 

2043 Parameters 

2044 ---------- 

2045 None 

2046 

2047 Returns 

2048 ------- 

2049 triangulated_mesh_info : list 

2050 A list of tuples containing block id, node_indices, triangulated 

2051 connectivity, and original block elements 

2052 ''' 

2053 triangulated_mesh_info = [] 

2054 for block_id in self.get_elem_blk_ids(): 

2055 surface_mesh_info = self.get_block_surface(block_id, warn=False) 

2056 for elem_type, node_indices, connectivity, original_elements in surface_mesh_info: 

2057 triangulation_scheme = mesh_triangulation_array(elem_type) 

2058 if triangulation_scheme is None: 

2059 if connectivity.shape[-1] > 3: 

2060 print('Warning: More than 3 ({:}) nodes per element in block {:}, this block will not be triangulated'.format( 

2061 connectivity.shape[-1], block_id)) 

2062 else: 

2063 triangulated_mesh_info.append( 

2064 (block_id, node_indices, connectivity, original_elements)) 

2065 else: 

2066 triangulated_mesh_info.append( 

2067 (block_id, 

2068 node_indices, 

2069 connectivity[:, 

2070 triangulation_scheme].reshape(-1, triangulation_scheme.shape[-1]), 

2071 np.repeat(original_elements, triangulation_scheme.shape[0]))) 

2072 return triangulated_mesh_info 

2073 

2074 def reduce_to_surfaces(self, *args, **kwargs): 

2075 return reduce_exodus_to_surfaces(self, *args, **kwargs) 

2076 

2077 def extract_sharp_edges(self, *args, **kwargs): 

2078 return extract_sharp_edges(self, *args, **kwargs) 

2079 

2080 def copy_structure(self, filename, variables = None, clobber = False, close = False): 

2081 """ 

2082 Copies the structure of an exodus file into a new file. 

2083 

2084 Copies the number of nodes/elements, element blocks, variable names, etc. 

2085 However it does not copy the timesteps or any variable values. 

2086 

2087 Parameters 

2088 ---------- 

2089 filename : str 

2090 The name of the new file into which the data will be copied. 

2091 variables : list of str 

2092 A list of variable names to copy over. If not specified, all 

2093 variables will be copied. 

2094 clobber : bool 

2095 If True, if the file already exists, it will be overwritten.  

2096 If False (default), an error will occur if the file already 

2097 exists. 

2098 close : bool 

2099 If True, the new file will be closed after it is created. 

2100 If False (default), the new file will remain open for further 

2101 modifications. 

2102 """ 

2103 # Create the empty exodus file 

2104 exo = Exodus(filename, mode='w', clobber=clobber, 

2105 title = self.title, 

2106 num_dims = self.num_dimensions, 

2107 num_nodes = self.num_nodes, 

2108 num_elem = self.num_elems, 

2109 num_blocks = self.num_blks, 

2110 num_node_sets = self.num_node_sets, 

2111 num_side_sets = self.num_side_sets) 

2112 

2113 # Add QA Record for this operation 

2114 qa_records = self.get_qa_records() 

2115 qa_records = qa_records + (('sdynpy Exodus.repack', version('sdynpy'),str(datetime.datetime.now().date()),str(datetime.datetime.now().time())),) 

2116 exo.put_qa_records(qa_records) 

2117 

2118 # Copy over the info records 

2119 exo.put_info_records(self.get_info_records()) 

2120 

2121 # Set up the nodes 

2122 exo.put_coord_names(self.get_coord_names()) 

2123 exo.put_coords(self.get_coords()) 

2124 if 'node_num_map' in self._ncdf_handle.variables: 

2125 exo.put_node_num_map(self.get_node_num_map()) 

2126 

2127 # Set up nodal variables 

2128 if variables is None: 

2129 names = self.get_node_variable_names() 

2130 else: 

2131 names = [name for name in self.get_node_variable_names() if name in variables] 

2132 if len(names) > 0: 

2133 exo.put_node_variable_names(names) 

2134 

2135 # Set up elements 

2136 if 'elem_num_map' in self._ncdf_handle.variables: 

2137 exo.put_elem_num_map(self.get_elem_num_map()) 

2138 

2139 exo.put_elem_blk_ids(self.get_elem_blk_ids()) 

2140 

2141 for block in self.get_elem_blk_ids(): 

2142 elem_type, elems_in_block, nodes_per_element, attributes_per_element = self.get_elem_blk_info(block) 

2143 exo.put_elem_blk_info(block, elem_type, elems_in_block, nodes_per_element, attributes_per_element) 

2144 exo.set_elem_connectivity(block,self.get_elem_connectivity(block)) 

2145 if attributes_per_element > 0: 

2146 exo.set_elem_attr(block, self.get_elem_attr(block)) 

2147 

2148 if variables is None: 

2149 elem_variable_names = self.get_elem_variable_names() 

2150 elem_variable_table = self.get_elem_variable_table() 

2151 else: 

2152 elem_variable_names = self.get_elem_variable_names() 

2153 elem_variable_table = self.get_elem_variable_table() 

2154 is_kept = [name in variables for name in elem_variable_names] 

2155 elem_variable_table = elem_variable_table[:,is_kept] 

2156 elem_variable_names = [name for name in elem_variable_names if name in variables] 

2157 if len(elem_variable_names) > 0: 

2158 exo.put_elem_variable_names(elem_variable_names,elem_variable_table) 

2159 

2160 # Set up nodesets 

2161 if self.num_node_sets > 0: 

2162 exo.put_node_set_names(self.get_node_set_names()) 

2163 exo.put_node_set_ids(self.get_node_set_ids()) 

2164 

2165 for node_set in self.get_node_set_ids(): 

2166 nodes = self.get_node_set_nodes(node_set) 

2167 try: 

2168 dist_factor = self.get_node_set_dist_factors(node_set) 

2169 except sdpy.fem.sdynpy_exodus.ExodusError: 

2170 dist_factor = None 

2171 exo.put_node_set_info(node_set, nodes, dist_factor) 

2172 

2173 # Set up sidesets 

2174 if self.num_side_sets > 0: 

2175 exo.put_side_set_names(self.get_side_set_names()) 

2176 exo.put_side_set_ids(self.get_side_set_ids()) 

2177 

2178 for side_set in self.get_side_set_ids(): 

2179 elements,sides = self.get_side_set_faces(side_set) 

2180 try: 

2181 dist_factor = self.get_side_set_dist_factors(side_set) 

2182 except sdpy.fem.sdynpy_exodus.ExodusError: 

2183 dist_factor = None 

2184 exo.put_side_set_info(side_set, elements, sides, dist_factor) 

2185 

2186 # Set up global variables 

2187 if variables is None: 

2188 names = self.get_global_variable_names() 

2189 else: 

2190 names = [name for name in self.get_global_variable_names() if name in variables] 

2191 if len(names) > 0: 

2192 exo.put_global_variable_names(names) 

2193 

2194 if close: 

2195 exo.close() 

2196 

2197 return exo 

2198 

2199 def repack(self, filename, repack_data, variables_to_repack = None, indices_in_repack = None, clobber = False, verbose = False, close = False): 

2200 if verbose: 

2201 print(f'Copying {self.filename} structure to {filename}') 

2202 exo = self.copy_structure(filename, variables_to_repack, clobber) 

2203 

2204 if isinstance(repack_data, TimeHistoryArray): 

2205 if not repack_data.validate_common_abscissa(): 

2206 print('Warning: repack_data does not have common abscissa. The first abscissa entry will be used.') 

2207 times = repack_data.ravel()[0].abscissa 

2208 repack_data = repack_data.ordinate 

2209 elif isinstance(repack_data, ShapeArray): 

2210 times = repack_data.frequency 

2211 repack_data = repack_data.shape_matrix.T 

2212 else: 

2213 repack_data = np.asarray(repack_data) 

2214 times = np.arange(repack_data.shape[-1]) 

2215 

2216 # Set the times 

2217 exo.set_times(times) 

2218 num_times = len(times) 

2219 

2220 # Go through node variables 

2221 if variables_to_repack is None: 

2222 variable_names = self.get_node_variable_names() 

2223 else: 

2224 variable_names = [name for name in self.get_node_variable_names() if name in variables_to_repack] 

2225 for variable_name in variable_names: 

2226 if verbose: 

2227 print(f'Repacking Nodal Variable {variable_name}') 

2228 data_matrix = self.get_node_variable_values(variable_name).T 

2229 if indices_in_repack is not None: 

2230 data_matrix = data_matrix[...,indices_in_repack] 

2231 for step in range(num_times): 

2232 q = repack_data[:,step] 

2233 exo.set_node_variable_values(variable_name, step, data_matrix@q) 

2234 

2235 # Go through element variables 

2236 if variables_to_repack is None: 

2237 variable_names = self.get_elem_variable_names() 

2238 else: 

2239 variable_names = [name for name in self.get_elem_variable_names() if name in variables_to_repack] 

2240 for variable_name in variable_names: 

2241 for block_id in self.get_elem_blk_ids(): 

2242 if verbose: 

2243 print(f'Repacking Element Variable {variable_name} for block {block_id}') 

2244 data_matrix = self.get_elem_variable_values(block_id, variable_name).T 

2245 if indices_in_repack is not None: 

2246 data_matrix = data_matrix[...,indices_in_repack] 

2247 for step in range(num_times): 

2248 q = repack_data[:,step] 

2249 exo.set_elem_variable_values(block_id, variable_name, step, data_matrix@q) 

2250 

2251 # Go through global variables 

2252 if variables_to_repack is None: 

2253 variable_names = self.get_global_variable_names() 

2254 else: 

2255 variable_names = [name for name in self.get_global_variable_names() if name in variables_to_repack] 

2256 for variable_name in variable_names: 

2257 if verbose: 

2258 print(f'Repacking Global Variable {variable_name}') 

2259 data_matrix = self.get_global_variable_values(variable_name) 

2260 if indices_in_repack is not None: 

2261 data_matrix = data_matrix[...,indices_in_repack] 

2262 for step in range(num_times): 

2263 q = repack_data[:,step] 

2264 exo.set_global_variable_values(variable_name, step, data_matrix@q) 

2265 

2266 if close: 

2267 exo.close() 

2268 

2269 return exo 

2270 

2271 def __repr__(self): 

2272 return_string = 'Exodus File at {:}'.format(self.filename) 

2273 try: 

2274 return_string += '\n {:} Timesteps'.format(self.num_times) 

2275 except Exception: 

2276 pass 

2277 try: 

2278 return_string += '\n {:} Nodes'.format(self.num_nodes) 

2279 except Exception: 

2280 pass 

2281 try: 

2282 return_string += '\n {:} Elements'.format(self.num_elems) 

2283 except Exception: 

2284 pass 

2285 try: 

2286 return_string += '\n Blocks: {:}'.format(', '.join(str(v) 

2287 for v in self.get_elem_blk_ids())) 

2288 except Exception: 

2289 pass 

2290 try: 

2291 return_string += '\n Node Variables: {:}'.format( 

2292 ', '.join(self.get_node_variable_names())) 

2293 except Exception: 

2294 pass 

2295 try: 

2296 return_string += '\n Element Variables: {:}'.format( 

2297 ', '.join(self.get_elem_variable_names())) 

2298 except Exception: 

2299 pass 

2300 try: 

2301 return_string += '\n Global Variables: {:}'.format( 

2302 ', '.join(self.get_global_variable_names())) 

2303 except Exception: 

2304 pass 

2305 return return_string 

2306 

2307 # def plot_mesh(self,surface_kwargs={'color':(0,0,1)}, 

2308 # bar_kwargs = {'color':(0,1,0),'tube_radius':None}, 

2309 # point_kwargs = {'color':(1,0,0),'scale_factor':0.1}, 

2310 # plot_surfaces = True, plot_bars = True, plot_points = True, 

2311 # plot_edges = False): 

2312 # '''Skins, triangulates, and plots a 3D representation of the mesh. 

2313 

2314 # Parameters 

2315 # ---------- 

2316 # mesh_kwargs : dict 

2317 # A dictionary of keyword arguments for the rendering of surface 

2318 # patches. 

2319 # bar_kwargs : dict 

2320 # A dictionary of keyword arguments for the rendering of lines (bar, 

2321 # beam elements) 

2322 # point_kwargs : dict 

2323 # A dictionary of keyword arguments for the rendering of points 

2324 # (sphere elements) 

2325 # show : bool 

2326 # A flag to specify whether or not to show the window. 

2327 

2328 # Returns 

2329 # ------- 

2330 # window : GLViewWidget 

2331 # A reference to the view that the 3D content is plotted in. 

2332 # meshes : list 

2333 # A list of the mesh objects that have been added to the view. 

2334 

2335 # ''' 

2336 # if mlab is None: 

2337 # raise ModuleNotFoundError('Mayavi not installed!') 

2338 # triangulated_mesh_info = self.triangulate_surface_mesh() 

2339 # meshes = [] 

2340 # for block,node_indices,faces,original_elements in triangulated_mesh_info: 

2341 # if faces.shape[1] == 3: 

2342 # if plot_surfaces: 

2343 # meshes.append(mlab.triangular_mesh( 

2344 # *(self.get_coords()[:,node_indices]),faces, 

2345 # **surface_kwargs)) 

2346 # if plot_edges: 

2347 # meshes.append(mlab.triangular_mesh( 

2348 # *(self.get_coords()[:,node_indices]),faces, 

2349 # representation='wireframe',color=(0,0,0))) 

2350 # elif faces.shape[1] == 2: 

2351 # if plot_bars: 

2352 # line_items = [] 

2353 # xs,ys,zs = self.get_coords()[:,node_indices][:,faces] 

2354 # for x,y,z in zip(xs,ys,zs): 

2355 # line_items.append(mlab.plot3d(x,y,z,**bar_kwargs)) 

2356 # meshes.append(line_items) 

2357 # elif faces.shape[1] == 1: 

2358 # if plot_points: 

2359 # meshes.append(mlab.points3d(*(self.get_coords()[:,node_indices]), 

2360 # **point_kwargs)) 

2361 # return meshes 

2362 

2363 

2364class subfield(SimpleNamespace): 

2365 def __init__(self, *args, **kwargs): 

2366 super().__init__(*args, **kwargs) 

2367 

2368 def __repr__(self): 

2369 return_string = 'Field with subfields: {:}'.format(', '.join(['{:}'.format(key) if type( 

2370 val) is np.ndarray else '{:}={:}'.format(key, val) for key, val in self.__dict__.items()])) 

2371 return return_string 

2372 

2373 def __str__(self): 

2374 return repr(self) 

2375 

2376 

2377class ExodusInMemory: 

2378 '''Read or write exodus files loaded into memory 

2379 

2380 This is a convenience class wrapped around the exodus class to enable 

2381 easier manipulation of exodus files that fit entirely into memory 

2382 

2383 Parameters 

2384 ---------- 

2385 exo : exodus 

2386 An exodus object, if you want to load an exodus file into memory, or 

2387 None if you want to create an empty exodus file. 

2388 variables : iterable 

2389 A list of variable names that are loaded into memory. Default is 

2390 to load all variables 

2391 timesteps : iterable 

2392 A list of timestep indices that are loaded into memory. Default is 

2393 to load all timesteps. 

2394 blocks : iterable 

2395 A list of block ids that are loaded into memory. Default is to load 

2396 all blocks. 

2397 ''' 

2398 

2399 def __init__(self, exo=None, variables=None, timesteps=None, blocks=None): 

2400 if exo is not None: 

2401 self.load_from_exodus(exo, variables=variables, timesteps=timesteps, blocks=blocks) 

2402 else: 

2403 # Title 

2404 self.title = '' 

2405 # Attributes 

2406 attribute_dict = {} 

2407 attribute_dict['api_version'] = 5.22 

2408 attribute_dict['version'] = 5.22 

2409 attribute_dict['floating_point_word_size'] = 8 

2410 attribute_dict['file_size'] = 1 

2411 attribute_dict['int64_status'] = 0 

2412 self.attributes = subfield(**attribute_dict) 

2413 # QA Records 

2414 self.qa_records = [] 

2415 # Info records 

2416 self.info_records = [""] 

2417 # Nodes 

2418 node_dict = {} 

2419 node_dict['coordinates'] = np.empty(0, dtype=float) 

2420 node_dict['node_num_map'] = None 

2421 node_dict['names'] = ('x', 'y', 'z') 

2422 self.nodes = subfield(**node_dict) 

2423 # TODO Coordinate Frames 

2424 # Blocks 

2425 self.blocks = [] 

2426 # Element Map 

2427 self.element_map = None 

2428 # Nodesets 

2429 self.nodesets = None 

2430 # Sidesets 

2431 self.sidesets = None 

2432 # Global Variables 

2433 self.global_vars = None 

2434 # Element Variables 

2435 self.elem_vars = None 

2436 # Nodal Variables 

2437 self.nodal_vars = None 

2438 # Time Steps 

2439 self.time = [] 

2440 

2441 def load_from_exodus(self, exo, variables=None, timesteps=None, blocks=None): 

2442 # Title 

2443 self.title = exo.title 

2444 # Attributes 

2445 attribute_dict = {} 

2446 attribute_dict['api_version'] = exo._ncdf_handle.api_version 

2447 attribute_dict['version'] = exo._ncdf_handle.version 

2448 attribute_dict['floating_point_word_size'] = exo._ncdf_handle.floating_point_word_size 

2449 attribute_dict['file_size'] = exo._ncdf_handle.file_size 

2450 try: 

2451 attribute_dict['int64_status'] = exo._ncdf_handle.int64_status 

2452 except AttributeError: 

2453 attribute_dict['int64_status'] = 0 

2454 self.attributes = subfield(**attribute_dict) 

2455 

2456 # QA Records 

2457 try: 

2458 self.qa_records = exo.get_qa_records() 

2459 except ExodusError: 

2460 self.qa_records = [] 

2461 # Info Records 

2462 try: 

2463 self.info_records = exo.get_info_records() 

2464 except ExodusError: 

2465 self.info_records = [""] 

2466 # Get Nodes 

2467 node_dict = {} 

2468 node_dict['coordinates'] = exo.get_coords().T 

2469 node_dict['node_num_map'] = exo.get_node_num_map().data 

2470 node_dict['names'] = exo.get_coord_names() 

2471 self.nodes = subfield(**node_dict) 

2472 # TODO Read Coordinate Frames 

2473 # Blocks 

2474 if blocks is None: 

2475 block_ids = exo.get_elem_blk_ids() 

2476 self.element_map = None 

2477 else: 

2478 block_ids = [block_id for block_id in exo.get_elem_blk_ids() if block_id in blocks] 

2479 if timesteps is None: 

2480 time_indices = np.arange(exo.get_times().size) 

2481 else: 

2482 time_indices = list(timesteps) 

2483 self.blocks = [] 

2484 for index, blk_id in enumerate(block_ids): 

2485 blk_dict = {} 

2486 blk_dict['id'] = blk_id 

2487 if not exo.num_elems == 0: 

2488 blk_dict['connectivity'] = exo.get_elem_connectivity(blk_id).data 

2489 blk_dict['elem_type'] = exo.get_elem_type(blk_id) 

2490 blk_dict['name'] = ''.join(s.decode() for s in exo._ncdf_handle.variables['eb_names'] 

2491 [index, :] if not isinstance(s, np.ma.core.MaskedConstant)) 

2492 blk_dict['status'] = exo._ncdf_handle.variables['eb_status'][:].data[index] 

2493 try: 

2494 blk_dict['attributes'] = exo.get_elem_attr(blk_id) 

2495 except ExodusError: 

2496 blk_dict['attributes'] = None 

2497 # TODO: Add attribute names once implemented 

2498 try: 

2499 blk_dict['attributes_name'] = None # exo.get_elem_attr_names(blk_id) 

2500 except ExodusError: 

2501 blk_dict['attributes_name'] = None 

2502 self.blocks.append(subfield(**blk_dict)) 

2503 # Element Map 

2504 self.element_map = exo.get_elem_num_map().data 

2505 

2506 # Get Nodesets 

2507 try: 

2508 nodeset_ids = exo.get_node_set_ids() 

2509 self.nodesets = [] 

2510 for index, ns_id in enumerate(nodeset_ids): 

2511 ns_dict = {} 

2512 ns_dict['id'] = ns_id 

2513 ns_dict['nodes'] = exo.get_node_set_nodes(ns_id) 

2514 try: 

2515 ns_dict['dist_factors'] = exo.get_node_set_dist_factors(ns_id) 

2516 except ExodusError: 

2517 ns_dict['dist_factors'] = None 

2518 try: 

2519 ns_dict['name'] = exo.get_node_set_names()[index] 

2520 except ExodusError: 

2521 ns_dict['name'] = None 

2522 ns_dict['status'] = exo._ncdf_handle.variables['ns_status'][:].data[index] 

2523 self.nodesets.append(subfield(**ns_dict)) 

2524 except ExodusError: 

2525 self.nodesets = None 

2526 

2527 # Get Sidesets 

2528 try: 

2529 sideset_ids = exo.get_side_set_ids() 

2530 self.sidesets = [] 

2531 for index, ss_id in enumerate(sideset_ids): 

2532 ss_dict = {} 

2533 ss_dict['id'] = ss_id 

2534 ss_dict['elements'], ss_dict['sides'] = exo.get_side_set_faces(ss_id) 

2535 try: 

2536 ss_dict['dist_factors'] = exo.get_side_set_dist_factors(ss_id) 

2537 except ExodusError: 

2538 ss_dict['dist_factors'] = None 

2539 ss_dict['status'] = exo._ncdf_handle.variables['ss_status'][:].data[index] 

2540 try: 

2541 ss_dict['name'] = exo.get_side_set_names()[index] 

2542 except ExodusError: 

2543 ss_dict['name'] = None 

2544 self.sidesets.append(subfield(**ss_dict)) 

2545 except ExodusError: 

2546 self.sidesets = None 

2547 

2548 # Get Global Variables 

2549 try: 

2550 num_glob_vars = exo.num_global_variables 

2551 if num_glob_vars == 0: 

2552 self.global_vars = None 

2553 else: 

2554 self.global_vars = [] 

2555 global_var_names = exo.get_global_variable_names() 

2556 if variables is not None: 

2557 global_var_names = [name for name in global_var_names if name in variables] 

2558 if len(global_var_names) == 0: 

2559 raise ExodusError('No Global Variables to Load') 

2560 for index, name in enumerate(global_var_names): 

2561 global_var_dict = {} 

2562 global_var_dict['name'] = name 

2563 global_var_dict['data'] = exo.get_global_variable_values(name)[time_indices] 

2564 self.global_vars.append(subfield(**global_var_dict)) 

2565 except ExodusError: 

2566 self.global_vars = None 

2567 

2568 # Get Nodal Variables 

2569 try: 

2570 num_node_vars = exo.num_node_variables 

2571 if num_node_vars == 0: 

2572 self.nodal_vars = None 

2573 else: 

2574 self.nodal_vars = [] 

2575 node_var_names = exo.get_node_variable_names() 

2576 if variables is not None: 

2577 node_var_names = [name for name in node_var_names if name in variables] 

2578 if len(node_var_names) == 0: 

2579 raise ExodusError('No Nodal Variables to Load') 

2580 for index, name in enumerate(node_var_names): 

2581 node_var_dict = {} 

2582 node_var_dict['name'] = name 

2583 node_var_dict['data'] = exo.get_node_variable_values(name).data[time_indices, :] 

2584 self.nodal_vars.append(subfield(**node_var_dict)) 

2585 except ExodusError: 

2586 self.nodal_vars = None 

2587 

2588 # Get Element Variables 

2589 try: 

2590 num_elem_vars = exo.num_elem_variables 

2591 if num_elem_vars == 0: 

2592 self.elem_vars = None 

2593 else: 

2594 self.elem_vars = [] 

2595 elem_var_names = exo.get_elem_variable_names() 

2596 variable_truth_table = exo.get_elem_variable_table().data 

2597 for blk_index, blk_id in enumerate(exo.get_elem_blk_ids()): 

2598 if blk_id not in block_ids: 

2599 continue 

2600 block_dict = {} 

2601 block_dict['id'] = blk_id 

2602 block_dict['elem_var_data'] = [] 

2603 for elem_index, elem_name in enumerate(elem_var_names): 

2604 if (variables is not None) and (elem_name not in variables): 

2605 continue 

2606 if variable_truth_table[blk_index, elem_index] == 0: 

2607 block_dict['elem_var_data'].append(None) 

2608 else: 

2609 elem_var_dict = {} 

2610 elem_var_dict['name'] = elem_name 

2611 elem_var_dict['data'] = exo.get_elem_variable_values( 

2612 blk_id, elem_index).data[time_indices, :] 

2613 block_dict['elem_var_data'].append(subfield(**elem_var_dict)) 

2614 if len(block_dict['elem_var_data']) == 0: 

2615 raise ExodusError('No Element Variables to Load') 

2616 self.elem_vars.append(subfield(**block_dict)) 

2617 except ExodusError: 

2618 self.elem_vars = None 

2619 

2620 # Get Time Steps 

2621 self.time = exo.get_times(time_indices).data 

2622 

2623 @staticmethod 

2624 def from_sdynpy(geometry, displacement_data=None): 

2625 fexo = ExodusInMemory() 

2626 

2627 # Put in the node positions 

2628 node_data = np.sort(geometry.node) 

2629 definition_coordinate_systems = geometry.coordinate_system(node_data.def_cs) 

2630 global_positions = global_coord(definition_coordinate_systems, node_data.coordinate) 

2631 fexo.nodes.coordinates = global_positions 

2632 fexo.nodes.node_num_map = node_data.id 

2633 node_map_dict = {node_id: node_index for node_index, node_id in enumerate(node_data.id)} 

2634 node_map_dict[0] = -1 # For tracelines, map 0 (pick up pen) to -1 (something easy to throw out) 

2635 node_index_map = np.vectorize( 

2636 node_map_dict.__getitem__) 

2637 # Now go through and add in the elements 

2638 element_data = geometry.element 

2639 element_types = np.unique(element_data.type) 

2640 elem_colors = [] 

2641 for elem_type in element_types: 

2642 blk_dict = {} 

2643 blk_dict['id'] = elem_type 

2644 blk_dict['name'] = 'block_{:}'.format(elem_type) 

2645 elements_this_type = element_data[element_data.type == elem_type] 

2646 connectivity = np.stack(elements_this_type.connectivity) 

2647 blk_dict['connectivity'] = node_index_map(connectivity) 

2648 blk_dict['elem_type'] = _inverse_exodus_elem_type_map[elem_type] 

2649 blk_dict['status'] = 1 

2650 blk_dict['attributes'] = None 

2651 blk_dict['attributes_name'] = None 

2652 elem_colors.append(elements_this_type.color) 

2653 fexo.blocks.append(subfield(**blk_dict)) 

2654 

2655 # Now do the same for the tracelines 

2656 traceline_data = geometry.traceline 

2657 for i, (key, traceline) in enumerate(traceline_data.ndenumerate()): 

2658 connectivity_1d = node_index_map(np.stack(traceline.connectivity)) 

2659 connectivity = np.concatenate((connectivity_1d[:-1, np.newaxis], 

2660 connectivity_1d[1:, np.newaxis]), axis=-1) 

2661 connectivity = connectivity[~np.any(connectivity == -1, axis=1)] # We mapped 0s to -1, so we want to throw those out 

2662 blk_dict = {} 

2663 blk_dict['id'] = i + 500 

2664 blk_dict['name'] = traceline.description 

2665 blk_dict['connectivity'] = connectivity 

2666 blk_dict['elem_type'] = 'bar' 

2667 blk_dict['status'] = 1 

2668 blk_dict['attributes'] = None 

2669 blk_dict['attributes_name'] = None 

2670 elem_colors.append(np.ones(connectivity.shape[0]) * traceline.color) 

2671 fexo.blocks.append(subfield(**blk_dict)) 

2672 

2673 from ..core.sdynpy_shape import ShapeArray 

2674 from ..core.sdynpy_data import NDDataArray, TimeHistoryArray 

2675 if isinstance(displacement_data, ShapeArray): 

2676 shapes = displacement_data.flatten() 

2677 fexo.time = shapes.frequency 

2678 # Add global variables frequency and damping 

2679 for name in ['frequency', 'damping']: 

2680 global_var_dict = {} 

2681 global_var_dict['name'] = name 

2682 global_var_dict['data'] = shapes[name] 

2683 # Now add displacement data for the shapes 

2684 coordinates = shapes[0].coordinate.copy() 

2685 indices = np.isin(coordinates.node, node_data.id) 

2686 coordinates = coordinates[indices] 

2687 # Keep only displacements 

2688 coordinates = coordinates[(abs(coordinates.direction) <= 3) & 

2689 (abs(coordinates.direction) >= 1)] 

2690 local_deformation_directions = coordinates.local_direction() 

2691 coordinate_nodes = node_data(coordinates.node) 

2692 displacement_coordinate_systems = geometry.coordinate_system(coordinate_nodes.disp_cs) 

2693 shape_displacements = shapes[coordinates] 

2694 coordinates.node = node_index_map(coordinates.node) 

2695 points = global_positions[coordinates.node] 

2696 global_deformation_directions = global_deflection(displacement_coordinate_systems, 

2697 local_deformation_directions, 

2698 points) 

2699 node_displacements = np.zeros((shape_displacements.shape[0],) 

2700 + node_data.shape 

2701 + (3,), dtype=shape_displacements.dtype) 

2702 

2703 for coordinate, direction, shape_coefficients in zip(coordinates, global_deformation_directions, shape_displacements.T): 

2704 node_index = coordinate.node 

2705 displacements = shape_coefficients[:, np.newaxis] * direction 

2706 node_displacements[:, node_index, :] += displacements 

2707 

2708 # Now add the variables 

2709 fexo.nodal_vars = [] 

2710 for i, name in enumerate(('DispX', 'DispY', 'DispZ')): 

2711 node_var_dict = {} 

2712 node_var_dict['name'] = name 

2713 node_var_dict['data'] = node_displacements[..., i] 

2714 fexo.nodal_vars.append(subfield(**node_var_dict)) 

2715 elif isinstance(displacement_data, NDDataArray): 

2716 if isinstance(displacement_data, TimeHistoryArray): 

2717 data = displacement_data.flatten() 

2718 fexo.time = data[0].abscissa 

2719 # Now add displacement data for the shapes 

2720 coordinates = data.response_coordinate 

2721 indices = np.isin(coordinates.node, node_data.id) 

2722 coordinates = coordinates[indices] 

2723 # Keep only displacements 

2724 coordinates = coordinates[(abs(coordinates.direction) <= 3) 

2725 & (abs(coordinates.direction) >= 1)] 

2726 local_deformation_directions = coordinates.local_direction() 

2727 coordinate_nodes = node_data(coordinates.node) 

2728 displacement_coordinate_systems = geometry.coordinate_system( 

2729 coordinate_nodes.disp_cs) 

2730 data_displacements = data[coordinates[:, np.newaxis]].ordinate.T 

2731 coordinates.node = node_index_map(coordinates.node) 

2732 points = global_positions[coordinates.node] 

2733 global_deformation_directions = global_deflection(displacement_coordinate_systems, 

2734 local_deformation_directions, 

2735 points) 

2736 node_displacements = np.zeros((data_displacements.shape[0],) 

2737 + node_data.shape 

2738 + (3,), dtype=data_displacements.dtype) 

2739 for coordinate, direction, shape_coefficients in zip(coordinates, global_deformation_directions, data_displacements.T): 

2740 node_index = coordinate.node 

2741 displacements = shape_coefficients[:, np.newaxis] * direction 

2742 node_displacements[:, node_index, :] += displacements 

2743 

2744 # Now add the variables 

2745 fexo.nodal_vars = [] 

2746 for i, name in enumerate(('DispX', 'DispY', 'DispZ')): 

2747 node_var_dict = {} 

2748 node_var_dict['name'] = name 

2749 node_var_dict['data'] = node_displacements[..., i] 

2750 fexo.nodal_vars.append(subfield(**node_var_dict)) 

2751 else: 

2752 raise NotImplementedError( 

2753 'Saving Functions with type {:} to Exodus Data is Not Implemented Yet'.format(type(displacement_data))) 

2754 else: 

2755 fexo.time = np.array([0]) 

2756 fexo.nodal_vars = [] 

2757 

2758 # Now add color information 

2759 node_var_dict = {} 

2760 node_var_dict['name'] = 'NodeColor' 

2761 node_var_dict['data'] = np.tile(node_data.color[np.newaxis, :], (len(fexo.time), 1)) 

2762 fexo.nodal_vars.append(subfield(**node_var_dict)) 

2763 

2764 fexo.elem_vars = [] 

2765 for block, color in zip(fexo.blocks, elem_colors): 

2766 block_dict = {} 

2767 block_dict['id'] = block.id 

2768 elem_var_dict = {} 

2769 elem_var_dict['name'] = 'ElemColor' 

2770 elem_var_dict['data'] = color * np.ones((len(fexo.time), 1)) 

2771 

2772 block_dict['elem_var_data'] = [subfield(**elem_var_dict)] 

2773 fexo.elem_vars.append(subfield(**block_dict)) 

2774 

2775 return fexo 

2776 

2777 def write_to_file(self, filename, clobber=False): 

2778 num_nodes, num_dims = self.nodes.coordinates.shape 

2779 num_elem = sum([block.connectivity.shape[0] for block in self.blocks]) 

2780 num_blocks = len(self.blocks) 

2781 if self.nodesets is None: 

2782 num_node_sets = 0 

2783 else: 

2784 num_node_sets = len(self.nodesets) 

2785 if self.sidesets is None: 

2786 num_side_sets = 0 

2787 else: 

2788 num_side_sets = len(self.sidesets) 

2789 exo_out = Exodus(filename, 'w', self.title, 

2790 num_dims, num_nodes, num_elem, num_blocks, num_node_sets, 

2791 num_side_sets, clobber=clobber) 

2792 

2793 # Write time steps 

2794 exo_out.set_times(self.time) 

2795 

2796 # Initialize Information 

2797 exo_out.put_info_records(self.info_records) 

2798 qa_records = list(self.qa_records) 

2799 qa_records.append(('pyexodus', __version__, str( 

2800 datetime.datetime.now().date()), str(datetime.datetime.now().time()))) 

2801 exo_out.put_qa_records(qa_records) 

2802 

2803 # Write the nodes 

2804 exo_out.put_coord_names(self.nodes.names) 

2805 exo_out.put_coords(self.nodes.coordinates.T) 

2806 if self.nodes.node_num_map is not None: 

2807 exo_out.put_node_num_map(self.nodes.node_num_map) 

2808 

2809 # Initialize blocks 

2810 exo_out.put_elem_blk_ids([block.id for block in self.blocks]) 

2811 for block in self.blocks: 

2812 if block.attributes is None: 

2813 nattr_per_elem = 0 

2814 else: 

2815 nattr_per_elem = block.attributes.shape[1] 

2816 exo_out.put_elem_blk_info(block.id, 

2817 block.elem_type, 

2818 block.connectivity.shape[0], 

2819 block.connectivity.shape[1], 

2820 nattr_per_elem) 

2821 exo_out.set_elem_connectivity(block.id, block.connectivity) 

2822 if nattr_per_elem > 0: 

2823 exo_out.set_elem_attr(block.id, block.attributes) 

2824 

2825 # Element Map 

2826 if self.element_map is not None: 

2827 exo_out.put_elem_num_map(self.element_map) 

2828 

2829 # Nodesets 

2830 if self.nodesets is not None: 

2831 exo_out.put_node_set_ids([ns.id for ns in self.nodesets]) 

2832 exo_out.put_node_set_names( 

2833 ['' if nodeset.name is None else nodeset.name for nodeset in self.nodesets]) 

2834 for nodeset in self.nodesets: 

2835 exo_out.put_node_set_info(nodeset.id, nodeset.nodes, nodeset.dist_factors) 

2836 

2837 # Sidesets 

2838 if self.sidesets is not None: 

2839 exo_out.put_side_set_ids([ns.id for ns in self.sidesets]) 

2840 exo_out.put_side_set_names( 

2841 ['' if sideset.name is None else sideset.name for sideset in self.sidesets]) 

2842 for sideset in self.sidesets: 

2843 exo_out.put_side_set_info(sideset.id, sideset.elements, 

2844 sideset.sides, sideset.dist_factors) 

2845 

2846 # Global Variables #TODO: Make it so I can write all timesteps at once. 

2847 if self.global_vars is not None: 

2848 exo_out.put_global_variable_names([var.name for var in self.global_vars]) 

2849 for i, var in enumerate(self.global_vars): 

2850 for j, time in enumerate(self.time): 

2851 exo_out.set_global_variable_values(i, j, self.global_vars[i].data[j]) 

2852 

2853 # Nodal Variables #TODO: Make it so I can write all timesteps at once. 

2854 if self.nodal_vars is not None: 

2855 exo_out.put_node_variable_names([var.name for var in self.nodal_vars]) 

2856 for i, var in enumerate(self.nodal_vars): 

2857 for j, time in enumerate(self.time): 

2858 exo_out.set_node_variable_values(i, j, self.nodal_vars[i].data[j, :]) 

2859 

2860 # Element Variables # TODO: Make it so I can write all timesteps at once 

2861 if self.elem_vars is not None: 

2862 nblocks = len(self.elem_vars) 

2863 nelemvars = len(self.elem_vars[0].elem_var_data) 

2864 elem_table = np.zeros((nblocks, nelemvars), dtype=int) 

2865 for i in range(nblocks): 

2866 for j in range(nelemvars): 

2867 elem_table[i, j] = not self.elem_vars[i].elem_var_data[j] is None 

2868 # Detect if any variables are not used ever 

2869 variable_names = [] 

2870 keep_booleans = [] 

2871 for j in range(nelemvars): 

2872 indices = np.nonzero(elem_table[:, j])[0] 

2873 if len(indices) == 0: 

2874 variable_names.append(None) 

2875 keep_booleans.append(False) 

2876 else: 

2877 variable_names.append(self.elem_vars[indices[0]].elem_var_data[j].name) 

2878 keep_booleans.append(True) 

2879 elem_table = elem_table[:, keep_booleans] 

2880 variable_names = [name for name, boolean in zip( 

2881 variable_names, keep_booleans) if boolean] 

2882 keep_indices = np.nonzero(keep_booleans)[0] 

2883 exo_out.put_elem_variable_names(variable_names, elem_table) 

2884 for i, block in enumerate(self.blocks): 

2885 for j, name in enumerate(variable_names): 

2886 if elem_table[i, j]: 

2887 for k, time in enumerate(self.time): 

2888 exo_out.set_elem_variable_values(block.id, j, k, 

2889 self.elem_vars[i].elem_var_data[keep_indices[j]].data[k, :]) 

2890 

2891 exo_out.close() 

2892 

2893 def repack(self, q, modes=None): 

2894 '''Repackages an exodus file as a linear combination of itself''' 

2895 if modes is None: 

2896 mode_indices = np.arange(len(self.time)) 

2897 else: 

2898 mode_indices = modes 

2899 

2900 if q.shape[0] != len(mode_indices): 

2901 raise ValueError( 

2902 'Number of rows in q must be equivalent to number of time steps in exodus file') 

2903 

2904 out_fexo = copy.deepcopy(self) 

2905 

2906 out_fexo.time = np.zeros((q.shape[1]), dtype='float') 

2907 

2908 if self.global_vars is not None: 

2909 for i, global_var in enumerate(self.global_vars): 

2910 out_fexo.global_vars[i].data = ( 

2911 global_var.data[np.newaxis, mode_indices] @ q).squeeze() 

2912 

2913 if self.nodal_vars is not None: 

2914 for i, nodal_var in enumerate(self.nodal_vars): 

2915 out_fexo.nodal_vars[i].data = (nodal_var.data.T[:, mode_indices] @ q).T 

2916 

2917 if self.elem_vars is not None: 

2918 for i, block in enumerate(self.elem_vars): 

2919 for j, elem_vars in enumerate(block.elem_var_data): 

2920 if elem_vars is not None: 

2921 out_fexo.elem_vars[i].elem_var_data[j].data = ( 

2922 elem_vars.data.T[:, mode_indices] @ q).T 

2923 

2924 return out_fexo 

2925 

2926 def get_block_surface(self, block_id, keep_midside_nodes=False, warn=True): 

2927 '''Gets the node indices and element connectivity of surface elements 

2928 

2929 This function "skins" the element block, returning a list of node 

2930 indices and a surface connectivity matrix. 

2931 

2932 Parameters 

2933 ---------- 

2934 block_id : int 

2935 The ID number of the block that will be skinned. 

2936 keep_midside_nodes : bool 

2937 Specifies whether or not to keep midside nodes in the surface mesh. 

2938 Default is False. 

2939 warn : bool 

2940 Specifies whether or not to warn the user if the block ID doesn't 

2941 have a skinning method defined for its element type. Default is 

2942 True. 

2943 

2944 Returns 

2945 ------- 

2946 element_block_information : list 

2947 A list of tuples of element information. These data are 

2948 element_type, node_indices, block_surface_connectivity, and 

2949 block_surface_original_elements. The element_type is a string 

2950 representing the new block element type ('quad4','tri3',etc.). The 

2951 node_indices can be used as an index into the coordinate or nodal 

2952 variable arrays to select nodes corresponding to this block. The 

2953 block_surface_connectivity represents the connectivity array of the 

2954 surface faces of the block. Values in this array correspond to 

2955 indices into the node_indices array. To recover the connectivity 

2956 array in the original node indices of the exodus file, it can be 

2957 passed through the node_indices as 

2958 node_indices[block_surface_connectivity]. The 

2959 block_surface_original_elements array shows the original element 

2960 indices of the block that each surface came from. This can be 

2961 used to map element variables to the new surface mesh. This list 

2962 will normally be length 1 unless an element type is processed that 

2963 has two different surface elements in it (e.g. wedges have 

2964 tris and quads) 

2965 ''' 

2966 block_index = [block.id for block in self.blocks].index(block_id) 

2967 elem_type = self.blocks[block_index].elem_type 

2968 connectivity = self.blocks[block_index].connectivity 

2969 face_connectivity_array = face_connectivity(elem_type, keep_midside_nodes) 

2970 element_block_information = [] 

2971 for reduced_elem_type, reduced_connectivity in face_connectivity_array: 

2972 if reduced_elem_type is None: 

2973 if warn: 

2974 print('Warning: Element Type {:} has no defined face reduction. Passing connectivity unchanged.'.format( 

2975 elem_type)) 

2976 block_face_connectivity = connectivity 

2977 block_elem_type = elem_type 

2978 block_face_original_elements = np.arange(connectivity.shape[0]) 

2979 else: 

2980 block_face_connectivity = connectivity[:, 

2981 reduced_connectivity].reshape(-1, reduced_connectivity.shape[-1]) 

2982 block_face_original_elements = np.repeat(np.arange(connectivity.shape[0]), 

2983 reduced_connectivity.shape[0]) 

2984 block_elem_type = reduced_elem_type 

2985 # Remove faces that are duplicates 

2986 (unique_rows, unique_row_indices, unique_inverse, unique_counts) = np.unique(np.sort( 

2987 block_face_connectivity, axis=1), return_index=True, return_inverse=True, return_counts=True, axis=0) 

2988 original_unique_counts = unique_counts[unique_inverse] 

2989 nondupe_faces = original_unique_counts == 1 

2990 block_face_connectivity = block_face_connectivity[nondupe_faces, :] 

2991 block_face_original_elements = block_face_original_elements[nondupe_faces] 

2992 

2993 node_indices = np.unique(block_face_connectivity) 

2994 node_map = np.zeros(self.nodes.coordinates.shape[0], dtype=int) 

2995 node_map[node_indices] = np.arange(len(node_indices)) 

2996 block_face_connectivity = node_map[block_face_connectivity] 

2997 element_block_information.append((block_elem_type, 

2998 node_indices, 

2999 block_face_connectivity, 

3000 block_face_original_elements)) 

3001 return element_block_information 

3002 

3003 def triangulate_surface_mesh(self): 

3004 '''Triangulate a surface mesh for plotting patches 

3005 

3006 This function generates a triangle mesh for each block in the model if 

3007 it can. If there are more than 3 nodes per element in a block, and the 

3008 triangulation scheme hasn't been defined in 

3009 pyexodus.mesh_triangulation_array, it will be skipped. 

3010 

3011 Parameters 

3012 ---------- 

3013 None 

3014 

3015 Returns 

3016 ------- 

3017 triangulated_mesh_info : list 

3018 A list of tuples containing block id, node_indices, triangulated 

3019 connectivity, and original block elements 

3020 ''' 

3021 triangulated_mesh_info = [] 

3022 for block in self.blocks: 

3023 surface_mesh_info = self.get_block_surface(block.id, warn=False) 

3024 for elem_type, node_indices, connectivity, original_elements in surface_mesh_info: 

3025 triangulation_scheme = mesh_triangulation_array(elem_type) 

3026 if triangulation_scheme is None: 

3027 if connectivity.shape[-1] > 3: 

3028 print('Warning: More than 3 ({:}) nodes per element in block {:}, this block will not be triangulated'.format( 

3029 connectivity.shape[-1], block.id)) 

3030 else: 

3031 triangulated_mesh_info.append( 

3032 (block.id, node_indices, connectivity, original_elements)) 

3033 else: 

3034 triangulated_mesh_info.append( 

3035 (block.id, 

3036 node_indices, 

3037 connectivity[:, 

3038 triangulation_scheme].reshape(-1, triangulation_scheme.shape[-1]), 

3039 np.repeat(original_elements, triangulation_scheme.shape[0]))) 

3040 return triangulated_mesh_info 

3041 

3042 # def plot_mesh(self,surface_kwargs={'color':(0,0,1)}, 

3043 # bar_kwargs = {'color':(0,1,0),'tube_radius':None}, 

3044 # point_kwargs = {'color':(1,0,0),'scale_factor':0.1}, 

3045 # plot_surfaces = True, plot_bars = True, plot_points = True, 

3046 # plot_edges = False): 

3047 # '''Skins, triangulates, and plots a 3D representation of the mesh. 

3048 

3049 # Parameters 

3050 # ---------- 

3051 # mesh_kwargs : dict 

3052 # A dictionary of keyword arguments for the rendering of surface 

3053 # patches. 

3054 # bar_kwargs : dict 

3055 # A dictionary of keyword arguments for the rendering of lines (bar, 

3056 # beam elements) 

3057 # point_kwargs : dict 

3058 # A dictionary of keyword arguments for the rendering of points 

3059 # (sphere elements) 

3060 # show : bool 

3061 # A flag to specify whether or not to show the window. 

3062 

3063 # Returns 

3064 # ------- 

3065 # window : GLViewWidget 

3066 # A reference to the view that the 3D content is plotted in. 

3067 # meshes : list 

3068 # A list of the mesh objects that have been added to the view. 

3069 

3070 # ''' 

3071 # if mlab is None: 

3072 # raise ModuleNotFoundError('Mayavi not installed!') 

3073 # triangulated_mesh_info = self.triangulate_surface_mesh() 

3074 # meshes = [] 

3075 # for block,node_indices,faces,original_elements in triangulated_mesh_info: 

3076 # if faces.shape[1] == 3: 

3077 # if plot_surfaces: 

3078 # meshes.append(mlab.triangular_mesh( 

3079 # *(self.nodes.coordinates.T[:,node_indices]),faces, 

3080 # **surface_kwargs)) 

3081 # if plot_edges: 

3082 # meshes.append(mlab.triangular_mesh( 

3083 # *(self.nodes.coordinates.T[:,node_indices]),faces, 

3084 # representation='wireframe',color=(0,0,0))) 

3085 # elif faces.shape[1] == 2: 

3086 # if plot_bars: 

3087 # line_items = [] 

3088 # xs,ys,zs = self.nodes.coordinates.T[:,node_indices][:,faces] 

3089 # for x,y,z in zip(xs,ys,zs): 

3090 # line_items.append(mlab.plot3d(x,y,z,**bar_kwargs)) 

3091 # meshes.append(line_items) 

3092 # elif faces.shape[1] == 1: 

3093 # if plot_points: 

3094 # meshes.append(mlab.points3d(*(self.nodes.coordinates.T[:,node_indices]), 

3095 # **point_kwargs)) 

3096 # return meshes 

3097 

3098 def reduce_to_surfaces(self,*args,**kwargs): 

3099 return reduce_exodus_to_surfaces(self,*args,**kwargs) 

3100 

3101 def extract_sharp_edges(self,*args,**kwargs): 

3102 return extract_sharp_edges(self, *args,**kwargs) 

3103 

3104 

3105def reduce_exodus_to_surfaces(fexo, blocks_to_transform=None, variables_to_transform=None, keep_midside_nodes=False, verbose=False): 

3106 '''Convert exodus finite element models to surface elements 

3107 

3108 This function converts specified volume meshes in an exodus file to surface 

3109 meshes to ease computational complexity. 

3110 

3111 Parameters 

3112 ---------- 

3113 fexo : ExodusInMemory object or Exodus object 

3114 fexo should be an in-memory representation of the finite element mesh 

3115 blocks_to_transform : iterable 

3116 blocks_to_transform includes all of the blocks that will be included 

3117 in the output model 

3118 variables_to_transform : iterable 

3119 variables_to_transform is a case-insensitive list of all variable names 

3120 that will be kept in the final model 

3121 keep_midside_nodes : bool 

3122 keep_midside_nodes specifies whether or not to transform quadratic 

3123 elements into linear elements, discarding any non-corner nodes. 

3124 

3125 Returns 

3126 ------- 

3127 fexo_out : ExodusInMemory object 

3128 An equivalent fexo reduced to the surface geometry. 

3129 

3130 Notes 

3131 ----- 

3132 TO ADD MORE ELEMENT TYPES, we need to define a volume element name that 

3133 we will find in the exodus file, for example 'tetra4' or 'hex20', the 

3134 number of nodes per face on the element, and a connectivity matrix that 

3135 specifies how the faces are made from the nodes of the element. For 

3136 example a hex8 element has the following structure:: 

3137 

3138 8--------------7 

3139 /| /| 

3140 / | / | 

3141 / | / | 

3142 5--------------6 | 

3143 | 4----------|---3 

3144 | / | / 

3145 | / | / 

3146 |/ |/ 

3147 1--------------2 

3148 

3149 So the 6 faces are as follows:: 

3150 

3151 1,2,6,5 

3152 2,3,7,6 

3153 3,4,8,7 

3154 4,1,5,8 

3155 4,3,2,1 

3156 5,6,7,8 

3157 

3158 We create the element face connectivity by simply mashing these together 

3159 one after another like so: 1,2,6,5,2,3,7,6,3,4,8,7,4,1,5,8,4,3,2,1,5,6,7,8 

3160 We lastly need to specify what we are turning the element into, in this 

3161 case, quad4. 

3162 So we add an entry to the reduce_element_types, 

3163 reduce_element_nodes_per_face, reduce_element_face, and 

3164 reduce_element_substitute_type variables containing this information 

3165 hex8:: 

3166 

3167 reduce_element_types{4} = 'hex8' 

3168 reduce_element_nodes_per_face{4} = 4 

3169 reduce_element_face{4} = [1,2,6,5,2,3,7,6,3,4,8,7,4,1,5,8,4,3,2,1,5,6,7,8] 

3170 reduce_element_substitute_type{4} = 'quad4' 

3171 

3172 To see what other elements look like, see page 18 of the exodusII manual: 

3173 http://prod.sandia.gov/techlib/access-control.cgi/1992/922137.pdf 

3174 ''' 

3175 

3176 if isinstance(fexo, Exodus): 

3177 exo_in_mem = False 

3178 else: 

3179 exo_in_mem = True 

3180 

3181 # If blocks_to_transform is not specified, keep all blocks 

3182 if blocks_to_transform is None: 

3183 if exo_in_mem: 

3184 blocks_to_transform = [block.id for block in fexo.blocks] 

3185 else: 

3186 blocks_to_transform = fexo.get_elem_blk_ids() 

3187 

3188 # if variables_to_keep is not specified, keep all variables 

3189 if variables_to_transform is None: 

3190 if exo_in_mem: 

3191 try: 

3192 node_variables_to_transform = np.arange(len(fexo.nodal_vars)) 

3193 except TypeError: 

3194 node_variables_to_transform = [] 

3195 try: 

3196 elem_variables_to_transform = np.arange(len(fexo.elem_vars[0].elem_var_data)) 

3197 except TypeError: 

3198 elem_variables_to_transform = [] 

3199 else: 

3200 try: 

3201 node_variables_to_transform = np.arange(fexo.num_node_variables) 

3202 except ExodusError: 

3203 node_variables_to_transform = [] 

3204 try: 

3205 elem_variables_to_transform = np.arange(fexo.num_elem_variables) 

3206 except ExodusError: 

3207 elem_variables_to_transform = [] 

3208 else: 

3209 if exo_in_mem: 

3210 try: 

3211 node_variables_to_transform = np.array([i for i, nodal_variable in enumerate( 

3212 fexo.nodal_vars) if nodal_variable.lower() in [var.lower() for var in variables_to_transform]]) 

3213 except TypeError: 

3214 node_variables_to_transform = [] 

3215 # Need to do extra processing for element variable names because 

3216 # not all variable names are in all blocks (might be none) 

3217 try: 

3218 elem_var_names = [] 

3219 for i in range(len(fexo.elem_vars[0].elem_var_data)): 

3220 var_name = None 

3221 for elem_var in fexo.elem_vars: 

3222 try: 

3223 var_name = elem_var.elem_var_data[i].name 

3224 break 

3225 except AttributeError: 

3226 continue 

3227 elem_var_names.append(var_name) 

3228 elem_variables_to_transform = np.array([i for i, name in enumerate( 

3229 elem_var_names) if name.lower() in [var.lower() for var in variables_to_transform]]) 

3230 except TypeError: 

3231 elem_variables_to_transform = [] 

3232 else: 

3233 try: 

3234 node_variables_to_transform = np.array([i for i, name in enumerate( 

3235 fexo.get_node_variable_names()) if name.lower() in [var.lower() for var in variables_to_transform]]) 

3236 except ExodusError: 

3237 node_variables_to_transform = [] 

3238 try: 

3239 elem_variables_to_transform = np.array([i for i, name in enumerate( 

3240 fexo.get_elem_variable_names()) if name.lower() in [var.lower() for var in variables_to_transform]]) 

3241 except ExodusError: 

3242 elem_variables_to_transform = [] 

3243 if verbose: 

3244 print('Keeping {:} nodal variables and {:} element variables'.format( 

3245 len(node_variables_to_transform), len(elem_variables_to_transform))) 

3246 

3247 # Define element types and what we will do to reduce them 

3248 reduced_element_types = [] 

3249 reduced_element_nodes_per_face = [] 

3250 reduced_element_face_connectivity = [] 

3251 reduced_element_substitute_type = [] 

3252 

3253 # 10-node tetrahedral 

3254 reduced_element_types.append('tetra10') 

3255 if keep_midside_nodes: 

3256 reduced_element_nodes_per_face.append(6) 

3257 reduced_element_face_connectivity.append([0, 1, 3, 4, 8, 7, 

3258 1, 2, 3, 5, 9, 8, 

3259 2, 0, 3, 6, 7, 9, 

3260 0, 2, 1, 6, 5, 4]) 

3261 reduced_element_substitute_type.append('tri6') 

3262 else: 

3263 reduced_element_nodes_per_face.append(3) 

3264 reduced_element_face_connectivity.append([0, 1, 3, 

3265 1, 2, 3, 

3266 2, 0, 3, 

3267 0, 2, 1]) 

3268 reduced_element_substitute_type.append('tri3') 

3269 # 4-node tetrahedral 

3270 reduced_element_types.append('tetra4') 

3271 reduced_element_nodes_per_face.append(3) 

3272 reduced_element_face_connectivity.append([0, 1, 3, 

3273 1, 2, 3, 

3274 2, 0, 3, 

3275 0, 2, 1]) 

3276 reduced_element_substitute_type.append('tri3') 

3277 # 20-node hexahedral 

3278 reduced_element_types.append('hex20') 

3279 if keep_midside_nodes: 

3280 reduced_element_nodes_per_face.append(8) 

3281 reduced_element_face_connectivity.append([0, 1, 5, 4, 8, 13, 16, 12, 

3282 1, 2, 6, 5, 9, 14, 17, 13, 

3283 2, 3, 7, 6, 10, 15, 18, 14, 

3284 3, 0, 4, 7, 11, 12, 19, 15, 

3285 3, 2, 1, 0, 11, 8, 9, 10, 

3286 4, 5, 6, 7, 16, 17, 18, 19]) 

3287 reduced_element_substitute_type.append('quad8') 

3288 else: 

3289 reduced_element_nodes_per_face.append(4) 

3290 reduced_element_face_connectivity.append([0, 1, 5, 4, 

3291 1, 2, 6, 5, 

3292 2, 3, 7, 6, 

3293 3, 0, 4, 7, 

3294 3, 2, 1, 0, 

3295 4, 5, 6, 7]) 

3296 reduced_element_substitute_type.append('quad4') 

3297 # 8-node hexahedral 

3298 reduced_element_types.append('hex8') 

3299 reduced_element_nodes_per_face.append(4) 

3300 reduced_element_face_connectivity.append([0, 1, 5, 4, 

3301 1, 2, 6, 5, 

3302 2, 3, 7, 6, 

3303 3, 0, 4, 7, 

3304 3, 2, 1, 0, 

3305 4, 5, 6, 7]) 

3306 reduced_element_substitute_type.append('quad4') 

3307 reduced_element_types.append('hex') 

3308 reduced_element_nodes_per_face.append(4) 

3309 reduced_element_face_connectivity.append([0, 1, 5, 4, 

3310 1, 2, 6, 5, 

3311 2, 3, 7, 6, 

3312 3, 0, 4, 7, 

3313 3, 2, 1, 0, 

3314 4, 5, 6, 7]) 

3315 reduced_element_substitute_type.append('quad4') 

3316 

3317 # Keep track of which nodes are used in the final model 

3318 if exo_in_mem: 

3319 keep_nodes = np.zeros(fexo.nodes.coordinates.shape[0], dtype=bool) 

3320 else: 

3321 keep_nodes = np.zeros(fexo.num_nodes, dtype=bool) 

3322 

3323 # Create the output variable 

3324 fexo_out = ExodusInMemory() 

3325 # Set the same times 

3326 if exo_in_mem: 

3327 fexo_out.time = copy.deepcopy(fexo.time) 

3328 else: 

3329 fexo_out.time = fexo.get_times().data 

3330 

3331 # Since we are just visualizing, eliminate exodus features that we don't 

3332 # care about 

3333 fexo_out.element_map = None 

3334 fexo_out.sidesets = None 

3335 fexo_out.nodesets = None 

3336 

3337 if exo_in_mem: 

3338 block_inds_to_transform = [(block_index, block.id) for block_index, block in enumerate( 

3339 fexo.blocks) if block.id in blocks_to_transform] 

3340 else: 

3341 block_inds_to_transform = [(block_index, block_id) for block_index, block_id in enumerate( 

3342 fexo.get_elem_blk_ids()) if block_id in blocks_to_transform] 

3343 

3344 # If the exodus file has element variables, we are only keeping the ones 

3345 # corresponding to the blocks we are keeping 

3346 if exo_in_mem: 

3347 has_elem_vars = (fexo.elem_vars is not None) and len(node_variables_to_transform) > 0 

3348 else: 

3349 try: 

3350 has_elem_vars = fexo.num_elem_variables > 0 and len(elem_variables_to_transform) > 0 

3351 except ExodusError: 

3352 has_elem_vars = False 

3353 if has_elem_vars: 

3354 fexo_out.elem_vars = [] 

3355 

3356 # Now we loop through each block to reduce the elements to faces 

3357 for block_index, block_id in block_inds_to_transform: 

3358 # Determine the element type 

3359 if exo_in_mem: 

3360 elem_type = copy.deepcopy(fexo.blocks[block_index].elem_type.lower()) 

3361 connectivity = copy.deepcopy(fexo.blocks[block_index].connectivity) 

3362 else: 

3363 elem_type = copy.deepcopy(fexo.get_elem_blk_info(block_id)[0].lower()) 

3364 connectivity = copy.deepcopy(fexo.get_elem_connectivity(block_id).data) 

3365 if verbose: 

3366 print('Analyzing Block {}, type {}'.format(block_id, elem_type)) 

3367 if elem_type in reduced_element_types: 

3368 block_type_ind = reduced_element_types.index(elem_type) 

3369 # If the element type IS in our list of types to reduce, then we 

3370 # will reduce to only surface nodes and elements. 

3371 # Here we are computing all the faces in the block and creating a 

3372 # face connectivity matrix (nfaces x nnodes per face) from an 

3373 # element connectivity matrix (nelems x nnodes per element). It is 

3374 # a complicated one-liner but boy is it fast. The basic steps are: 

3375 # 1. Pass the element reduce_element_face vector in as the column 

3376 # indices of the element connectivity matrix, which selects the face 

3377 # nodes of all faces, making each row of the block connectivity 

3378 # matrix contain all faces 

3379 # block.connectivity[:,reduced_element_face_connectivity[block_type_ind]] 

3380 # 2. Since each row now contains a number of faces, we want to 

3381 # split it out into each face using the reshape command with the 

3382 # number of nodes per face. After we do this, we have a connectivity 

3383 # matrix where each row is a face of the element and the columns are 

3384 # the nodes in that face. 

3385 face_connectivity = connectivity[:, reduced_element_face_connectivity[block_type_ind]].reshape( 

3386 (-1, reduced_element_nodes_per_face[block_type_ind])) 

3387 # We will create a list that ties each face back to the original 

3388 # element. This is used to map the element variables that were 

3389 # defined for the volume elements to the newly created surface 

3390 # elements. Basically this vector will be increasing by 1, with 

3391 # each number repeated for the number of faces in the element. For 

3392 # example if the elements are tet4s with 4 faces per element, the 

3393 # vector will look like 

3394 # [0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, ... ] 

3395 face_original_elements = np.repeat(np.arange(connectivity.shape[0]), len( 

3396 reduced_element_face_connectivity[block_type_ind]) / reduced_element_nodes_per_face[block_type_ind]) 

3397 # To find EXTERIOR faces, we want to look for faces that only exist 

3398 # once. If two elements have the same face, then that face is 

3399 # necessarily on the interior of the block. Because the ordering 

3400 # of the nodes is somewhat arbitrary, we first sort the nodes so 

3401 # they are in ascending order for each face, then we can directly 

3402 # compare the rows because two rows consisting of the same nodes 

3403 # will look the same if they are sorted. This gives us the unique 

3404 # rows (but we don't really care about them because they are 

3405 # sorted, so we ditch the first output argument), the unique row 

3406 # indices, which is a list of the first time that index appeared 

3407 # for each unique entry, and a unique row mapping, which is the 

3408 # location each row in the original face connectivity matrix ends 

3409 # up in the unique set of rows (see help unique for more info) 

3410 (unique_rows, unique_row_indices, unique_inverse, unique_counts) = np.unique(np.sort( 

3411 face_connectivity, axis=1), return_index=True, return_inverse=True, return_counts=True, axis=0) 

3412 # We can compute the values that are duplicates by using the optional 

3413 # output argumenst of unique. The counts variable will enable us to 

3414 # see which values were repeated, and the unique_inverse will allow us 

3415 # to reconstruct the original array from the unique array. 

3416 original_unique_counts = unique_counts[unique_inverse] 

3417 # The non-duplicated faces are then the ones that only exist once in 

3418 # the array 

3419 nondupe_faces = original_unique_counts == 1 

3420 # We then reduce the face connectivity matrix, keeping only the faces 

3421 # that are not duplicated. 

3422 face_connectivity = face_connectivity[nondupe_faces, :] 

3423 # We also reduce our vector that ties the faces back to the original 

3424 # values to keep it consistent with the face connectivity matrix 

3425 face_original_elements = face_original_elements[nondupe_faces] 

3426 # Now we need to also reduce the element variables in the same way, 

3427 # mapping the original elements variables to the faces that correspond 

3428 # to that element. 

3429 new_elem_type = reduced_element_substitute_type[block_type_ind] 

3430 else: 

3431 # If we don't reduce, 

3432 if verbose: 

3433 print(' No Reduction') 

3434 face_connectivity = connectivity 

3435 face_original_elements = np.arange(connectivity.shape[0]) 

3436 new_elem_type = elem_type 

3437 # We then update the connectivity in the block to make it match our new 

3438 # face connectivity 

3439 blk_dict = {} 

3440 blk_dict['id'] = block_id 

3441 blk_dict['connectivity'] = face_connectivity 

3442 blk_dict['elem_type'] = new_elem_type 

3443 blk_dict['name'] = 'block_{:}'.format(block_id) 

3444 blk_dict['status'] = 1 

3445 blk_dict['attributes'] = None 

3446 fexo_out.blocks.append(subfield(**blk_dict)) 

3447 if verbose: 

3448 print(' Converted to {}'.format(blk_dict['elem_type'])) 

3449 # The nodes in the block are now all the unique nodes in the face 

3450 # connectivity matrix, so we make sure to keep those nodes in the final 

3451 # set of nodes. 

3452 block_nodes = np.unique(face_connectivity) 

3453 keep_nodes[block_nodes] = True 

3454 

3455 # Update element variables if necessary 

3456 if has_elem_vars: 

3457 var_names = [] 

3458 var_data = [] 

3459 if exo_in_mem: 

3460 # Select the correct variable 

3461 face_elem_vars = fexo.elem_vars[[var.id for var in fexo.elem_vars].index(block_id)] 

3462 for elem_index, elem_var_data in enumerate(face_elem_vars.elem_var_data): 

3463 # Check if we need to skip it 

3464 if elem_index not in elem_variables_to_transform: 

3465 print(' Skipping Element Variable {:}'.format(elem_index)) 

3466 continue 

3467 # If the variable doesn't exist it will be None (for example a 

3468 # volume quantity on a surface element). We only operate if 

3469 # the variable exists (is not None) 

3470 if elem_var_data is not None: 

3471 # We use the face's original elements vector to map the 

3472 # element data to the face connectivity matrix 

3473 if verbose: 

3474 print(' Processing Element Variable {:} for block {:}'.format( 

3475 elem_var_data.name, block_id)) 

3476 var_data.append(elem_var_data.data[:, face_original_elements]) 

3477 var_names.append(elem_var_data.name) 

3478 else: 

3479 if verbose: 

3480 print(' Element Variable {:} not defined for block {:}'.format( 

3481 elem_index, block_id)) 

3482 var_data.append(None) 

3483 var_names.append(None) 

3484 else: 

3485 # Get the element truth table 

3486 elem_truth_table = fexo.get_elem_variable_table() 

3487 # Get the element variable names 

3488 elem_var_names = fexo.get_elem_variable_names() 

3489 # Loop through and pick element variables that exist in the truth table 

3490 for elem_index, elem_name in enumerate(elem_var_names): 

3491 # Check if we need to skip it 

3492 if elem_index not in elem_variables_to_transform: 

3493 if verbose: 

3494 print(' Skipping Element Variable {:}'.format(elem_name)) 

3495 continue 

3496 if elem_truth_table[block_index, elem_index] == 0: 

3497 if verbose: 

3498 print(' Element Variable {:} not defined for block {:}'.format( 

3499 elem_name, block_id)) 

3500 var_data.append(None) 

3501 var_names.append(None) 

3502 else: 

3503 # We use the face's original elements vector to map the 

3504 # element data to the face connectivity matrix 

3505 if verbose: 

3506 print(' Processing Element Variable {:} for block {:}'.format( 

3507 elem_name, block_id)) 

3508 var_data.append(fexo.get_elem_variable_values( 

3509 block_id, elem_index).data[:, face_original_elements]) 

3510 var_names.append(elem_name) 

3511 # Now add the element variable 

3512 if verbose: 

3513 print(' Adding Element Variables to output exodus file') 

3514 blk_dict = {} 

3515 blk_dict['id'] = block_id 

3516 blk_dict['elem_var_data'] = [] 

3517 for name, data in zip(var_names, var_data): 

3518 if name is None or data is None: 

3519 blk_dict['elem_var_data'].append(None) 

3520 else: 

3521 elem_var_dict = {} 

3522 elem_var_dict['name'] = name 

3523 elem_var_dict['data'] = data 

3524 blk_dict['elem_var_data'].append(subfield(**elem_var_dict)) 

3525 fexo_out.elem_vars.append(subfield(**blk_dict)) 

3526 

3527 # We have reduced the face connectivity, so now we need to reduce the nodes 

3528 # as well. We create a node map vector that maps the initial node indices 

3529 # to the new node indices 

3530 # i.e. the new_node_index = node_map[old_node_index] 

3531 # We will need to do this because once we reduce the nodes, we will need to 

3532 # re-update the element connectivity matrices with the updated node numbers 

3533 # since the element connectivity matrix refers to the node index, not a 

3534 # node number. By eliminating nodes, we are effectively renumbering the 

3535 # nodes. 

3536 if verbose: 

3537 print('Analyzing Nodes') 

3538 if exo_in_mem: 

3539 original_nodes = np.arange(len(keep_nodes)) + \ 

3540 1 if fexo.nodes.node_num_map is None else fexo.nodes.node_num_map 

3541 else: 

3542 original_nodes = fexo.get_node_num_map() 

3543 fexo_out.nodes.node_num_map = original_nodes[keep_nodes] 

3544 node_map = np.zeros(keep_nodes.shape, dtype=int) 

3545 node_map[keep_nodes] = np.arange(sum(keep_nodes)) 

3546 # Reduce the nodes, keeping only the ones where keep_nodes is true 

3547 if verbose: 

3548 print(' Reducing Coordinates') 

3549 if exo_in_mem: 

3550 fexo_out.nodes.coordinates = copy.deepcopy(fexo.nodes.coordinates[keep_nodes, :]) 

3551 else: 

3552 fexo_out.nodes.coordinates = fexo.get_coords()[:, keep_nodes].T 

3553 # We do the same thing to the nodal variables if they exist 

3554 if verbose: 

3555 print(' Reducing Nodal Variables') 

3556 if exo_in_mem: 

3557 if len(node_variables_to_transform) > 0: 

3558 fexo_out.nodal_vars = [] 

3559 for node_var_index, node_var in enumerate(fexo.nodal_vars): 

3560 if node_var_index not in node_variables_to_transform: 

3561 if verbose: 

3562 print(' Skipping Nodal Variable {:}'.format(node_var.name)) 

3563 continue 

3564 node_dict = {} 

3565 node_dict['name'] = node_var.name 

3566 node_dict['data'] = copy.deepcopy(node_var.data[:, keep_nodes]) 

3567 fexo_out.nodal_vars.append(subfield(**node_dict)) 

3568 else: 

3569 try: 

3570 if len(node_variables_to_transform) > 0: 

3571 fexo_out.nodal_vars = [] 

3572 node_vars = fexo.get_node_variable_names() 

3573 for node_var_index, node_var_name in enumerate(node_vars): 

3574 if node_var_index not in node_variables_to_transform: 

3575 if verbose: 

3576 print(' Skipping Nodal Variable {:}'.format(node_var_name)) 

3577 continue 

3578 if verbose: 

3579 print(' Variable {:}: {:}'.format(node_var_index, node_var_name)) 

3580 node_dict = {} 

3581 node_dict['name'] = node_var_name 

3582 node_dict['data'] = np.empty((len(fexo_out.time), sum(keep_nodes))) 

3583 for i, time in enumerate(fexo_out.time): 

3584 if verbose: 

3585 print(' Step {:}'.format(i)) 

3586 node_dict['data'][i, :] = fexo.get_node_variable_values( 

3587 node_var_index, step=i).data[keep_nodes] 

3588 fexo_out.nodal_vars.append(subfield(**node_dict)) 

3589 except ExodusError: 

3590 fexo_out.nodal_vars = None 

3591 

3592 # Finally we go through each element block and map the old node indices to 

3593 # the new node indices 

3594 for block in fexo_out.blocks: 

3595 block.connectivity = node_map[block.connectivity] 

3596 

3597 # Return our new fexo 

3598 return fexo_out 

3599 

3600 

3601def extract_sharp_edges(exo, edge_threshold=60, **kwargs): 

3602 dot_threshold = np.cos(edge_threshold * np.pi / 180) 

3603 surface_fexo = reduce_exodus_to_surfaces(exo, **kwargs) 

3604 edge_fexo = copy.deepcopy(surface_fexo) 

3605 keep_nodes = np.zeros(surface_fexo.nodes.coordinates.shape[0], dtype=bool) 

3606 for block_ind, block in enumerate(surface_fexo.blocks): 

3607 connectivity = block.connectivity 

3608 if connectivity.shape[-1] < 3 or 'bar' in block.elem_type.lower() or 'beam' in block.elem_type.lower(): 

3609 edge_fexo.blocks[block_ind].id = None 

3610 continue 

3611 print('Reducing Block {:}'.format(block.id)) 

3612 edge_connectivity = np.concatenate((connectivity[..., np.newaxis], 

3613 np.roll(connectivity, -1, -1)[..., np.newaxis]), -1).reshape(-1, 2) 

3614 edge_original_elements = np.repeat(np.arange(connectivity.shape[0]), connectivity.shape[-1]) 

3615 (unique_rows, unique_row_indices, unique_inverse, unique_counts) = np.unique(np.sort( 

3616 edge_connectivity, axis=1), return_index=True, return_inverse=True, return_counts=True, axis=0) 

3617 # Now we go through each edge and compute the angle between the two original faces 

3618 keep_edges = np.zeros(unique_rows.shape[0], dtype=bool) 

3619 for i, (edge, count) in enumerate(zip(unique_rows, unique_counts)): 

3620 if count != 2: 

3621 keep_edges[i] = True 

3622 else: 

3623 # Find faces that the edges are in 

3624 edge_indices = np.nonzero(unique_inverse == i)[0] 

3625 face_indices = edge_original_elements[edge_indices] 

3626 face_connectivities = connectivity[face_indices] 

3627 node_coords = surface_fexo.nodes.coordinates[face_connectivities[:, :3], :] 

3628 vectors_a = node_coords[:, 2] - node_coords[:, 0] 

3629 vectors_b = node_coords[:, 1] - node_coords[:, 0] 

3630 normals = np.cross(vectors_a, vectors_b) 

3631 normals /= np.linalg.norm(normals, axis=-1)[:, np.newaxis] 

3632 if np.dot(*normals) < dot_threshold: 

3633 keep_edges[i] = True 

3634 print(' Kept {:} of {:} edges'.format(keep_edges.sum(), keep_edges.size)) 

3635 edge_connectivity = unique_rows[keep_edges] 

3636 edge_fexo.blocks[block_ind].connectivity = edge_connectivity 

3637 edge_fexo.blocks[block_ind].elem_type = 'bar' 

3638 block_nodes = np.unique(edge_connectivity) 

3639 keep_nodes[block_nodes] = True 

3640 node_map = np.zeros(keep_nodes.shape, dtype=int) 

3641 node_map[keep_nodes] = np.arange(sum(keep_nodes)) 

3642 edge_fexo.nodes.coordinates = edge_fexo.nodes.coordinates[keep_nodes, :] 

3643 for node_var_index, node_var in enumerate(edge_fexo.nodal_vars): 

3644 node_var.data = node_var.data[:, keep_nodes] 

3645 edge_fexo.elem_vars = None 

3646 edge_fexo.blocks = [block for block in edge_fexo.blocks if block.id is not None] 

3647 for block in edge_fexo.blocks: 

3648 block.connectivity = node_map[block.connectivity] 

3649 edge_fexo.nodes.node_num_map = surface_fexo.nodes.node_num_map[keep_nodes] 

3650 return edge_fexo 

3651 

3652def read_sierra_matlab_matrix_file(file): 

3653 """ 

3654 Reads a matrix.m file from Sierra Structural Dynamics. 

3655  

3656 If the KAA or MAA outputs are used, this function can read those matrices. 

3657 

3658 Parameters 

3659 ---------- 

3660 file : str 

3661 The name of the file to read. 

3662 

3663 Returns 

3664 ------- 

3665 matrix : np.ndarray 

3666 A NumPy array containing the matrix. 

3667 """ 

3668 with open(file,'r') as f: 

3669 f.readline() 

3670 num_rows = int(f.readline().replace(';','').split()[-1]) 

3671 matrix = np.zeros((num_rows,num_rows)) 

3672 f.readline() 

3673 while True: 

3674 try: 

3675 row,col,data = f.readline().replace(';','').split() 

3676 matrix[int(row)-1,int(col)-1] = float(data) 

3677 if int(row) != int(col): 

3678 matrix[int(col)-1,int(row)-1] = float(data) 

3679 except ValueError: 

3680 break 

3681 return matrix 

3682 

3683def read_sierra_matlab_map_file(file,return_inverse=False): 

3684 """ 

3685 Reads a map file from Sierra Structural Dynamics. 

3686  

3687 If the MFILE output is used, this can read the global node ID and aset map arrays that 

3688 are output from Sierra SD. 

3689 

3690 Parameters 

3691 ---------- 

3692 file : str 

3693 File containing the map data. 

3694 return_inverse : bool, optional 

3695 If True, will return both the forward map and the inverse map. The default is False. 

3696 

3697 Returns 

3698 ------- 

3699 array : np.ndarray 

3700 Returns the same map as in the file. 

3701 array_inv : np.ndarray 

3702 Returns the inverse map of the file. Only returned if return_inverse is True. 

3703 

3704 """ 

3705 with open(file,'r') as f: 

3706 f.readline() 

3707 f.readline() 

3708 shape = [int(v) for v in f.readline().strip().split('(')[1][:-2].split(',')] 

3709 array = -1*np.ones(shape,dtype=int) 

3710 while True: 

3711 try: 

3712 lhs,rhs = f.readline().split('=') 

3713 index = int(lhs.strip()[2:-1])-1 

3714 value = int(rhs.strip()[:-1]) 

3715 array[index] = value 

3716 except ValueError: 

3717 break 

3718 array = array.flatten() 

3719 if not return_inverse: 

3720 return array 

3721 array_inv = -1*np.ones(array.max()+1,dtype=int) 

3722 for index,value in enumerate(array): 

3723 if value == -1: 

3724 continue 

3725 array_inv[value] = index 

3726 return array,array_inv