Coverage for lasso/dyna/d3plot_header.py: 89%

415 statements  

« prev     ^ index     » next       coverage.py v7.2.4, created at 2023-04-28 18:42 +0100

1import enum 

2from typing import Any, Dict, Tuple, Union 

3 

4import numpy as np 

5import rich 

6 

7from ..io.binary_buffer import BinaryBuffer 

8from ..logging import get_logger 

9 

10# We have a lot of docstrings here but even if not so, we want to contain the 

11# code here. 

12# pylint: disable=too-many-lines 

13 

14LOGGER = get_logger(__file__) 

15 

16 

17def get_digit(number: int, i_digit: int) -> int: 

18 """Get a digit from a number 

19 

20 Parameters 

21 ---------- 

22 number: int 

23 number to get digit from 

24 i_digit: int 

25 index of the digit 

26 

27 Returns 

28 ------- 

29 digit: int 

30 digit or 0 if i_digit is too large 

31 

32 Notes 

33 ----- 

34 `i_digit` does refer to a digit from the 

35 lowest position counting. Thus, 

36 123 with `i_digit=0` is `3`. 

37 """ 

38 digit_list = [] 

39 

40 # pylint: disable = inconsistent-return-statements 

41 def _get_digit_recursive(x: int): 

42 if x < 10: 

43 digit_list.append(x) 

44 return x 

45 _get_digit_recursive(x // 10) 

46 digit_list.append(x % 10) 

47 

48 # do the thing 

49 _get_digit_recursive(number) 

50 

51 # revert list from smallest to biggest 

52 digit_list = digit_list[::-1] 

53 

54 return digit_list[i_digit] if i_digit < len(digit_list) else 0 

55 

56 

57class D3plotFiletype(enum.Enum): 

58 """Enum for the filetype of a D3plot""" 

59 

60 D3PLOT = 1 

61 D3PART = 5 

62 D3EIGV = 11 

63 INTFOR = 4 

64 

65 

66def d3plot_filetype_from_integer(value: int) -> D3plotFiletype: 

67 """Get a D3plotFiletype object from an integer 

68 

69 Parameters 

70 ---------- 

71 value: int 

72 integer value representing the filetype 

73 

74 Returns 

75 ------- 

76 filetype: D3plotFiletype 

77 d3plot filetype object 

78 

79 Raises 

80 ------ 

81 RuntimeError if invalid value. 

82 """ 

83 

84 valid_entries = { 

85 entry.value: entry 

86 for entry in D3plotFiletype.__members__.values() 

87 if entry.value != 4 # no intfor 

88 } 

89 

90 if value not in valid_entries: 

91 valid_filetypes = ",".join( 

92 f"{key} ({value.value})" 

93 for key, value in D3plotFiletype.__members__.items() 

94 if value.value != 4 

95 ) 

96 err_msg = f"Invalid filetype value of {value}. Expected one of: {valid_filetypes}" 

97 raise ValueError(err_msg) 

98 

99 return valid_entries[value] 

100 

101 

102# pylint: disable = too-many-instance-attributes 

103class D3plotHeader: 

104 """Class for reading only header information of a d3plot 

105 

106 Attributes 

107 ---------- 

108 filepath: str 

109 Filepath of the processed file. 

110 itype: np.dtype 

111 Integer type of d3plot. 

112 ftype: np.dtype 

113 Floating point type of d3plot. 

114 wordsize: int 

115 size of words in bytes (4 = single precision, 8 = double precision). 

116 raw_header: Dict[str, Any] 

117 Raw header data as dict. 

118 external_numbers_dtype: np.dtype 

119 Integer type of user ids. 

120 n_header_bytes: int 

121 Number of bytes of header (at least 256 or more). 

122 title: str 

123 Main title. 

124 title2: str 

125 Optional, secondary title. 

126 runtime: int 

127 Runtime of the d3plot as timestamp. 

128 filetype: D3plotFiletype 

129 Filetype such as d3plot or d3part. 

130 source_version: int 

131 Source version of LS-Dyna. 

132 release_version: str 

133 Release version of LS-Dyna. 

134 version: float 

135 Version of LS-Dyna. 

136 extra_long_header: bool 

137 If header was longer than default. 

138 n_dimensions: int 

139 Number of dimensions, usually three. 

140 n_global_vars: int 

141 How many global vars for each state. 

142 n_adapted_element_pairs: int 

143 How many adapted element pairs. 

144 has_node_deletion_data: bool 

145 If node deletion data is present. 

146 has_element_deletion_data: bool 

147 If element deletion data is present. 

148 has_numbering_section: bool 

149 If a user numbering section is present. 

150 has_material_type_section: bool 

151 If material type section was written. 

152 n_numbering_section_words: int 

153 Amount of words for numbering section. 

154 has_invariant_numbering: bool 

155 If invariant numbering is used whatever that means. 

156 quadratic_elems_has_full_connectivity: bool 

157 If quadric elements have full connectivity. 

158 quadratic_elems_has_data_at_integration_points: bool 

159 If quadric elements data is at integration points. 

160 n_post_branches: int 

161 Unused and unknown. 

162 n_types: Tuple[int, ...] 

163 Behind geometry these are integers indicating additional data such as 

164 part names. 

165 n_parts: int 

166 Obviously number of parts. 

167 n_nodes: int 

168 Number of nodes. 

169 has_node_temperatures: bool 

170 If node temperature is present. 

171 has_node_temperature_layers: bool 

172 If node temperatures are layered. 

173 has_node_heat_flux: bool 

174 If node heat flux is present. 

175 has_node_mass_scaling: bool 

176 Mass scaling is written. 

177 has_node_displacement: bool 

178 Node displacement is written. 

179 has_node_velocity: bool 

180 Node velocity is written. 

181 has_node_acceleration: bool 

182 Node acceleration is written. 

183 has_node_temperature_gradient: bool 

184 Node temperature gradient is written. 

185 has_node_residual_forces: bool 

186 Node residual forces are written. 

187 has_node_residual_moments: bool 

188 Node residual moments are written. 

189 has_node_max_contact_penetration_absolute: bool 

190 Node contact penetration info exist. 

191 has_node_max_contact_penetration_relative: bool 

192 Node relative contact penetration info was written. 

193 has_node_contact_energy_density: int 

194 Node energy density was written. 

195 n_shell_tshell_layers: int 

196 Number of layers for shells and thick shells. 

197 n_shell_tshell_history_vars: int 

198 Number of history vars for shells and thick shells. 

199 has_shell_tshell_stress: bool 

200 If shells and thick shells have stresses. 

201 has_shell_tshell_pstrain: bool 

202 If shells and thick shells have eff. plastic strain. 

203 has_element_strain: bool 

204 If all elements have strain. 

205 has_solid_shell_plastic_strain_tensor: bool 

206 If solids have plastic strain tensor. 

207 has_solid_shell_thermal_strain_tensor: bool 

208 If solids have thermal strain tensor. 

209 n_solids: int 

210 Number of solids. 

211 n_solid_vars: int 

212 Number of solid variables per element and state. 

213 n_solid_materials: int 

214 Number of solid materials/parts. 

215 n_solid_history_vars: int 

216 Number of solid history variables. 

217 n_solid_thermal_vars: int 

218 Number of solid thermal variables. 

219 n_solids_20_node_hexas: int 

220 Number of 20-node solid hexas. 

221 n_solids_27_node_hexas: int 

222 Number of 27-node solid hexas. 

223 n_solids_21_node_pentas: int 

224 Number of 21-node solid pentas. 

225 n_solids_15_node_tetras: int 

226 Number of 15-node solid tetras. 

227 n_solids_20_node_tetras: int 

228 Number of 20-node solid tetras. 

229 n_solids_40_node_pentas: int 

230 Number of 40-node solid pentas. 

231 n_solids_64_node_hexas: int 

232 Number of 64-node solid hexas. 

233 has_solid_2_extra_nodes: bool 

234 If two extra nodes were written for solids. 

235 has_solid_stress: bool 

236 If solid stress is present. 

237 has_solid_pstrain: bool 

238 If solid eff. plastic strain is present. 

239 has_quadratic_solids: bool 

240 If quadratic solids were used. 

241 has_cubic_solids: bool 

242 If cubic solids were used. 

243 has_solid_internal_energy_density: bool 

244 If solids have internal energy density. 

245 n_solid_layers: int 

246 Number of solid layers. 

247 n_shells: int 

248 Number of shells. 

249 n_shell_vars: int 

250 Number of shell vars per element and state. 

251 n_shell_materials: int 

252 Number of shell materials/parts. 

253 n_shells_8_nodes: int 

254 Number of 8-node shells. 

255 has_shell_four_inplane_gauss_points: bool 

256 If shells have four inplace gaussian integration points. 

257 has_shell_forces: bool 

258 If shell forces are present. 

259 has_shell_extra_variables: bool 

260 If extra shell variables such as forces are present. 

261 has_shell_internal_energy_density: bool 

262 If shell internal energy density is present. 

263 n_thick_shells: int 

264 Number of thick shell elements. 

265 n_thick_shell_vars: int 

266 Number of thick shell element vars. 

267 n_thick_shell_materials: int 

268 Number of thick shell materials/parts. 

269 has_thick_shell_energy_density: bool 

270 If thick shells have energy density. 

271 thick_shell_energy_density_position: int 

272 Nnused. 

273 n_beams: int 

274 Number of beam elements. 

275 n_beam_vars: int 

276 Number of state variables per beam element. 

277 n_beam_materials: int 

278 Number of beam materials. 

279 n_beam_history_vars: int 

280 Number of beam history variables. 

281 n_airbags: int 

282 Number of airbags. 

283 has_airbag_n_chambers: bool 

284 If airbags have number of chambers var. 

285 has_rigid_road_surface: bool 

286 If rigid road surface was written. 

287 has_rigid_body_data: bool 

288 If rigid body section was written. 

289 has_reduced_rigid_body_data: bool 

290 If the reduced set of rigid body data was written. 

291 n_rigid_wall_vars: int 

292 Number of rigid wall vars. 

293 n_sph_nodes: int 

294 Number of sph nodes. 

295 n_sph_materials: int 

296 Number of sph materials. 

297 n_ale_materials: int 

298 Number of ale materials. 

299 n_ale_fluid_groups: int 

300 Number of ale fluid groups. 

301 has_cfd_data: bool 

302 If CFD-Data was written. 

303 has_multi_solver_data: bool 

304 If multi-solver data was written. 

305 cfd_extra_data: int 

306 If cfd data contains extra section. 

307 legacy_code_type: int 

308 Originally a code indicator but unused nowadays. 

309 unused_numst: int 

310 Unused and not explained in docs. 

311 """ 

312 

313 # meta 

314 filepath: str = "" 

315 

316 # file info 

317 itype: np.dtype = np.int32 

318 ftype: np.dtype = np.float32 

319 wordsize: int = 4 

320 raw_header: Dict[str, Any] = {} 

321 external_numbers_dtype = np.int32 

322 n_header_bytes: int = 0 

323 

324 # header 

325 title: str = "" 

326 title2: str = "" 

327 runtime: int = 0 

328 filetype: D3plotFiletype = D3plotFiletype.D3PLOT 

329 

330 source_version: int = 0 

331 release_version: str = "" 

332 version: float = 0.0 

333 extra_long_header: bool = False 

334 

335 # general info 

336 n_dimensions: int = 3 

337 n_global_vars: int = 0 

338 n_adapted_element_pairs: int = 0 

339 has_node_deletion_data: bool = False 

340 has_element_deletion_data: bool = False 

341 has_numbering_section: bool = False 

342 has_material_type_section: bool = False 

343 n_numbering_section_words: int = 0 

344 has_invariant_numbering: bool = False 

345 quadratic_elems_has_full_connectivity: bool = False 

346 quadratic_elems_has_data_at_integration_points: bool = False 

347 n_post_branches: int = 0 

348 n_types: Tuple[int, ...] = tuple() 

349 

350 # parts 

351 n_parts: int = 0 

352 

353 # nodes 

354 n_nodes: int = 0 

355 has_node_temperatures: bool = False 

356 has_node_temperature_layers: bool = False 

357 has_node_heat_flux: bool = False 

358 has_node_mass_scaling: bool = False 

359 has_node_displacement: bool = False 

360 has_node_velocity: bool = False 

361 has_node_acceleration: bool = False 

362 has_node_temperature_gradient: bool = False 

363 has_node_residual_forces: bool = False 

364 has_node_residual_moments: bool = False 

365 has_node_max_contact_penetration_absolute: bool = False 

366 has_node_max_contact_penetration_relative: bool = False 

367 has_node_contact_energy_density: int = False 

368 

369 # elements 

370 n_shell_tshell_layers: int = 3 

371 n_shell_tshell_history_vars: int = 0 

372 has_shell_tshell_stress: bool = False 

373 has_shell_tshell_pstrain: bool = False 

374 has_element_strain: bool = False 

375 has_solid_shell_plastic_strain_tensor: bool = False 

376 has_solid_shell_thermal_strain_tensor: bool = False 

377 

378 # solids 

379 n_solids: int = 0 

380 n_solid_vars: int = 0 

381 n_solid_materials: int = 0 

382 n_solid_history_vars: int = 0 

383 n_solid_thermal_vars: int = 0 

384 n_solids_20_node_hexas: int = 0 

385 n_solids_27_node_hexas: int = 0 

386 n_solids_21_node_pentas: int = 0 

387 n_solids_15_node_tetras: int = 0 

388 n_solids_20_node_tetras: int = 0 

389 n_solids_40_node_pentas: int = 0 

390 n_solids_64_node_hexas: int = 0 

391 has_solid_2_extra_nodes: bool = False 

392 has_solid_stress: bool = False 

393 has_solid_pstrain: bool = False 

394 has_quadratic_solids: bool = False 

395 has_cubic_solids: bool = False 

396 has_solid_internal_energy_density: bool = False 

397 

398 # shells 

399 n_shells: int = 0 

400 n_shell_vars: int = 0 

401 n_shell_materials: int = 0 

402 n_shells_8_nodes: int = 0 

403 has_shell_four_inplane_gauss_points: bool = False 

404 has_shell_forces: bool = False 

405 has_shell_extra_variables: bool = False 

406 has_shell_internal_energy_density: bool = False 

407 # has_shell_internal_energy: bool = False 

408 

409 # thick shells 

410 n_thick_shells: int = 0 

411 n_thick_shell_vars: int = 0 

412 n_thick_shell_materials: int = 0 

413 has_thick_shell_energy_density: bool = False 

414 thick_shell_energy_density_position: int = 0 

415 

416 # beams 

417 n_beams: int = 0 

418 n_beam_vars: int = 0 

419 n_beam_materials: int = 0 

420 n_beam_history_vars: int = 0 

421 

422 # airbags 

423 n_airbags: int = 0 

424 has_airbag_n_chambers: bool = False 

425 

426 # rigid roads 

427 has_rigid_road_surface: bool = False 

428 

429 # rigid bodies 

430 has_rigid_body_data: bool = False 

431 has_reduced_rigid_body_data: bool = False 

432 

433 # sph 

434 n_sph_nodes: int = 0 

435 n_sph_materials: int = 0 

436 

437 # ale 

438 n_ale_materials: int = 0 

439 n_ale_fluid_groups: int = 0 

440 

441 # cfd 

442 has_cfd_data: bool = False 

443 

444 # multi-solver 

445 has_multi_solver_data: bool = False 

446 cfd_extra_data: int = 0 

447 

448 # historical artifacts 

449 legacy_code_type: int = 6 

450 unused_numst: int = 0 

451 

452 def __init__(self, filepath: Union[str, BinaryBuffer, None] = None): 

453 """Create a D3plotHeader instance 

454 

455 Parameters 

456 ---------- 

457 filepath: Union[str, BinaryBuffer, None] 

458 path to a d3plot file or a buffer holding d3plot memory 

459 

460 Returns 

461 ------- 

462 header: D3plotHeader 

463 d3plot header instance 

464 

465 Examples 

466 -------- 

467 Create an empty header file 

468 

469 >>> header = D3plotHeader() 

470 

471 Now load only the header of a d3plot. 

472 

473 >>> header.load_file("path/to/d3plot") 

474 

475 Or we can do the above together. 

476 

477 >>> header = D3plotHeader("path/to/d3plot") 

478 

479 Notes 

480 ----- 

481 This class does not load the entire memory of a d3plot 

482 but merely what is required to parse the header information. 

483 Thus, it is safe to use on big files. 

484 """ 

485 

486 if filepath is not None: 

487 self.load_file(filepath) 

488 

489 def print(self) -> None: 

490 """Print the header""" 

491 rich.print(self.__dict__) 

492 

493 def _read_file_buffer(self, filepath: str) -> BinaryBuffer: 

494 """Reads a d3plots header 

495 

496 Parameters 

497 ---------- 

498 filepath: str 

499 path to d3plot 

500 

501 Returns 

502 ------- 

503 bb: BinaryBuffer 

504 buffer holding the exact header data in binary form 

505 """ 

506 

507 LOGGER.debug("_read_file_buffer start") 

508 LOGGER.debug("filepath: %s", filepath) 

509 

510 # load first 64 single words 

511 n_words_header = 64 

512 n_bytes_hdr_guessed = 64 * self.wordsize 

513 bb = BinaryBuffer(filepath, n_bytes_hdr_guessed) 

514 

515 # check if single or double 

516 self.wordsize, self.itype, self.ftype = self._determine_file_settings(bb) 

517 

518 # Oops, seems other wordsize is used 

519 if self.wordsize != D3plotHeader.wordsize: 

520 bb = BinaryBuffer(filepath, n_words_header * self.wordsize) 

521 

522 # check for extra long header 

523 n_header_bytes = self._determine_n_bytes(bb, self.wordsize) 

524 if len(bb) <= n_header_bytes: 

525 bb = BinaryBuffer(filepath, n_bytes=n_header_bytes) 

526 

527 LOGGER.debug("_read_file_buffer end") 

528 

529 return bb 

530 

531 def _determine_n_bytes(self, bb: BinaryBuffer, wordsize: int) -> int: 

532 """Determines how many bytes the header has 

533 

534 Returns 

535 ------- 

536 size: int 

537 size of the header in bytes 

538 """ 

539 

540 LOGGER.debug("_determine_n_bytes start") 

541 

542 n_base_words = 64 

543 min_n_bytes = n_base_words * wordsize 

544 

545 if len(bb) < n_base_words * wordsize: 

546 err_msg = "File or file buffer must have at least '{0}' bytes instead of '{1}'" 

547 raise RuntimeError(err_msg.format(min_n_bytes, len(bb))) 

548 

549 n_extra_header_words = int(bb.read_number(57 * self.wordsize, self.itype)) 

550 

551 LOGGER.debug("_determine_n_bytes end") 

552 

553 return (n_base_words + n_extra_header_words) * wordsize 

554 

555 def load_file(self, file: Union[str, BinaryBuffer]) -> "D3plotHeader": 

556 """Load d3plot header from a d3plot file 

557 

558 Parameters 

559 ---------- 

560 file: Union[str, BinaryBuffer] 

561 path to d3plot or `BinaryBuffer` holding memory of d3plot 

562 

563 Returns 

564 ------- 

565 self: D3plotHeader 

566 returning self on success 

567 

568 Notes 

569 ----- 

570 This routine only loads the minimal amount of data 

571 that is neccessary. Thus it is safe to use on huge files. 

572 

573 Examples 

574 -------- 

575 >>> header = D3plotHeader().load_file("path/to/d3plot") 

576 >>> header.n_shells 

577 19684 

578 """ 

579 

580 # pylint: disable = too-many-locals, too-many-branches, too-many-statements 

581 

582 LOGGER.debug("_load_file start") 

583 LOGGER.debug("file: %s", file) 

584 

585 if not isinstance(file, (str, BinaryBuffer)): 

586 err_msg = "Argument 'file' must have type 'str' or 'lasso.io.BinaryBuffer'." 

587 raise ValueError(err_msg) 

588 

589 # get the memory 

590 if isinstance(file, str): 

591 bb = self._read_file_buffer(file) 

592 self.n_header_bytes = len(bb) 

593 else: 

594 bb = file 

595 self.wordsize, self.itype, self.ftype = self._determine_file_settings(bb) 

596 self.n_header_bytes = self._determine_n_bytes(bb, self.wordsize) 

597 

598 LOGGER.debug("n_header_bytes: %d", self.n_header_bytes) 

599 

600 # read header 

601 header_words = { 

602 "title": [0 * self.wordsize, str, 9 * self.wordsize], 

603 "runtime": [10 * self.wordsize, self.itype], 

604 "filetype": [11 * self.wordsize, self.itype], 

605 "source_version": [12 * self.wordsize, self.itype], 

606 "release_version": [13 * self.wordsize, str, 1 * self.wordsize], 

607 "version": [14 * self.wordsize, self.ftype], 

608 "ndim": [15 * self.wordsize, self.itype], 

609 "numnp": [16 * self.wordsize, self.itype], 

610 "icode": [17 * self.wordsize, self.itype], 

611 "nglbv": [18 * self.wordsize, self.itype], 

612 "it": [19 * self.wordsize, self.itype], 

613 "iu": [20 * self.wordsize, self.itype], 

614 "iv": [21 * self.wordsize, self.itype], 

615 "ia": [22 * self.wordsize, self.itype], 

616 "nel8": [23 * self.wordsize, self.itype], 

617 "nummat8": [24 * self.wordsize, self.itype], 

618 "numds": [25 * self.wordsize, self.itype], 

619 "numst": [26 * self.wordsize, self.itype], 

620 "nv3d": [27 * self.wordsize, self.itype], 

621 "nel2": [28 * self.wordsize, self.itype], 

622 "nummat2": [29 * self.wordsize, self.itype], 

623 "nv1d": [30 * self.wordsize, self.itype], 

624 "nel4": [31 * self.wordsize, self.itype], 

625 "nummat4": [32 * self.wordsize, self.itype], 

626 "nv2d": [33 * self.wordsize, self.itype], 

627 "neiph": [34 * self.wordsize, self.itype], 

628 "neips": [35 * self.wordsize, self.itype], 

629 "maxint": [36 * self.wordsize, self.itype], 

630 "nmsph": [37 * self.wordsize, self.itype], 

631 "ngpsph": [38 * self.wordsize, self.itype], 

632 "narbs": [39 * self.wordsize, self.itype], 

633 "nelt": [40 * self.wordsize, self.itype], 

634 "nummatt": [41 * self.wordsize, self.itype], 

635 "nv3dt": [42 * self.wordsize, self.itype], 

636 "ioshl1": [43 * self.wordsize, self.itype], 

637 "ioshl2": [44 * self.wordsize, self.itype], 

638 "ioshl3": [45 * self.wordsize, self.itype], 

639 "ioshl4": [46 * self.wordsize, self.itype], 

640 "ialemat": [47 * self.wordsize, self.itype], 

641 "ncfdv1": [48 * self.wordsize, self.itype], 

642 "ncfdv2": [49 * self.wordsize, self.itype], 

643 "nadapt": [50 * self.wordsize, self.itype], 

644 "nmmat": [51 * self.wordsize, self.itype], 

645 "numfluid": [52 * self.wordsize, self.itype], 

646 "inn": [53 * self.wordsize, self.itype], 

647 "npefg": [54 * self.wordsize, self.itype], 

648 "nel48": [55 * self.wordsize, self.itype], 

649 "idtdt": [56 * self.wordsize, self.itype], 

650 "extra": [57 * self.wordsize, self.itype], 

651 } 

652 

653 header_extra_words = { 

654 "nel20": [64 * self.wordsize, self.itype], 

655 "nt3d": [65 * self.wordsize, self.itype], 

656 "nel27": [66 * self.wordsize, self.itype], 

657 "neipb": [67 * self.wordsize, self.itype], 

658 "nel21p": [68 * self.wordsize, self.itype], 

659 "nel15t": [69 * self.wordsize, self.itype], 

660 "soleng": [70 * self.wordsize, self.itype], 

661 "nel20t": [71 * self.wordsize, self.itype], 

662 "nel40p": [72 * self.wordsize, self.itype], 

663 "nel64": [73 * self.wordsize, self.itype], 

664 "quadr": [74 * self.wordsize, self.itype], 

665 "cubic": [75 * self.wordsize, self.itype], 

666 "tsheng": [76 * self.wordsize, self.itype], 

667 "nbranch": [77 * self.wordsize, self.itype], 

668 "penout": [78 * self.wordsize, self.itype], 

669 "engout": [79 * self.wordsize, self.itype], 

670 } 

671 

672 # read header for real 

673 self.raw_header = self.read_words(bb, header_words) 

674 

675 if self.raw_header["extra"] != 0: 

676 self.read_words(bb, header_extra_words, self.raw_header) 

677 else: 

678 for name, (_, dtype) in header_extra_words.items(): 

679 self.raw_header[name] = dtype() 

680 

681 # PARSE HEADER (no fun ahead) 

682 if isinstance(file, str): 

683 self.filepath = file 

684 elif isinstance(file, BinaryBuffer): 

685 if isinstance(file.filepath_, str): 

686 self.filepath = file.filepath_ 

687 elif isinstance(file.filepath_, list) and len(file.filepath_) > 0: 

688 self.filepath = file.filepath_[0] 

689 

690 self.title = self.raw_header["title"].strip() 

691 self.runtime = self.raw_header["runtime"] 

692 

693 # filetype 

694 filetype = self.raw_header["filetype"] 

695 if filetype > 1000: 

696 filetype -= 1000 

697 self.external_numbers_dtype = np.int64 

698 else: 

699 self.external_numbers_dtype = np.int32 

700 

701 self.filetype = d3plot_filetype_from_integer(filetype) 

702 

703 self.source_version = self.raw_header["source_version"] 

704 self.release_version = self.raw_header["release_version"] # .split("\0", 1)[0] 

705 self.version = self.raw_header["version"] 

706 

707 # ndim 

708 ndim = self.raw_header["ndim"] 

709 if ndim in (5, 7): 

710 self.has_material_type_section = True 

711 ndim = 3 

712 # self.raw_header['elem_connectivity_unpacked'] = True 

713 if ndim == 4: 

714 ndim = 3 

715 # self.raw_header['elem_connectivity_unpacked'] = True 

716 if 5 < ndim < 8: 

717 ndim = 3 

718 self.has_rigid_road_surface = True 

719 if ndim in (8, 9): 

720 ndim = 3 

721 self.has_rigid_body_data = True 

722 if self.raw_header["ndim"] == 9: 

723 self.has_rigid_road_surface = True 

724 self.has_reduced_rigid_body_data = True 

725 if ndim not in (2, 3): 

726 raise RuntimeError(f"Invalid header entry ndim: {self.raw_header['ndim']}") 

727 

728 self.n_nodes = self.raw_header["numnp"] 

729 self.legacy_code_type = self.raw_header["icode"] 

730 self.n_global_vars = self.raw_header["nglbv"] 

731 

732 # it 

733 # - mass scaling 

734 # - node temperature 

735 # - node heat flux 

736 if get_digit(self.raw_header["it"], 1) == 1: 

737 self.has_node_mass_scaling = True 

738 it_first_digit = get_digit(self.raw_header["it"], 0) 

739 if it_first_digit == 1: 

740 self.has_node_temperatures = True 

741 elif it_first_digit == 2: 

742 self.has_node_temperatures = True 

743 self.has_node_heat_flux = True 

744 elif it_first_digit == 3: 

745 self.has_node_temperatures = True 

746 self.has_node_heat_flux = True 

747 self.has_node_temperature_layers = True 

748 

749 # iu iv ia 

750 self.has_node_displacement = self.raw_header["iu"] != 0 

751 self.has_node_velocity = self.raw_header["iv"] != 0 

752 self.has_node_acceleration = self.raw_header["ia"] != 0 

753 

754 # nel8 

755 self.n_solids = abs(self.raw_header["nel8"]) 

756 if self.raw_header["nel8"] < 0: 

757 self.has_solid_2_extra_nodes = True 

758 

759 # nummat8 

760 self.n_solid_materials = self.raw_header["nummat8"] 

761 

762 # numds 

763 self.has_shell_four_inplane_gauss_points = self.raw_header["numds"] < 0 

764 

765 # numst 

766 self.unused_numst = self.raw_header["numst"] 

767 

768 # nv3d 

769 self.n_solid_vars = self.raw_header["nv3d"] 

770 

771 # nel2 

772 self.n_beams = self.raw_header["nel2"] 

773 

774 # nummat2 

775 self.n_beam_materials = self.raw_header["nummat2"] 

776 

777 # nv1d 

778 self.n_beam_vars = self.raw_header["nv1d"] 

779 

780 # nel4 

781 self.n_shells = self.raw_header["nel4"] 

782 

783 # nummat4 

784 self.n_shell_materials = self.raw_header["nummat4"] 

785 

786 # nv2d 

787 self.n_shell_vars = self.raw_header["nv2d"] 

788 

789 # neiph 

790 self.n_solid_history_vars = self.raw_header["neiph"] 

791 

792 # neips 

793 self.n_shell_tshell_history_vars = self.raw_header["neips"] 

794 

795 # maxint 

796 maxint = self.raw_header["maxint"] 

797 if maxint > 0: 

798 self.n_shell_tshell_layers = maxint 

799 elif maxint <= -10000: 

800 self.has_element_deletion_data = True 

801 self.n_shell_tshell_layers = abs(maxint) - 10000 

802 elif maxint < 0: 

803 self.has_node_deletion_data = True 

804 self.n_shell_tshell_layers = abs(maxint) 

805 

806 # nmsph 

807 self.n_sph_nodes = self.raw_header["nmsph"] 

808 

809 # ngpsph 

810 self.n_sph_materials = self.raw_header["ngpsph"] 

811 

812 # narbs 

813 self.has_numbering_section = self.raw_header["narbs"] != 0 

814 self.n_numbering_section_words = self.raw_header["narbs"] 

815 

816 # nelt 

817 self.n_thick_shells = self.raw_header["nelt"] 

818 

819 # nummatth 

820 self.n_thick_shell_materials = self.raw_header["nummatt"] 

821 

822 # nv3dt 

823 self.n_thick_shell_vars = self.raw_header["nv3dt"] 

824 

825 # ioshl1 

826 if self.raw_header["ioshl1"] == 1000: 

827 self.has_shell_tshell_stress = True 

828 self.has_solid_stress = True 

829 elif self.raw_header["ioshl1"] == 999: 

830 self.has_solid_stress = True 

831 

832 # ioshl2 

833 if self.raw_header["ioshl2"] == 1000: 

834 self.has_shell_tshell_pstrain = True 

835 self.has_solid_pstrain = True 

836 elif self.raw_header["ioshl2"] == 999: 

837 self.has_solid_pstrain = True 

838 

839 # ioshl3 

840 self.has_shell_forces = self.raw_header["ioshl3"] == 1000 

841 

842 # ioshl4 

843 self.has_shell_extra_variables = self.raw_header["ioshl4"] == 1000 

844 

845 # ialemat 

846 self.n_ale_materials = self.raw_header["ialemat"] 

847 

848 # ncfdv1 

849 ncfdv1 = self.raw_header["ncfdv1"] 

850 if ncfdv1 == 67108864: 

851 self.has_multi_solver_data = True 

852 elif ncfdv1 != 0: 

853 self.has_cfd_data = True 

854 

855 # ncfdv2 

856 # unused 

857 

858 # nadapt 

859 self.n_adapted_element_pairs = self.raw_header["nadapt"] 

860 

861 # nmmat 

862 self.n_parts = self.raw_header["nmmat"] 

863 

864 # numfluid 

865 self.n_ale_fluid_groups = self.raw_header["numfluid"] 

866 

867 # inn 

868 self.has_invariant_numbering = self.raw_header["inn"] != 0 

869 

870 # nepfg 

871 npefg = self.raw_header["npefg"] 

872 self.n_airbags = npefg % 1000 

873 self.has_airbag_n_chambers = npefg // 1000 == 4 

874 

875 # nel48 

876 self.n_shells_8_nodes = self.raw_header["nel48"] 

877 

878 # idtdt 

879 self.has_node_temperature_gradient = get_digit(self.raw_header["idtdt"], 0) == 1 

880 self.has_node_residual_forces = get_digit(self.raw_header["idtdt"], 1) == 1 

881 self.has_node_residual_moments = self.has_node_residual_forces 

882 self.has_solid_shell_plastic_strain_tensor = get_digit(self.raw_header["idtdt"], 2) == 1 

883 self.has_solid_shell_thermal_strain_tensor = get_digit(self.raw_header["idtdt"], 3) == 1 

884 if self.raw_header["idtdt"] > 100: 

885 self.has_element_strain = get_digit(self.raw_header["idtdt"], 4) == 1 

886 else: 

887 # took a 1000 years to figure this out ... 

888 # Warning: 4 gaussian points are not considered 

889 if self.n_shell_vars > 0: 

890 if ( 

891 self.n_shell_vars 

892 - self.n_shell_tshell_layers 

893 * ( 

894 6 * self.has_shell_tshell_stress 

895 + self.has_shell_tshell_pstrain 

896 + self.n_shell_tshell_history_vars 

897 ) 

898 - 8 * self.has_shell_forces 

899 - 4 * self.has_shell_extra_variables 

900 ) > 1: 

901 self.has_element_strain = True 

902 # else: 

903 # self.has_element_strain = False 

904 elif self.n_thick_shell_vars > 0: 

905 if ( 

906 self.n_thick_shell_vars 

907 - self.n_shell_tshell_layers 

908 * ( 

909 6 * self.has_shell_tshell_stress 

910 + self.has_shell_tshell_pstrain 

911 + self.n_shell_tshell_history_vars 

912 ) 

913 ) > 1: 

914 self.has_element_strain = True 

915 # else: 

916 # self.has_element_strain = False 

917 # else: 

918 # self.has_element_strain = False 

919 

920 # internal energy 

921 # shell_vars_behind_layers = (self.n_shell_vars - 

922 # (self.n_shell_tshell_layers * ( 

923 # 6 * self.has_shell_tshell_stress + 

924 # self.has_shell_tshell_pstrain + 

925 # self.n_shell_tshell_history_vars) + 

926 # 8 * self.has_shell_forces 

927 # + 4 * self.has_shell_extra_variables)) 

928 

929 # if not self.has_element_strain: 

930 # if shell_vars_behind_layers > 1 and shell_vars_behind_layers < 6: 

931 # self.has_shell_internal_energy = True 

932 # else: 

933 # self.has_shell_internal_energy = False 

934 # elif self.has_element_strain: 

935 # if shell_vars_behind_layers > 12: 

936 # self.has_shell_internal_energy = True 

937 # else: 

938 # self.has_shell_internal_energy = False 

939 

940 # nel20 

941 if "nel20" in self.raw_header: 

942 self.n_solids_20_node_hexas = self.raw_header["nel20"] 

943 

944 # nt3d 

945 if "nt3d" in self.raw_header: 

946 self.n_solid_thermal_vars = self.raw_header["nt3d"] 

947 

948 # nel27 

949 if "nel27" in self.raw_header: 

950 self.n_solids_27_node_hexas = self.raw_header["nel27"] 

951 

952 # neipb 

953 if "neipb" in self.raw_header: 

954 self.n_beam_history_vars = self.raw_header["neipb"] 

955 

956 # nel21p 

957 if "nel21p" in self.raw_header: 

958 self.n_solids_21_node_pentas = self.raw_header["nel21p"] 

959 

960 # nel15t 

961 if "nel15t" in self.raw_header: 

962 self.n_solids_15_node_tetras = self.raw_header["nel15t"] 

963 

964 # soleng 

965 if "soleng" in self.raw_header: 

966 self.has_solid_internal_energy_density = self.raw_header["soleng"] 

967 

968 # nel20t 

969 if "nel20t" in self.raw_header: 

970 self.n_solids_20_node_tetras = self.raw_header["nel20t"] 

971 

972 # nel40p 

973 if "nel40p" in self.raw_header: 

974 self.n_solids_40_node_pentas = self.raw_header["nel40p"] 

975 

976 # nel64 

977 if "nel64" in self.raw_header: 

978 self.n_solids_64_node_hexas = self.raw_header["nel64"] 

979 

980 # quadr 

981 if "quadr" in self.raw_header: 

982 quadr = self.raw_header["quadr"] 

983 if quadr == 1: 

984 self.quadratic_elems_has_full_connectivity = True 

985 elif quadr == 2: 

986 self.quadratic_elems_has_full_connectivity = True 

987 self.quadratic_elems_has_data_at_integration_points = True 

988 

989 # cubic 

990 if "cubic" in self.raw_header: 

991 self.has_cubic_solids = self.raw_header["cubic"] != 0 

992 

993 # tsheng 

994 if "tsheng" in self.raw_header: 

995 self.has_thick_shell_energy_density = self.raw_header["tsheng"] != 0 

996 

997 # nbranch 

998 if "nbranch" in self.raw_header: 

999 self.n_post_branches = self.raw_header["nbranch"] 

1000 

1001 # penout 

1002 if "penout" in self.raw_header: 

1003 penout = self.raw_header["penout"] 

1004 if penout == 1: 

1005 self.has_node_max_contact_penetration_absolute = True 

1006 if penout == 2: 

1007 self.has_node_max_contact_penetration_absolute = True 

1008 self.has_node_max_contact_penetration_relative = True 

1009 

1010 # engout 

1011 if "engout" in self.raw_header: 

1012 self.has_node_contact_energy_density = self.raw_header["engout"] == 1 

1013 

1014 return self 

1015 

1016 @property 

1017 def has_femzip_indicator(self) -> bool: 

1018 """If the femzip indicator can be found in the header 

1019 

1020 Notes 

1021 ----- 

1022 Only use on raw files. 

1023 

1024 If the header displays a femzip indicator then the file 

1025 is femzipped. If you load the femzip file as such then 

1026 this indicator will not be set, since femzip itself 

1027 corrects the indicator again. 

1028 """ 

1029 if "nmmat" in self.raw_header: 

1030 return self.raw_header["nmmat"] == 76_893_465 

1031 return False 

1032 

1033 @property 

1034 def n_rigid_wall_vars(self) -> int: 

1035 """number of rigid wall vars 

1036 

1037 Notes 

1038 ----- 

1039 Depends on lsdyna version. 

1040 """ 

1041 return 4 if self.version >= 971 else 1 

1042 

1043 @property 

1044 def n_solid_layers(self) -> int: 

1045 """number of solid layers 

1046 

1047 Returns 

1048 ------- 

1049 n_solid_layers: int 

1050 """ 

1051 n_solid_base_vars = ( 

1052 6 * self.has_solid_stress + self.has_solid_pstrain + self.n_solid_history_vars 

1053 ) 

1054 

1055 return 8 if self.n_solid_vars // max(n_solid_base_vars, 1) >= 8 else 1 

1056 

1057 def read_words(self, bb: BinaryBuffer, words_to_read: dict, storage_dict: dict = None): 

1058 """Read several words described by a dict 

1059 

1060 Parameters 

1061 ---------- 

1062 bb: BinaryBuffer 

1063 words_to_read: dict 

1064 this dict describes the words to be read. One entry 

1065 must be a tuple of len two (byte position and dtype) 

1066 storage_dict: dict 

1067 in this dict the read words will be saved 

1068 

1069 Returns 

1070 ------- 

1071 storage_dict: dict 

1072 the storage dict given as arg or a new dict if none was given 

1073 """ 

1074 

1075 if storage_dict is None: 

1076 storage_dict = {} 

1077 

1078 for name, data in words_to_read.items(): 

1079 

1080 # check buffer length 

1081 if data[0] >= len(bb): 

1082 continue 

1083 

1084 # read data 

1085 if data[1] == self.itype: 

1086 storage_dict[name] = int(bb.read_number(data[0], data[1])) 

1087 elif data[1] == self.ftype: 

1088 storage_dict[name] = float(bb.read_number(data[0], data[1])) 

1089 elif data[1] == str: 

1090 try: 

1091 storage_dict[name] = bb.read_text(data[0], data[2]) 

1092 except UnicodeDecodeError: 

1093 storage_dict[name] = "" 

1094 

1095 else: 

1096 raise RuntimeError(f"Encountered unknown dtype {str(data[1])} during reading.") 

1097 

1098 return storage_dict 

1099 

1100 @staticmethod 

1101 def _determine_file_settings( 

1102 bb: Union[BinaryBuffer, None] = None 

1103 ) -> Tuple[int, Union[np.int32, np.int64], Union[np.float32, np.float64]]: 

1104 """Determine the precision of the file 

1105 

1106 Parameters 

1107 ---------- 

1108 bb: Union[BinaryBuffer, None] 

1109 binary buffer from the file 

1110 

1111 Returns 

1112 ------- 

1113 wordsize: int 

1114 size of each word in bytes 

1115 itype: np.dtype 

1116 type of integers 

1117 ftype: np.dtype 

1118 type of floats 

1119 """ 

1120 

1121 LOGGER.debug("_determine_file_settings") 

1122 

1123 word_size = 4 

1124 itype = np.int32 

1125 ftype = np.float32 

1126 

1127 # test file type flag (1=d3plot, 5=d3part, 11=d3eigv) 

1128 

1129 if isinstance(bb, BinaryBuffer): 

1130 

1131 # single precision 

1132 value = bb.read_number(44, np.int32) 

1133 if value > 1000: 

1134 value -= 1000 

1135 if value in ( 

1136 D3plotFiletype.D3PLOT.value, 

1137 D3plotFiletype.D3PART.value, 

1138 D3plotFiletype.D3EIGV.value, 

1139 ): 

1140 word_size = 4 

1141 itype = np.int32 

1142 ftype = np.float32 

1143 

1144 LOGGER.debug("wordsize=%d itype=%s ftype=%s", word_size, itype, ftype) 

1145 LOGGER.debug("_determine_file_settings end") 

1146 

1147 return word_size, itype, ftype 

1148 

1149 # double precision 

1150 value = bb.read_number(88, np.int64) 

1151 if value > 1000: 

1152 value -= 1000 

1153 if value in ( 

1154 D3plotFiletype.D3PLOT.value, 

1155 D3plotFiletype.D3PART.value, 

1156 D3plotFiletype.D3EIGV.value, 

1157 ): 

1158 word_size = 8 

1159 itype = np.int64 

1160 ftype = np.float64 

1161 

1162 LOGGER.debug("wordsize=%d itype=%s ftype=%s", word_size, itype, ftype) 

1163 LOGGER.debug("_determine_file_settings end") 

1164 

1165 return word_size, itype, ftype 

1166 

1167 raise RuntimeError(f"Unknown file type '{value}'.") 

1168 

1169 LOGGER.debug("wordsize=%d itype=%s ftype=%s", word_size, itype, ftype) 

1170 LOGGER.debug("_determine_file_settings end") 

1171 

1172 return word_size, itype, ftype 

1173 

1174 def compare(self, other: "D3plotHeader") -> Dict[str, Tuple[Any, Any]]: 

1175 """Compare two headers and get the differences 

1176 

1177 Parameters 

1178 ---------- 

1179 other: D3plotHeader 

1180 other d3plot header instance 

1181 

1182 Returns 

1183 ------- 

1184 differences: Dict[str, Tuple[Any, Any]] 

1185 The different entries of both headers in a dict 

1186 """ 

1187 assert isinstance(other, D3plotHeader) 

1188 

1189 differences = {} 

1190 names = {*self.raw_header.keys(), *other.raw_header.keys()} 

1191 for name in names: 

1192 value1 = self.raw_header[name] if name in self.raw_header else "missing" 

1193 value2 = other.raw_header[name] if name in self.raw_header else "missing" 

1194 if value1 != value2: 

1195 differences[name] = (value1, value2) 

1196 

1197 return differences