4. Simulation data management and feature engineering

An active learning campaign is only as good as the design matrix that feeds it. LS-DYNA and PAM-CRASH produce rich, heterogeneous output — nodal displacements, section forces, accelerations, energies — but a surrogate model or acquisition function cannot consume raw d3plot or binout files. This chapter covers the pipeline that turns a directory of run decks into a clean, aligned, scalarized feature table: parsing solver output, aligning curves onto a common time base, reducing time histories to informative scalars, and assembling a design matrix when the underlying decks differ in mesh, units, and output requests.

Parsing LS-DYNA and PAM-CRASH Output

LS-DYNA writes binary state data to d3plot and scalar/curve data to binout (or ASCII nodout, elout, glstat, matsum). The lsda/lspp Python APIs and the open-source lasso-python library read these directly. A typical extraction reads a nodal acceleration history and a section force:

import lasso
d3plot = lasso.dyna.D3plot("run_042/d3plot")
# node 1057, component 1 (X), all states
t = d3plot.arrays[lasso.dyna.ArrayType.GLOBAL_TIME]
a_x = d3plot.arrays[lasso.dyna.ArrayType.NODE_ACCELERATION][:, 1057, 0]

PAM-CRASH stores results in DSY binary and TH time-history files, read via the pyPAM wrapper or the pamcrash2lsda converter. The key discipline is identical: never parse by screen-scraping the ASCII listing; bind to the binary reader so that entity IDs and component ordering are stable across runs.

Entity IDs are not guaranteed to be contiguous or ordered the same way across decks. Always resolve curves by explicit node/element ID and component index, not by array position. A silent off-by-one here corrupts every downstream feature.

Curve Alignment

Different runs terminate at different physical times, use different output intervals (DT in *CONTROL_TERMINATION), and may have different start offsets. Before scalarization, every history must be resampled onto a common time base. Two conventions matter:

  • Reference time base: define t_ref = linspace(0, t_end, N) with N chosen so the sampling interval is at least as fine as the coarsest run. For crash pulses, N = 2000 over 120 ms gives 0.06 ms resolution, adequate for 300–500 Hz content.
  • Interpolation: use linear interpolation for forces and displacements; use zero-order hold only for discrete state flags. Do not extrapolate — pad with the last valid value and record a truncated flag.

Alignment also means normalizing the time origin. If a run has a 2 ms pre-trigger, shift so that t = 0 corresponds to the first nonzero acceleration. Record the shift as a feature; it often correlates with trigger timing variation.

Scalarization of Time Histories

A surrogate cannot regress on 2000-point curves. Scalarization compresses each history into a small vector of physically meaningful descriptors. For a crash pulse a(t), the standard set is:

FeatureDefinitionTypical use
Peakmax|a(t)|Injury/limit constraint
Time to peakargmax|a(t)|Pulse shape sensitivity
Pulse durationwidth above 10% of peakEnergy absorption window
Impulse∫a(t)dtMomentum change
Rise ratemax da/dtStiffness indicator
Spectral centroidfirst moment of |FFT(a)|Frequency content

For section forces and energies, peak, final value, and integral are usually sufficient. The choice of scalar set is itself a design decision: too few features and the surrogate underfits; too many and the design matrix becomes ill-conditioned. A practical rule is 5–10 scalars per response channel, chosen so that pairwise correlation stays below 0.9.

Compute scalars on the aligned time base, not the raw one. Peak and impulse are sensitive to sampling; a run with coarse DT will report a lower peak than an otherwise identical fine run.

Building the Design Matrix from Heterogeneous Decks

The hardest part of a campaign is that run decks are not identical. Some use different mesh densities, some add a reinforcement, some change units from mm-tonne-s to m-kg-s. The design matrix must encode both the design variables (what you varied) and the context (how the deck differed), otherwise the surrogate will attribute mesh-induced differences to a design variable.

A robust construction proceeds in four steps:

  1. Canonicalize units. Convert all inputs and outputs to a single system (SI or mm-tonne-s). Store the conversion factor per run so results can be reported back in native units.
  2. Extract design variables. Parse the parameterized keywords (*PARAMETER in LS-DYNA, #include substitutions in PAM-CRASH) to recover the actual values used, not the nominal ones.
  3. Add context columns. Mesh size, element formulation, output interval, and solver version are categorical or numeric context features. One-hot encode categorical ones.
  4. Assemble and validate. Concatenate into a matrix X with one row per run and one column per feature. Check for constant columns, NaNs, and rank deficiency before handing off.
# design matrix assembly (schematic)
X = pd.concat([design_vars, context, scalars], axis=1)
X = X.loc[:, X.nunique() > 1]          # drop constant columns
X = X.fillna(X.median())                # explicit, logged imputation
assert np.linalg.matrix_rank(X) == X.shape[1], "rank deficient"

The figure below shows the full pipeline from heterogeneous decks to a validated design matrix, with the alignment and scalarization stages highlighted as the points where most data-quality failures originate.

From Heterogeneous Run Decks to Design Matrix Run Decks d3plot / binout DSY / TH varying mesh, units varying DT Parse lasso-python pyPAM resolve by ID + component Align t_ref = linspace linear interp zero-shift origin truncation flag Scalarize peak, t_peak impulse, rise spectral centroid 5–10 per channel Assemble Design Matrix X (one row per run) design variables | context columns (mesh, units, DT) | scalarized responses Validate: drop constants, impute NaNs, check rank reject if rank deficient or correlation > 0.9

Practical Failure Modes and Checks

Three failure modes dominate real campaigns. First, silent unit mismatch: a run in m-kg-s reports accelerations 1000× smaller than one in mm-tonne-s, and the surrogate learns a spurious unit effect. Guard by asserting the expected magnitude range per channel. Second, misaligned entity IDs: a remeshed run renumbers nodes, so node 1057 in run A is not node 1057 in run B. Guard by resolving through a persistent part/node label map, not raw index. Third, truncated histories: a run that terminated early produces a short curve; after alignment it is padded, and its impulse is underestimated. Guard by flagging any run whose t_end is below 95% of the nominal termination time and excluding it from scalar-based fitting until re-run.

A design matrix is trustworthy only when every column has a documented provenance: which keyword, which entity ID, which time base, and which scalar definition produced it. Store this as a machine-readable schema alongside X so that later runs can be appended without re-deriving the pipeline.

With a validated design matrix in hand, the active learning loop can proceed: fit an initial surrogate, compute an acquisition function, select the next deck to run, and append its row. The quality of that loop is bounded by the quality of the matrix built here — alignment and scalarization are not preprocessing chores but the foundation of every prediction the campaign will make.