2. LS-DYNA input deck structure

An LS-DYNA input deck is not a single monolithic file in serious engineering workflows. It is a tree of keyword files linked by *INCLUDE statements, and that tree is exactly what a Python automation layer must understand before it can safely vary a parameter, swap a material, or generate a DOE matrix. Treating the deck as an opaque blob and performing blind text substitution is the most common source of silent, hard-to-diagnose model corruption in automated studies. This section establishes a precise mental model of the deck: how keyword files are structured, how includes resolve into a single solver input stream, and where the legitimate parameterization points are.

Keyword file anatomy

Every LS-DYNA keyword file is a sequence of keyword blocks. A block begins with a keyword line starting with *, optionally followed by a title line, then fixed-format or comma-separated data cards. The solver reads the deck as a flat stream after include expansion, so block order matters only where the solver requires it (for example, *CONTROL cards are position-independent, but *PART must precede references to it in some contexts, and *DEFINE_CURVE must precede its use by a *MAT or load card).

A minimal but realistic deck fragment looks like this:

*KEYWORD
*TITLE
B-pillar drop-tower baseline
*INCLUDE
../common/materials.k
*INCLUDE
../common/contacts.k
*INCLUDE_PATH_RELATIVE
../common
*PART
B-pillar shell
         1         1         1
*SECTION_SHELL
         1         2       1.2         5
0.0       0.0       0.0       0.0
*MAT_PIECEWISE_LINEAR_PLASTICITY
         1    7.85e-9     210.0       0.30
   250.0   0.0      0.0      0.0      0.0      0.0
*DEFINE_CURVE
         1         0       1.0       1.0
0.0      250.0
0.10     310.0
0.25     360.0
*END

Two details matter for automation. First, the *INCLUDE path is resolved relative to the including file's directory by default, not the working directory, unless *INCLUDE_PATH_RELATIVE or the -I command-line flag changes the search root. Second, the *END keyword terminates the deck; anything after it is ignored, which is a frequent trap when appending generated blocks.

Include hierarchy and resolution

Real decks nest includes several levels deep: a top-level main.k includes a model/ subtree, which includes materials/, contacts/, and loads/. The solver flattens this tree depth-first in the order the *INCLUDE lines appear. The figure below shows a representative hierarchy and the parameterization points that automation can safely target.

Deck include tree and automation parameterization points main.k top-level deck model/parts.k *PART, *SECTION model/materials.k *MAT_*, *DEFINE_CURVE model/loads.k *BOUNDARY, *LOAD steel_DP780.k MAT_024 + curve 101 alu_6061.k MAT_024 + curve 102 pulse_curves.k *DEFINE_CURVE 201 Safe parameterization points Thickness in *SECTION_SHELL (field 4) — numeric, per-part, no cross-references Yield stress / hardening table in *MAT_024 and its *DEFINE_CURVE ordinates Pulse amplitude and duration in *DEFINE_CURVE 201 (loads.k) Termination time in *CONTROL_TERMINATION — single scalar, deck-wide

The tree above is deliberately shallow, but the principle scales: automation should identify the owning file of each parameter and modify that file, rather than rewriting the flattened deck. This preserves the include structure, keeps diffs reviewable, and lets multiple DOE cases share unchanged subtrees.

Parameterization points and their risk classes

Not every numeric field is equally safe to automate. It helps to classify parameterization points by how many other cards reference them:

ParameterOwning keywordReference riskAutomation approach
Shell thickness*SECTION_SHELLLow — referenced only by *PART SIDDirect field replacement
Yield stress*MAT_024 / *DEFINE_CURVEMedium — curve LCID must stay consistentReplace ordinates, keep LCID
Material ID*PART MIDHigh — breaks all downstream referencesRemap IDs across all files
Contact thickness*CONTACT_* SST/MSTMedium — part set membershipEdit set, not raw IDs
Termination time*CONTROL_TERMINATIONLow — single scalarDirect field replacement

The high-risk row is the one that catches most beginners. Changing a *PART MID from 1 to 7 without updating the corresponding *MAT block and any *SET_PART or contact references produces a deck that runs but silently uses the wrong material — or aborts with "material 7 not found." A robust automation layer therefore treats ID remapping as a graph operation over the include tree, not a string replacement.

Building a deck model in Python

The practical first step is to parse the include tree into an in-memory representation. A lightweight approach uses a recursive resolver that records, for each keyword block, its source file and line range:

import re
from pathlib import Path

INCLUDE_RE = re.compile(r'^\*INCLUDE(?:_PATH_RELATIVE)?\s*$', re.I)

def resolve_deck(path, seen=None):
    """Return list of (file, line_no, text) for the flattened deck."""
    seen = seen or set()
    path = Path(path).resolve()
    if path in seen:
        raise RuntimeError(f"circular include: {path}")
    seen.add(path)
    out = []
    lines = path.read_text().splitlines()
    i = 0
    while i < len(lines):
        line = lines[i]
        if INCLUDE_RE.match(line.strip()):
            inc = lines[i + 1].strip()
            out.extend(resolve_deck(path.parent / inc, seen))
            i += 2
            continue
        out.append((path, i + 1, line))
        i += 1
    return out

With this flattened view, you can locate every *SECTION_SHELL block, read its thickness field, and write a modified copy into a case directory while leaving the original include tree untouched. The key discipline is to write to a new case directory and keep the source deck read-only; this makes each DOE case reproducible and diffable.

Practical rule: parameterize by editing the owning file in a copied case directory, never by appending override blocks after *END. LS-DYNA ignores everything after *END, so appended overrides silently do nothing.

Common failure symptoms and diagnosis

When an automated deck fails, the symptom usually points to the layer that broke. A "file not found" during include expansion means the relative path was resolved against the wrong root — check whether the case directory preserves the original relative layout. A "material ID not found" means an ID remap missed a reference in a sibling include. A model that runs but produces implausibly stiff or soft response usually indicates a curve LCID mismatch: the *MAT points to curve 101 but the edited file renumbered it to 201. A deck that terminates at time zero often has a malformed *CONTROL_TERMINATION field due to fixed-format column drift after a text substitution.

These symptoms are cheap to catch with a pre-run validation pass: parse the flattened deck, collect all defined IDs (materials, curves, parts, sets), collect all referenced IDs, and assert that every reference resolves. Running that check before submitting a batch of DOE cases turns hours of solver-time debugging into a sub-second Python assertion.

What to carry forward

The deck is a tree, not a file. Automation must resolve the include hierarchy, identify the owning file for each parameter, classify parameters by reference risk, and write modified copies into isolated case directories. With that model in place, the next layer — generating DOE matrices and driving the solver — becomes a matter of looping over case directories rather than wrestling with text substitution inside a monolithic deck.