4. Extracting time-history data from d3plot and binout files
LS-DYNA writes two fundamentally different families of result files, and understanding their binary layouts is the key to extracting time-history data reliably. The d3plot family stores full-field state data — nodal coordinates, displacements, velocities, stresses, and element results — at discrete time states. The binout file stores scalar and vector time-history channels produced by *DATABASE_BINARY_* and *DATABASE_HISTORY_* cards. Both are binary, both are endian-sensitive, and both require a reader that respects their internal record structure rather than treating them as flat byte streams.
Binary Format Structure
A d3plot file begins with a control block of 64-bit floating-point words followed by integer control words. The first word is the code identifier, the second encodes the number of state words per time step, and subsequent words carry the node count, solid element count, beam count, shell count, and the number of time states. After the control block, the geometry records appear: nodal coordinates, element connectivity, and material identifiers. Then come the state records, each containing displacement, velocity, acceleration, and stress arrays for every node and element. The critical detail is that d3plot is written in the native endianness of the machine that ran the simulation, and the word size is 4 bytes for single precision or 8 bytes for double precision. A reader must detect both before interpreting any offset.
The binout file is organized as a directory tree. A header block identifies the file as a binout container, followed by a sequence of records. Each record has a type identifier, a name string, and a payload. The payload for a time-history channel contains a time array and one or more data arrays. The binout format is self-describing: the reader can enumerate record names and their dimensions without prior knowledge of the simulation deck. This makes it the preferred source for scalar history data such as nodal acceleration, interface force, or energy summaries.
d3plot is state-indexed and field-oriented; binout is channel-indexed and time-oriented. Choose the file based on whether you need spatial fields or scalar histories.
Python Reader Libraries
Three libraries dominate practical Python workflows. lasso-python (LS-DYNA API) provides a high-level interface to both d3plot and binout, with methods such as D3plot and Binout that expose time states and channel names directly. qd-ldyna is a lighter reader focused on d3plot and binout parsing with explicit control over word size and endianness. pyD3plot is a minimal, dependency-free reader useful for teaching and for environments where installing compiled packages is restricted.
For binout, the lasso-python Binout class is the most convenient. It reads the directory tree and returns a dictionary-like object where each key is a channel name and each value is a NumPy array. For d3plot, the D3plot class returns a sequence of state objects, each with node_displacement, node_velocity, and element_stress arrays. The following example reads a binout channel and a d3plot state:
from lasso.dyna import Binout, D3plot
import numpy as np
# Read binout time-history channels
b = Binout("run01/binout")
time = b.read("nodout", "time")
accel_x = b.read("nodout", "x_acceleration")
print("channels:", b.get_child_names("nodout"))
# Read d3plot state data
d = D3plot("run01/d3plot")
state = d.get_state(0)
disp = state.node_displacement # shape (n_nodes, 3)
print("nodes:", disp.shape[0], "states:", d.get_number_of_states())
When using qd-ldyna, the equivalent call is Binout("run01/binout").read("nodout"), which returns a nested dictionary. The advantage of qd-ldyna is that it exposes the raw record offsets, which is useful when a file is truncated or when the endianness flag is ambiguous.
Endianness, Word Size, and Failure Symptoms
The most common extraction failure is reading a file with the wrong endianness. Symptoms include absurdly large node counts, negative time values, or a struct.error when unpacking. The fix is to detect the endianness from the first word: if the code identifier is not a recognized value, swap the byte order. Word size is equally important. A double-precision d3plot read as single precision produces displacement values that are roughly half the correct magnitude and a state count that is twice the true value.
A second failure mode is reading a binout channel that does not exist. The lasso-python reader raises a KeyError; the correct approach is to call get_child_names first and verify the channel name. A third failure mode is time-array mismatch: the binout time array and the d3plot state times are not guaranteed to align, because binout channels can be written at a different frequency than d3plot states. Always interpolate or resample before comparing the two.
Practical Extraction Workflow
A reproducible extraction workflow begins with file validation. Check that the d3plot and binout files exist and are non-zero in size. Then open the binout file and enumerate channel names. For a typical crash simulation, the relevant channels are nodout for nodal acceleration, elout for element stress and strain, glstat for global energy, and rcforc for contact interface forces. Read the time array once and reuse it for all channels.
Next, open the d3plot file and read the state count. If the state count is zero, the file is either truncated or the endianness is wrong. Compare the d3plot state times with the binout time array. If they differ, build a common time base using numpy.interp for scalar channels and linear interpolation for field data. Finally, write the extracted arrays to a structured format such as .npz or .csv for downstream analysis.
Common Pitfalls and Troubleshooting
| Symptom | Likely cause | Corrective action |
|---|---|---|
| Node count is negative or huge | Wrong endianness | Swap byte order in the reader or use qd-ldyna auto-detection |
| Displacement magnitude roughly half | Double-precision file read as single | Set word size to 8 bytes |
KeyError on channel name |
Channel not written by the deck | Call get_child_names and verify the *DATABASE_HISTORY card |
| Time arrays do not align | Different output frequencies | Resample to a common time base with numpy.interp |
| Truncated final state | Simulation terminated abnormally | Read states up to get_number_of_states() - 1 and check the messag file |
For large models, memory is the limiting factor. A d3plot with one million nodes and one thousand states can exceed several gigabytes if all states are loaded at once. The practical approach is to iterate over states and extract only the nodes or elements of interest, using the connectivity array to map global node IDs to local indices. The lasso-python reader supports partial reads through the get_state method, which loads one state at a time. For binout, the entire channel is typically small enough to load into memory, but for very long simulations the time array can be read in slices using the record offset information exposed by qd-ldyna.
Once the data is extracted, the analysis phase begins. Time-history data from binout is used for signal processing: filtering, peak detection, and integration to velocity or displacement. Field data from d3plot is used for spatial visualization and for extracting history at specific nodes by indexing the state arrays. Keeping the two sources separate but aligned on a common time base is the foundation for any reliable crash simulation post-processing pipeline.