4. LS-DYNA input deck structure and parameterization

An LS-DYNA input deck is not a single monolithic file in most real projects. It is a tree of keyword files linked by *INCLUDE statements, and the way that tree is organized determines how easily a batch study can be automated. Before writing a single line of Python that sweeps a parameter, you need to know exactly which file owns which card, which fields are safe to modify, and which values are derived from others. This section builds that mental model and then turns it into a concrete parameterization strategy.

Anatomy of a Keyword File

Every LS-DYNA deck is a sequence of keyword blocks. Each block begins with a keyword line starting with *, optionally followed by a title line, then one or more fixed-format data cards. The format is column-sensitive: a card is divided into 10-character fields, and a value placed in the wrong column will be silently misread or rejected. For example, a *CONTROL_TERMINATION block looks like this:

*CONTROL_TERMINATION
$#  endtim    endcyc     dtmin    endeng    endmas
     0.050         0       0.0       0.0       0.0

The $# line is a comment that documents field names; LS-DYNA ignores it, but your parser should not. A robust parameterization workflow treats these comment lines as a schema: they tell you the field order and let you map a named parameter to a column offset without hard-coding magic numbers.

Common blocks you will encounter and their role in a batch study:

  • *KEYWORD — declares the LS-DYNA version and precision; must match the solver build.
  • *PART, *SECTION, *MAT_* — define geometry ownership, thickness or cross-section, and constitutive behavior. Material parameters are the most frequent sweep targets.
  • *INITIAL_VELOCITY, *LOAD_*, *BOUNDARY_* — define the loading and constraint state; these often carry the design variables in crash or impact studies.
  • *CONTROL_* — solver settings such as termination time, time-step scale factor, and hourglass control.
  • *DATABASE_* — output requests; changing these changes runtime and post-processing cost, so they are usually held fixed across a sweep.

Include Files and the Deck Tree

Production decks split these blocks across files so that different engineers can work independently. A typical layout:

main.k
├── *INCLUDE
│   ├── mesh/parts.k
│   ├── mesh/sections.k
│   ├── mat/steel_mat24.k
│   ├── mat/foam_mat57.k
│   ├── bc/constraints.k
│   ├── load/initial_velocity.k
│   └── control/termination.k

The *INCLUDE card takes a path, and paths may be relative to the including file or to the working directory depending on the path option and solver version. This is the single most common source of batch failures: a deck that runs interactively from its own folder fails when launched from a different working directory because relative includes no longer resolve. Two defenses are worth adopting from the start. First, resolve every include path to an absolute path in your Python driver before invoking the solver. Second, keep a manifest that records the resolved tree so a failed run can be reproduced exactly.

Include files also create a scoping question: if the same keyword appears in two included files, LS-DYNA does not merge them — the later definition typically overrides or the solver errors, depending on the keyword. For parameterization this means each design variable must have exactly one authoritative location. Duplicating a material card in two files and sweeping only one is a classic silent bug.

Deck Tree and Design-Variable Ownership main.k *KEYWORD + *INCLUDE list parts.k *PART *ELEMENT_SHELL sections.k *SECTION_SHELL thickness t steel_mat24.k *MAT_PIECEWISE_LINEAR rho, E, sigy foam_mat57.k *MAT_LOW_DENSITY_FOAM rho, stress curve load/control *INITIAL_VELOCITY *CONTROL_TERMINATION Design variables selected for the batch study t (shell thickness) → sections.k | sigy (yield stress) → steel_mat24.k | v0 (initial velocity) → load/initial_velocity.k Each variable has exactly one owning file; all other cards are frozen for the sweep. Python driver: read template → substitute fields → write run_XXX.k → launch solver run_001.k: t=1.2, sigy=355, v0=12.0 | run_002.k: t=1.4, sigy=355, v0=12.0 | run_003.k: t=1.2, sigy=420, v0=12.0 Frozen: mesh, contacts, output requests, termination time

Identifying Design Variables

A design variable is a numeric field that (a) has a physical meaning you intend to study, (b) lives in exactly one card, and (c) can be changed without invalidating the rest of the deck. Not every numeric field qualifies. Mesh coordinates, node IDs, and part IDs are structural — changing them breaks references. Output intervals and time-step controls affect cost and stability but rarely represent a design decision. The practical filter is to ask: if I change this value, does the physical response change in a way I want to measure, and does the deck remain valid?

For a typical crashworthiness sweep, a defensible variable set might be:

VariableKeyword / fieldOwning fileRange (example)
Shell thickness t*SECTION_SHELL, field 2sections.k1.0 – 2.0 mm
Yield stress sigy*MAT_024, field 3steel_mat24.k300 – 450 MPa
Initial velocity v0*INITIAL_VELOCITY, field 3load/initial_velocity.k8 – 16 m/s
Foam density rho*MAT_057, field 1foam_mat57.k40 – 80 kg/m³

These ranges are illustrative, not measured results. The point is that each row maps a named variable to a specific card and field, which is exactly what the Python layer needs.

Parameterization Strategy

There are three common approaches, and they differ in how much of the deck Python touches.

Template substitution. Keep a pristine template deck and replace placeholder tokens such as __THICKNESS__ with formatted values. This is simple and transparent, but it requires editing the template to insert tokens, and fixed-format columns must be preserved — a value like 1.20 must occupy the same 10-character field as the original. Use a formatter that right-justifies to the field width.

Card-aware rewriting. Parse the deck into blocks, locate the target card by keyword and occurrence index, and rewrite only the specified field. This avoids polluting the template with tokens and is safer when the same keyword appears multiple times (for example, several *MAT_024 cards for different parts). The parser must respect the 10-character field width and skip $ comment lines.

Solver-native parameterization. LS-DYNA supports *PARAMETER and expression-based cards, allowing a value to be defined once and referenced elsewhere. This is elegant for coupled variables (for example, thickness that also drives a mass scaling factor), but it moves complexity into the deck and can obscure what a batch run actually changed. Use it when variables are genuinely coupled; otherwise keep the sweep logic in Python where it is auditable.

Whichever approach you choose, write the resolved parameter set alongside each run's output. A run directory containing run_017.k, params.json, and the solver log is reproducible; one containing only the modified deck is not, because the mapping from run index to parameter values lives only in your head.

A Minimal Working Example

Suppose sections.k contains a single shell section whose thickness sits in field 2 of the data card. A card-aware rewrite in Python looks like this:

FIELD = 10

def set_section_thickness(lines, keyword, occurrence, value):
    idx, seen = None, 0
    for i, line in enumerate(lines):
        if line.strip().upper().startswith(keyword):
            if seen == occurrence:
                idx = i
                break
            seen += 1
    if idx is None:
        raise KeyError(f"{keyword} occurrence {occurrence} not found")
    card = lines[idx + 2]          # keyword line, title line, data card
    fields = [card[j:j+FIELD] for j in range(0, len(card.rstrip("\n")), FIELD)]
    fields[1] = f"{value:10.4f}"   # field 2, right-justified
    lines[idx + 2] = "".join(fields) + "\n"
    return lines

Two details matter here. The offset idx + 2 assumes one title line; verify this against your actual deck, because some keywords omit the title. And the slice loop must stop at the true line length, not a fixed 80 columns, or trailing spaces will be misread as fields. After rewriting, the driver writes run_XXX.k, resolves include paths to absolute form, and launches the solver with a per-run working directory.

Verification Before the Sweep

Do not launch a hundred runs to discover that the thickness field was off by one column. Run a single smoke test first: generate one modified deck, diff it against the template, and confirm that exactly the intended fields changed and nothing else. Then run the solver on that single deck and check that it terminates normally and that the modified parameter appears where expected in the output — for example, the section thickness echoed in the d3hsp file, or the initial velocity reflected in the early-time kinetic energy. Only after this single-run check passes should the batch driver be allowed to fan out.

Typical failure symptoms and their causes: a solver error citing an unknown keyword usually means an include path did not resolve; a run that completes but shows unchanged response usually means the variable was written to a card that is overridden later in the deck; a run that aborts immediately with a negative volume or time-step error often means a numeric field was shifted by one column and a thickness was read as a material constant. Each of these is caught by the diff-and-smoke-test step, which is why it belongs in the driver rather than in a manual checklist.

Never sweep a variable that appears in more than one include file unless you have confirmed which definition the solver actually uses. Silent overrides produce results that look plausible but correspond to a parameter value you never intended to test.

With the deck tree mapped, the design variables isolated to single owning files, and a rewrite routine that preserves fixed-format columns, the batch layer becomes straightforward: a parameter grid, a loop that materializes one deck per combination, and a manifest that ties every run back to its inputs. The next step is to make that loop robust to solver failures and to organize the output for downstream Six Sigma analysis.