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.
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:
| Parameter | Owning keyword | Reference risk | Automation approach |
|---|---|---|---|
| Shell thickness | *SECTION_SHELL | Low — referenced only by *PART SID | Direct field replacement |
| Yield stress | *MAT_024 / *DEFINE_CURVE | Medium — curve LCID must stay consistent | Replace ordinates, keep LCID |
| Material ID | *PART MID | High — breaks all downstream references | Remap IDs across all files |
| Contact thickness | *CONTACT_* SST/MST | Medium — part set membership | Edit set, not raw IDs |
| Termination time | *CONTROL_TERMINATION | Low — single scalar | Direct 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.
*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.