1. Parametric Keyword Template for DOE Factor Injection

Why a Template Instead of a Script

DOE generation for LS-DYNA models fails most often not because the solver is wrong, but because the input deck is not substitutable. If a factor value is buried inside a card whose field position shifts when a neighboring value changes, a naive string replacement will silently corrupt the model. The goal of this first case is therefore narrow and structural: produce one keyword file in which every DOE factor appears exactly once, at a fixed, machine-locatable position, and in a form that a substitution script can rewrite without touching anything else.

Two properties must hold for the template to be reusable across the whole DOE campaign:

  • Uniqueness — each factor token appears exactly once in the deck, so a global replace cannot hit an unintended card.
  • Positional stability — the token occupies a fixed column range on a fixed card, so substitution is a field-level edit, not a text-level guess.

LS-DYNA's fixed-format cards make this achievable, but only if you respect the 10-character field convention and the comma-delimited free format consistently. Mixing the two within one card is the single most common source of broken decks.

Choosing the Placeholder Convention

A placeholder must be syntactically valid enough that the deck still parses when the template is run unmodified (useful for a smoke test), yet visually unmistakable. The convention used here is a leading $ comment marker is not acceptable inside a data field, so instead we use a bracketed token that is a legal numeric literal in neither fixed nor free format — which forces an early, loud failure if substitution is skipped:

*KEYWORD
*CONTROL_TERMINATION
$#  endtim    endcyc     dtmin    endeng    endmas
   {{T_END}}         0       0.0       0.0       0.0
*MAT_PIECEWISE_LINEAR_PLASTICITY
$#     mid        ro         e        pr      sigy      etan      eppf      tdel
   {{MAT_ID}}  {{RHO}}  {{E_MOD}}  {{NU}}  {{SIGY}}       0.0       0.0       0.0
$#     lc        lcs       lcsr        vp
         0         0         0       0.0
*SECTION_SHELL
$#   secid    elform      shrf       nip     propt   qr/irid     icomp     setyp
   {{SEC_ID}}         2       0.0         5       0.0       0.0       0.0         1
$#      t1        t2        t3        t4      nloc     marea      idof    edgset
   {{THICK}}  {{THICK}}  {{THICK}}  {{THICK}}       0.0       0.0       0.0         0
*PART
$# title
plate_under_impact
$#     pid     secid       mid     eosid      hgid      grav    adpopt      tmid
   {{PART_ID}}   {{SEC_ID}}   {{MAT_ID}}         0         0         0         0         0
*END

Note the deliberate choices: {{THICK}} is repeated four times on the shell thickness card because all four integration-point thicknesses must move together — but the token is identical, so a single replace handles all four. {{SEC_ID}} and {{MAT_ID}} appear on both their defining card and the *PART reference card; again the identical token guarantees referential integrity. This is the core trick: let the token, not the line number, carry the linkage.

Keep the token alphabet disjoint from LS-DYNA's own syntax. Curly braces, angle brackets, and the @ sign are safe; $, *, and & are not, because they are comment, keyword, and include markers respectively.

How the Template Maps to the DOE Factor Table

The template is only half the contract. The other half is a manifest that tells the generator which token corresponds to which factor, its unit, and its legal range. The SVG below shows the relationship: each DOE factor flows into exactly one token, and each token resolves to one or more physical card fields, with the reference cards (PART) inheriting the same token so IDs stay consistent.

DOE Factor → Token → Card Field Mapping DOE Factors T_END (ms) RHO (t/mm³) E_MOD (MPa) SIGY (MPa) THICK (mm) MAT_ID (int) SEC_ID (int) Template Tokens {{T_END}} {{RHO}} {{E_MOD}} {{SIGY}} {{THICK}} {{MAT_ID}} {{SEC_ID}} Card Fields *CONTROL_TERMINATION *MAT_PL: ro *MAT_PL: e *MAT_PL: sigy *SECTION_SHELL: t1..t4 *MAT_PL: mid + *PART *SECTION_SHELL: secid + *PART Each factor resolves to exactly one token; tokens on reference cards reuse the same name to preserve ID linkage.

Building the Manifest and the Substitution Check

The manifest is a small CSV that the generator reads. It is the authoritative record of what the template expects, and it doubles as the validation contract: if a token in the template is missing from the manifest, or a manifest token is absent from the template, the build must fail before any solver run.

token,unit,type,lower,upper,levels
T_END,ms,float,5.0,20.0,3
RHO,t/mm^3,float,7.80e-9,7.90e-9,3
E_MOD,MPa,float,200000,215000,3
SIGY,MPa,float,250,400,4
THICK,mm,float,1.0,3.0,3
MAT_ID,-,int,1,1,1
SEC_ID,-,int,1,1,1

With the manifest in place, substitution is a two-pass operation. Pass one verifies token coverage; pass two writes the concrete deck. The Python below is complete and runnable against the template shown above — it needs only the standard library.

import csv, re, sys
from pathlib import Path

TEMPLATE = Path("template.k")
MANIFEST = Path("factors.csv")
TOKEN_RE = re.compile(r"\{\{([A-Z0-9_]+)\}\}")

def load_manifest(path):
    with path.open(newline="") as f:
        return {row["token"]: row for row in csv.DictReader(f)}

def check_coverage(template_text, manifest):
    found = set(TOKEN_RE.findall(template_text))
    declared = set(manifest)
    missing_in_template = declared - found
    undeclared = found - declared
    if missing_in_template or undeclared:
        sys.exit(f"coverage error: missing_in_template={missing_in_template} "
                 f"undeclared={undeclared}")
    return found

def render(template_text, values):
    def sub(m):
        tok = m.group(1)
        if tok not in values:
            raise KeyError(f"no value supplied for {tok}")
        return str(values[tok])
    return TOKEN_RE.sub(sub, template_text)

if __name__ == "__main__":
    text = TEMPLATE.read_text()
    manifest = load_manifest(MANIFEST)
    check_coverage(text, manifest)
    # baseline values taken from the manifest midpoints (example only)
    baseline = {tok: (float(r["lower"]) + float(r["upper"])) / 2
                for tok, r in manifest.items() if r["type"] == "float"}
    baseline.update({tok: int(r["lower"]) for tok, r in manifest.items()
                     if r["type"] == "int"})
    Path("baseline.k").write_text(render(text, baseline))
    print("wrote baseline.k with", len(baseline), "substituted factors")

Verifying Structural Integrity

Substitution correctness is not the same as model correctness. After rendering baseline.k, confirm three things before trusting the template for the full DOE sweep:

  1. No residual tokens. grep -c '{{' baseline.k must return 0. A non-zero count means a token was misspelled in the manifest or the template.
  2. Card count unchanged. The number of lines beginning with * in baseline.k must equal that in template.k. Substitution must never add or remove cards.
  3. Field width sanity. In fixed-format decks, a substituted value longer than 10 characters will push into the next field. The manifest's upper bound should be chosen so that the widest legal value still fits; if it cannot, switch that card to comma-delimited free format and document the change.
A common failure symptom is a deck that runs but produces a suspiciously stiff or soft response. That usually means a token was replaced with a value in the wrong unit — for example RHO entered in kg/m³ instead of t/mm³. The manifest's unit column exists precisely to catch this during review, not during post-processing.

Once these three checks pass, the template is a stable contract: every subsequent DOE case can generate a deck by supplying a value dictionary, and the structural integrity of the model is guaranteed by construction rather than by inspection.