1. Audit a Flat Keyword Deck for Required Control Cards

Why a flat deck needs a control-card audit

A single-file LS-DYNA deck is convenient to hand around, but it hides a specific risk: the solver will happily start, read the mesh, and only later reveal that a termination card is missing, a time-step card has a malformed field, or the d3plot output request never existed. By the time the job aborts — or worse, runs to the default termination time and produces no animation data — you have already burned queue time. The audit described here is a pre-submission gate that answers three questions about one flat deck: are the three mandatory control cards present, are their keyword lines syntactically well-formed, and do their field values make physical sense for the model being submitted?

The three cards in scope are:

  • *CONTROL_TERMINATION — defines when the analysis stops (ENDTIM), and optionally the time-step scale factor for termination (ENDCYC, DTMIN).
  • *CONTROL_TIMESTEP — controls the explicit time-step calculation: DTINIT (initial time step), TSSFAC (time-step safety factor, default 0.9), and DTMIN (minimum time-step termination ratio).
  • *DATABASE_BINARY_D3PLOT — requests binary d3plot output, with DT (output interval) or NPLTC (cycle interval) and LCDT (time-step curve ID).

All three are "control" cards in the sense that they govern solver behaviour rather than describing geometry, and all three are frequently omitted when a deck is assembled by hand from fragments. The audit is therefore a structural check first and a numeric sanity check second.

What "syntactically valid" means for these cards

LS-DYNA keyword input is fixed-format in the sense that each card is a keyword line followed by one or more data lines, and each data line is a sequence of ten-character fields. The parser is tolerant of whitespace but not of misplaced fields: a value written in the wrong column will be read as a different parameter, or silently ignored. For the three cards in scope, the practical syntax rules are:

  • The keyword line must begin with * in column 1, with no leading whitespace, and the keyword must be spelled exactly (case-insensitive, but the underscore is required).
  • Each data line must contain the expected number of fields; trailing fields may be blank, but a blank field in the middle of a line shifts every subsequent value.
  • Numeric fields must be parseable as integers or reals as appropriate; a stray character such as 0.9s or 1e-3 ms will be rejected or misread.
  • Comment lines beginning with $ are ignored and may appear between cards, but not inside a card's data block.

A useful mental model is that the deck is a sequence of records, and the audit is a record-level check: for each of the three keywords, confirm exactly one occurrence, confirm the data line has the right field count, and confirm the values are in range.

The audit procedure

The procedure below is deliberately tool-agnostic: it can be done with a text editor, a shell script, or a small Python checker. The point is the sequence of checks, not the tool.

  1. Locate the deck and confirm it is flat. A flat deck contains no *INCLUDE lines. If *INCLUDE is present, the audit must be repeated on each included file, or the deck must be flattened first. For this case, assume a single file job_flat.k with no includes.
  2. Count keyword occurrences. Search for each of the three keywords and record the line numbers. Exactly one occurrence of each is expected. Zero means the card is missing; more than one means a duplicate that LS-DYNA will resolve by last-wins or first-wins depending on the card, which is a separate defect.
  3. Inspect the data line field layout. For each card, read the line immediately following the keyword and verify the field count and column alignment. A quick check is to confirm that the first non-blank character of each field sits at column 1, 11, 21, 31, and so on.
  4. Check numeric ranges. ENDTIM must be positive; TSSFAC should be between 0 and 1 (0.9 is the usual default); DTMIN should be a small positive fraction; DT for d3plot must be positive and should be a sensible fraction of ENDTIM.
  5. Cross-check consistency. The d3plot interval DT should divide ENDTIM into a reasonable number of frames (typically 20–100 for a first look). If DT > ENDTIM, the job will produce at most one plot state, which is almost never intended.
A fast first pass is to grep for the three keywords and print the following line. If any of the three greps returns nothing, stop and fix that before looking at anything else — a missing control card is a hard failure, not a warning.

Reading the audit result

The SVG below shows the decision structure of the audit as it applies to one flat deck. Each of the three cards is checked independently for presence, then for field layout, then for value range; the deck is only cleared for submission when all three branches reach the "pass" state. The right-hand column shows the typical symptom observed at runtime when a branch fails, which is what makes the audit worth doing before submission rather than after.

job_flat.k single file, no *INCLUDE *CONTROL_TERMINATION ENDTIM > 0 present exactly once? field count = 8 ENDTIM numeric > 0? no unit suffix *CONTROL_TIMESTEP 0 < TSSFAC ≤ 1 present exactly once? field count = 8 TSSFAC in (0,1]? DTMIN small positive *DATABASE_BINARY_D3PLOT DT > 0, DT ≤ ENDTIM present exactly once? field count = 8 DT positive & ≤ ENDTIM? frame count 20–100 PASS → termination defined PASS → stable Δt PASS → animation data if missing / malformed runs to default ENDTIM or aborts at input echo if missing / malformed default TSSFAC 0.9 used or Δt collapses / negative if missing / malformed no d3plot written or single frame only All three PASS → deck cleared for submission any FAIL → fix card, re-run audit

A worked example: three defects in one deck

Consider a flat deck job_flat.k assembled from a mesh fragment and a material fragment. The relevant excerpt, with line numbers, is:

  1  *KEYWORD
  2  *CONTROL_TERMINATION
  3  $  ENDTIM    ENDCYC    DTMIN     NSTEPS    ENDENG    ENDMAS
  4   0.050
  5  *CONTROL_TIMESTEP
  6  $  DTINIT    TSSFAC    ISDO      TSLIMT    DT2MS     LCTM
  7   0.0       0.9s
  8  *DATABASE_BINARY_D3PLOT
  9  $      DT      LCDT      BEAM      NPLTC     PSETID
 10   0.010
 11  *PART
 12  ...

Running the audit against this excerpt produces three findings:

CardCheckResultAction
*CONTROL_TERMINATIONPresent, one occurrence, ENDTIM = 0.050 positivePASSNone
*CONTROL_TIMESTEPTSSFAC field contains 0.9sFAIL — non-numeric suffixChange to 0.9
*DATABASE_BINARY_D3PLOTDT = 0.010, ENDTIM = 0.050 → 5 framesWARN — frame count lowReduce DT to 0.001 for 50 frames

The 0.9s defect is the classic one: a unit suffix that a human reads as "0.9 seconds" but the fixed-format parser reads as a malformed real. Depending on the LS-DYNA version, this either aborts at input echo or silently falls back to the default TSSFAC = 0.9, which happens to be the intended value — so the job may run and the defect goes unnoticed until a later deck reuses the same fragment with a different intended factor. The audit catches it because it checks the field text, not just the parsed value.

The d3plot warning is a judgement call rather than a hard failure. Five frames over a 50 ms event is enough to confirm the job ran, but not enough to see a deformation sequence. The audit flags it so the decision is explicit rather than accidental.

Automating the check

The audit is small enough to script. The following Python function reads a flat deck, locates each of the three keywords, and returns a per-card status. It assumes the deck has already been confirmed free of *INCLUDE lines; that check is a one-line grep and is left to the caller.

import re

REQUIRED = {
    "*CONTROL_TERMINATION": {"fields": 8, "checks": {"ENDTIM": (0, None)}},
    "*CONTROL_TIMESTEP":    {"fields": 8, "checks": {"TSSFAC": (0, 1.0)}},
    "*DATABASE_BINARY_D3PLOT": {"fields": 8, "checks": {"DT": (0, None)}},
}

def audit_flat_deck(path):
    with open(path, "r", encoding="utf-8", errors="replace") as fh:
        lines = fh.readlines()

    # strip comments and blank lines, keep (lineno, text)
    records = [(i + 1, ln.rstrip("\n"))
               for i, ln in enumerate(lines)
               if ln.strip() and not ln.lstrip().startswith("$")]

    results = {}
    for kw, spec in REQUIRED.items():
        hits = [(n, t) for n, t in records if t.strip().upper() == kw]
        if len(hits) != 1:
            results[kw] = ("FAIL", f"occurrences={len(hits)}")
            continue
        lineno, _ = hits[0]
        # data line is the next record after the keyword
        idx = next(i for i, (n, _) in enumerate(records) if n == lineno)
        if idx + 1 >= len(records):
            results[kw] = ("FAIL", "no data line")
            continue
        data = records[idx + 1][1]
        fields = [data[i:i + 10].strip() for i in range(0, len(data), 10)]
        if len(fields) < spec["fields"]:
            results[kw] = ("FAIL", f"fields={len(fields)}")
            continue
        status, detail = "PASS", []
        for name, (lo, hi) in spec["checks"].items():
            raw = fields[0] if name in ("ENDTIM", "DT") else fields[1]
            try:
                val = float(raw)
            except ValueError:
                status, detail = "FAIL", f"{name}='{raw}' not numeric"
                break
            if lo is not None and val <= lo:
                status, detail = "FAIL", f"{name}={val} <= {lo}"
                break
            if hi is not None and val > hi:
                status, detail = "FAIL", f"{name}={val} > {hi}"
                break
            detail.append(f"{name}={val}")
        results[kw] = (status, "; ".join(detail))
    return results

if __name__ == "__main__":
    for kw, (status, detail) in audit_flat_deck("job_flat.k").items():
        print(f"{status:4s}  {kw:28s}  {detail}")

Run against the excerpt above, the script prints PASS for termination, FAIL for timestep with the message TSSFAC='0.9s' not numeric, and PASS for d3plot. The d3plot frame-count warning is not in the script because it depends on ENDTIM, which lives on a different card; add it as a second pass once the three presence checks are clean.

The script assumes the data line immediately follows the keyword line. That is true for these three cards, but not for every LS-DYNA keyword — some cards have optional second data lines, and some accept a blank line as a valid "use defaults" record. Do not generalise this parser to the whole deck without checking each card's format in the LS-DYNA Keyword User's Manual.

Verification and what to do with a failure

The audit is complete when all three cards report PASS and the d3plot frame count is within the intended range. A FAIL on presence means the card must be added; a FAIL on field layout means the data line must be re-columned; a FAIL on range means the value must be corrected. In every case the fix is local to the card, which is the advantage of auditing a flat deck before splitting it into includes: the defect is in one file, at one line, and the correction does not propagate.

Two failure modes are worth calling out because they are easy to miss. First, a card that is present but whose keyword line has a leading space will not be recognised — the parser requires * in column 1. Second, a card that appears twice will be resolved by the solver according to its own precedence rules, which for control cards is typically last-wins; the audit reports the duplicate count so the ambiguity is visible. Neither of these is caught by a simple "does the keyword exist" search, which is why the audit checks occurrence count and column position rather than mere presence.

Once the deck passes, it is ready for submission — and, if the next step is to split it into logical includes, it is also a clean baseline against which the split can be verified: the same three cards must survive the refactor with the same values and the same single occurrence each.