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
« 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
4import numpy as np
5import rich
7from ..io.binary_buffer import BinaryBuffer
8from ..logging import get_logger
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
14LOGGER = get_logger(__file__)
17def get_digit(number: int, i_digit: int) -> int:
18 """Get a digit from a number
20 Parameters
21 ----------
22 number: int
23 number to get digit from
24 i_digit: int
25 index of the digit
27 Returns
28 -------
29 digit: int
30 digit or 0 if i_digit is too large
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 = []
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)
48 # do the thing
49 _get_digit_recursive(number)
51 # revert list from smallest to biggest
52 digit_list = digit_list[::-1]
54 return digit_list[i_digit] if i_digit < len(digit_list) else 0
57class D3plotFiletype(enum.Enum):
58 """Enum for the filetype of a D3plot"""
60 D3PLOT = 1
61 D3PART = 5
62 D3EIGV = 11
63 INTFOR = 4
66def d3plot_filetype_from_integer(value: int) -> D3plotFiletype:
67 """Get a D3plotFiletype object from an integer
69 Parameters
70 ----------
71 value: int
72 integer value representing the filetype
74 Returns
75 -------
76 filetype: D3plotFiletype
77 d3plot filetype object
79 Raises
80 ------
81 RuntimeError if invalid value.
82 """
84 valid_entries = {
85 entry.value: entry
86 for entry in D3plotFiletype.__members__.values()
87 if entry.value != 4 # no intfor
88 }
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)
99 return valid_entries[value]
102# pylint: disable = too-many-instance-attributes
103class D3plotHeader:
104 """Class for reading only header information of a d3plot
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 """
313 # meta
314 filepath: str = ""
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
324 # header
325 title: str = ""
326 title2: str = ""
327 runtime: int = 0
328 filetype: D3plotFiletype = D3plotFiletype.D3PLOT
330 source_version: int = 0
331 release_version: str = ""
332 version: float = 0.0
333 extra_long_header: bool = False
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()
350 # parts
351 n_parts: int = 0
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
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
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
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
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
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
422 # airbags
423 n_airbags: int = 0
424 has_airbag_n_chambers: bool = False
426 # rigid roads
427 has_rigid_road_surface: bool = False
429 # rigid bodies
430 has_rigid_body_data: bool = False
431 has_reduced_rigid_body_data: bool = False
433 # sph
434 n_sph_nodes: int = 0
435 n_sph_materials: int = 0
437 # ale
438 n_ale_materials: int = 0
439 n_ale_fluid_groups: int = 0
441 # cfd
442 has_cfd_data: bool = False
444 # multi-solver
445 has_multi_solver_data: bool = False
446 cfd_extra_data: int = 0
448 # historical artifacts
449 legacy_code_type: int = 6
450 unused_numst: int = 0
452 def __init__(self, filepath: Union[str, BinaryBuffer, None] = None):
453 """Create a D3plotHeader instance
455 Parameters
456 ----------
457 filepath: Union[str, BinaryBuffer, None]
458 path to a d3plot file or a buffer holding d3plot memory
460 Returns
461 -------
462 header: D3plotHeader
463 d3plot header instance
465 Examples
466 --------
467 Create an empty header file
469 >>> header = D3plotHeader()
471 Now load only the header of a d3plot.
473 >>> header.load_file("path/to/d3plot")
475 Or we can do the above together.
477 >>> header = D3plotHeader("path/to/d3plot")
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 """
486 if filepath is not None:
487 self.load_file(filepath)
489 def print(self) -> None:
490 """Print the header"""
491 rich.print(self.__dict__)
493 def _read_file_buffer(self, filepath: str) -> BinaryBuffer:
494 """Reads a d3plots header
496 Parameters
497 ----------
498 filepath: str
499 path to d3plot
501 Returns
502 -------
503 bb: BinaryBuffer
504 buffer holding the exact header data in binary form
505 """
507 LOGGER.debug("_read_file_buffer start")
508 LOGGER.debug("filepath: %s", filepath)
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)
515 # check if single or double
516 self.wordsize, self.itype, self.ftype = self._determine_file_settings(bb)
518 # Oops, seems other wordsize is used
519 if self.wordsize != D3plotHeader.wordsize:
520 bb = BinaryBuffer(filepath, n_words_header * self.wordsize)
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)
527 LOGGER.debug("_read_file_buffer end")
529 return bb
531 def _determine_n_bytes(self, bb: BinaryBuffer, wordsize: int) -> int:
532 """Determines how many bytes the header has
534 Returns
535 -------
536 size: int
537 size of the header in bytes
538 """
540 LOGGER.debug("_determine_n_bytes start")
542 n_base_words = 64
543 min_n_bytes = n_base_words * wordsize
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)))
549 n_extra_header_words = int(bb.read_number(57 * self.wordsize, self.itype))
551 LOGGER.debug("_determine_n_bytes end")
553 return (n_base_words + n_extra_header_words) * wordsize
555 def load_file(self, file: Union[str, BinaryBuffer]) -> "D3plotHeader":
556 """Load d3plot header from a d3plot file
558 Parameters
559 ----------
560 file: Union[str, BinaryBuffer]
561 path to d3plot or `BinaryBuffer` holding memory of d3plot
563 Returns
564 -------
565 self: D3plotHeader
566 returning self on success
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.
573 Examples
574 --------
575 >>> header = D3plotHeader().load_file("path/to/d3plot")
576 >>> header.n_shells
577 19684
578 """
580 # pylint: disable = too-many-locals, too-many-branches, too-many-statements
582 LOGGER.debug("_load_file start")
583 LOGGER.debug("file: %s", file)
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)
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)
598 LOGGER.debug("n_header_bytes: %d", self.n_header_bytes)
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 }
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 }
672 # read header for real
673 self.raw_header = self.read_words(bb, header_words)
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()
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]
690 self.title = self.raw_header["title"].strip()
691 self.runtime = self.raw_header["runtime"]
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
701 self.filetype = d3plot_filetype_from_integer(filetype)
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"]
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']}")
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"]
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
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
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
759 # nummat8
760 self.n_solid_materials = self.raw_header["nummat8"]
762 # numds
763 self.has_shell_four_inplane_gauss_points = self.raw_header["numds"] < 0
765 # numst
766 self.unused_numst = self.raw_header["numst"]
768 # nv3d
769 self.n_solid_vars = self.raw_header["nv3d"]
771 # nel2
772 self.n_beams = self.raw_header["nel2"]
774 # nummat2
775 self.n_beam_materials = self.raw_header["nummat2"]
777 # nv1d
778 self.n_beam_vars = self.raw_header["nv1d"]
780 # nel4
781 self.n_shells = self.raw_header["nel4"]
783 # nummat4
784 self.n_shell_materials = self.raw_header["nummat4"]
786 # nv2d
787 self.n_shell_vars = self.raw_header["nv2d"]
789 # neiph
790 self.n_solid_history_vars = self.raw_header["neiph"]
792 # neips
793 self.n_shell_tshell_history_vars = self.raw_header["neips"]
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)
806 # nmsph
807 self.n_sph_nodes = self.raw_header["nmsph"]
809 # ngpsph
810 self.n_sph_materials = self.raw_header["ngpsph"]
812 # narbs
813 self.has_numbering_section = self.raw_header["narbs"] != 0
814 self.n_numbering_section_words = self.raw_header["narbs"]
816 # nelt
817 self.n_thick_shells = self.raw_header["nelt"]
819 # nummatth
820 self.n_thick_shell_materials = self.raw_header["nummatt"]
822 # nv3dt
823 self.n_thick_shell_vars = self.raw_header["nv3dt"]
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
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
839 # ioshl3
840 self.has_shell_forces = self.raw_header["ioshl3"] == 1000
842 # ioshl4
843 self.has_shell_extra_variables = self.raw_header["ioshl4"] == 1000
845 # ialemat
846 self.n_ale_materials = self.raw_header["ialemat"]
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
855 # ncfdv2
856 # unused
858 # nadapt
859 self.n_adapted_element_pairs = self.raw_header["nadapt"]
861 # nmmat
862 self.n_parts = self.raw_header["nmmat"]
864 # numfluid
865 self.n_ale_fluid_groups = self.raw_header["numfluid"]
867 # inn
868 self.has_invariant_numbering = self.raw_header["inn"] != 0
870 # nepfg
871 npefg = self.raw_header["npefg"]
872 self.n_airbags = npefg % 1000
873 self.has_airbag_n_chambers = npefg // 1000 == 4
875 # nel48
876 self.n_shells_8_nodes = self.raw_header["nel48"]
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
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))
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
940 # nel20
941 if "nel20" in self.raw_header:
942 self.n_solids_20_node_hexas = self.raw_header["nel20"]
944 # nt3d
945 if "nt3d" in self.raw_header:
946 self.n_solid_thermal_vars = self.raw_header["nt3d"]
948 # nel27
949 if "nel27" in self.raw_header:
950 self.n_solids_27_node_hexas = self.raw_header["nel27"]
952 # neipb
953 if "neipb" in self.raw_header:
954 self.n_beam_history_vars = self.raw_header["neipb"]
956 # nel21p
957 if "nel21p" in self.raw_header:
958 self.n_solids_21_node_pentas = self.raw_header["nel21p"]
960 # nel15t
961 if "nel15t" in self.raw_header:
962 self.n_solids_15_node_tetras = self.raw_header["nel15t"]
964 # soleng
965 if "soleng" in self.raw_header:
966 self.has_solid_internal_energy_density = self.raw_header["soleng"]
968 # nel20t
969 if "nel20t" in self.raw_header:
970 self.n_solids_20_node_tetras = self.raw_header["nel20t"]
972 # nel40p
973 if "nel40p" in self.raw_header:
974 self.n_solids_40_node_pentas = self.raw_header["nel40p"]
976 # nel64
977 if "nel64" in self.raw_header:
978 self.n_solids_64_node_hexas = self.raw_header["nel64"]
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
989 # cubic
990 if "cubic" in self.raw_header:
991 self.has_cubic_solids = self.raw_header["cubic"] != 0
993 # tsheng
994 if "tsheng" in self.raw_header:
995 self.has_thick_shell_energy_density = self.raw_header["tsheng"] != 0
997 # nbranch
998 if "nbranch" in self.raw_header:
999 self.n_post_branches = self.raw_header["nbranch"]
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
1010 # engout
1011 if "engout" in self.raw_header:
1012 self.has_node_contact_energy_density = self.raw_header["engout"] == 1
1014 return self
1016 @property
1017 def has_femzip_indicator(self) -> bool:
1018 """If the femzip indicator can be found in the header
1020 Notes
1021 -----
1022 Only use on raw files.
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
1033 @property
1034 def n_rigid_wall_vars(self) -> int:
1035 """number of rigid wall vars
1037 Notes
1038 -----
1039 Depends on lsdyna version.
1040 """
1041 return 4 if self.version >= 971 else 1
1043 @property
1044 def n_solid_layers(self) -> int:
1045 """number of solid layers
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 )
1055 return 8 if self.n_solid_vars // max(n_solid_base_vars, 1) >= 8 else 1
1057 def read_words(self, bb: BinaryBuffer, words_to_read: dict, storage_dict: dict = None):
1058 """Read several words described by a dict
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
1069 Returns
1070 -------
1071 storage_dict: dict
1072 the storage dict given as arg or a new dict if none was given
1073 """
1075 if storage_dict is None:
1076 storage_dict = {}
1078 for name, data in words_to_read.items():
1080 # check buffer length
1081 if data[0] >= len(bb):
1082 continue
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] = ""
1095 else:
1096 raise RuntimeError(f"Encountered unknown dtype {str(data[1])} during reading.")
1098 return storage_dict
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
1106 Parameters
1107 ----------
1108 bb: Union[BinaryBuffer, None]
1109 binary buffer from the file
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 """
1121 LOGGER.debug("_determine_file_settings")
1123 word_size = 4
1124 itype = np.int32
1125 ftype = np.float32
1127 # test file type flag (1=d3plot, 5=d3part, 11=d3eigv)
1129 if isinstance(bb, BinaryBuffer):
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
1144 LOGGER.debug("wordsize=%d itype=%s ftype=%s", word_size, itype, ftype)
1145 LOGGER.debug("_determine_file_settings end")
1147 return word_size, itype, ftype
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
1162 LOGGER.debug("wordsize=%d itype=%s ftype=%s", word_size, itype, ftype)
1163 LOGGER.debug("_determine_file_settings end")
1165 return word_size, itype, ftype
1167 raise RuntimeError(f"Unknown file type '{value}'.")
1169 LOGGER.debug("wordsize=%d itype=%s ftype=%s", word_size, itype, ftype)
1170 LOGGER.debug("_determine_file_settings end")
1172 return word_size, itype, ftype
1174 def compare(self, other: "D3plotHeader") -> Dict[str, Tuple[Any, Any]]:
1175 """Compare two headers and get the differences
1177 Parameters
1178 ----------
1179 other: D3plotHeader
1180 other d3plot header instance
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)
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)
1197 return differences