2. Binary and ASCII result file structures

Before writing a single line of parsing code, you need a precise mental model of what each solver writes to disk. LS-DYNA and PAM-CRASH both produce large, heterogeneous result sets, but they organize them very differently. LS-DYNA splits output across a family of files — d3plot for full-field state data, binout* for time history and scalar channels, and d3hsp/messag for metadata and diagnostics. PAM-CRASH concentrates most of its output into the .erfh (extended result file, binary) and .dsy (time-history / curve) files. Understanding the internal layout of these containers determines which Python library you reach for, how much memory a post-processing job will consume, and whether you can stream results or must load them whole.

2.1 The d3plot family: state data as a sequence of states

A d3plot file is not a single monolithic blob. It is a state-based container: the model geometry (nodal coordinates, element connectivity, part definitions) is written once in a header block, followed by a sequence of states. Each state corresponds to one output time and stores the current nodal displacements, velocities, accelerations, element stresses/strains, and — if requested — shell resultants, solid history variables, and rigid-body data. The number of states equals the number of output intervals you requested via *DATABASE_BINARY_D3PLOT (controlled by DT or NPLTC).

Two structural details matter for parsing. First, the file is word-addressed: LS-DYNA writes 4-byte words, and the header contains control words that tell you how many words each state occupies. Second, when a simulation is large, LS-DYNA splits the output into d3plot, d3plot01, d3plot02, … — a family of files that must be read as one logical stream. The d3plot header records the total number of parts, nodes, and elements, plus a "state size" that lets a reader skip directly to any state without scanning the whole file.

The practical consequence: random access to a single state is cheap, but reading all states for a large model is I/O-bound. In Python, the lasso-python library (from the DYNAmore/Ansys ecosystem) exposes d3plot reading through a compiled reader that returns NumPy arrays per state. A minimal pattern looks like this:

import lasso
from lasso.dyna import D3plot, ArrayType

d3 = D3plot("d3plot")
# geometry is available once
node_ids = d3.arrays[ArrayType.node_ids]
# per-state arrays are indexed by state
disp = d3.arrays[ArrayType.node_displacement]   # shape (n_states, n_nodes, 3)
times = d3.arrays[ArrayType.global_timesteps]
print(disp.shape, times[:5])

Note the shape convention: (n_states, n_entities, n_components). This is the single most important fact for vectorized post-processing — you can slice disp[-1] for the final deformed shape or disp[:, node_index, :] for a nodal time history without any Python loop.

2.2 binout: time history and scalar channels

Where d3plot stores full-field state data, binout stores reduced time-history data: nodal accelerations at selected nodes, section forces, rigid-wall contact forces, energy summaries, and material-level scalars. The binout file is itself a container of named channels (also called "bins"), each with its own time vector and data arrays. Typical channel names include nodout, elout, rcforc, glstat, matsum, and secforc.

Because binout is a self-describing binary format, you can enumerate channels before reading any data. The lasso reader and the older qd-ldyna/binout readers both support this:

from lasso.dyna import Binout

b = Binout("binout")
print(b.read())                 # list available channel names
t = b.read("glstat", "time")
ke = b.read("glstat", "kinetic_energy")
ie = b.read("glstat", "internal_energy")

The key engineering point is that binout channels are not guaranteed to share a time base. glstat may be written every 100 steps while nodout is written every 10. Any comparison — for example, plotting kinetic energy against a nodal acceleration peak — requires explicit interpolation onto a common time vector. Assuming aligned time arrays is a common and silent source of error.

Tip: Always inspect b.read() output and the length of each channel's time array before merging. If lengths differ, resample with numpy.interp onto the coarser or finer grid, and document which grid you chose.

2.3 PAM-CRASH .erfh and .dsy

PAM-CRASH takes a more consolidated approach. The .erfh file is the primary binary result container: it holds the model metadata (nodes, elements, materials, part sets) together with the state data, analogous to a combined d3plot plus model definition. The .dsy file holds time-history curves — sensor outputs, nodal quantities, section forces — in a compact binary layout. In many workflows the .dsy is the PAM-CRASH equivalent of binout, while .erfh plays the role of d3plot.

PAM-CRASH also supports an ASCII variant of the time-history output (often with a .dsy or .TH extension depending on the version and the DYNA/PAM output switch). ASCII is human-readable and easy to parse with pandas.read_csv, but it is far larger and slower for long simulations. The binary .erfh is the format you must handle for production-sized models.

Python access to PAM-CRASH results is less standardized than for LS-DYNA. Options include the vendor's own Python API (ESI/Ansys PAM-CRASH scripting), the pyPAM-style community readers, or — when only time histories are needed — direct parsing of the ASCII .dsy. A robust strategy is to isolate format handling behind a thin adapter so that the rest of your analysis code is format-agnostic:

class ResultSource:
    def states(self): ...          # yields (time, field_arrays)
    def history(self, channel): ... # returns (time, values)
    def metadata(self): ...         # nodes, elements, parts

class D3plotSource(ResultSource): ...
class ErfhSource(ResultSource): ...

This adapter pattern pays off immediately when you must run the same contour or curve-extraction script against both solvers.

2.4 Metadata: the part that makes results interpretable

Raw arrays are meaningless without the model metadata that maps indices to physical entities. Every one of these formats carries, at minimum: node IDs and coordinates, element connectivity and type (shell, solid, beam), part IDs and their element membership, and the set definitions used for output requests. In d3plot this lives in the header and the first state; in binout it is implicit in the channel naming; in .erfh it is embedded in the model block.

When you build a post-processing pipeline, load metadata once and keep it in a lightweight structure (a dict of NumPy arrays or a small dataclass). Re-reading geometry for every state is the most common performance mistake in LS-DYNA/PAM-CRASH scripting.

Result File Structures: LS-DYNA vs. PAM-CRASH LS-DYNA d3plot (state data) • Header: nodes, elements, parts • State 1 … State N (per output time) • Word-addressed, splittable (d3plot01…) • Arrays: (n_states, n_entities, n_comp) binout (time history) • Named channels: glstat, nodout, rcforc, matsum, secforc … • Independent time bases per channel • Self-describing binary container d3hsp / messag (metadata) • Model summary, part table • Energy balance, warnings • ASCII, human-readable • Useful for sanity checks PAM-CRASH .erfh (extended result file) • Model metadata + state data • Nodes, elements, materials, sets • Binary, consolidated container • Analogous to d3plot + model def • Vendor Python API / pyPAM readers .dsy (time history) • Sensor / nodal / section curves • Compact binary layout • ASCII variant for small cases Adapter pattern ResultSource → states() / history() D3plotSource, ErfhSource Format-agnostic analysis code Both formats: load metadata once, stream states, align time bases explicitly

2.5 Choosing a reading strategy

The format differences translate directly into engineering decisions. For LS-DYNA, the split between d3plot and binout means you should decide early whether your analysis needs full-field data (contours, deformed shapes, element-level stress) or only reduced histories (energy, force, acceleration). Reading a multi-gigabyte d3plot just to extract one nodal acceleration curve is wasteful — that curve is almost certainly already in binout as a nodout channel.

For PAM-CRASH, the consolidated .erfh means a single reader call can return both metadata and states, but it also means the file is larger and the reader must be chosen carefully. If your workflow only needs time histories, parse the .dsy and avoid loading .erfh entirely.

Common pitfall: Assuming that state index i in d3plot corresponds to the same physical time as row i in a binout channel. They are written on independent schedules. Always join on the actual time arrays, not on positional index.

With these structures understood, the next step is to build a reader layer that normalizes both solvers into a common in-memory representation — a topic that directly enables the contouring, curve extraction, and cross-solver comparison work in later chapters.