2. Parameterizing *MAT_ELASTIC Belt Material Properties via Python

Why *MAT_ELASTIC Needs a Generator, Not a Text Editor

Seatbelt webbing is a poor candidate for hand-edited material cards. A single belt path typically contains several segments — lap, shoulder, buckle stalk, retractor payout — each with its own effective stiffness, and each segment may be re-tuned dozens of times during a restraint development program. The *MAT_ELASTIC card itself is simple (density, Young's modulus, Poisson's ratio), but the failure modes are not: LS-DYNA is field-position sensitive, so a value that drifts one column to the right silently becomes a different parameter, and a density entered in t/mm³ versus kg/mm³ changes the belt's inertial response by three orders of magnitude without any error message.

The task here is a single Python function that consumes a CSV of per-segment material data and emits correctly formatted *MAT_ELASTIC cards. The design constraint that matters most is unit consistency: the CSV must declare its unit system, and the writer must either accept it or convert it — never guess.

Defining the CSV Contract

Keep the schema narrow and explicit. One row per belt segment, with the LS-DYNA material ID as the key. A companion header line records the unit system so the writer can validate rather than assume.

# units: mm_ton_s
mid,name,rho,e,pr,segment
101,BELT_LAP,7.85e-10,1.20e4,0.30,lap
102,BELT_SHOULDER,7.85e-10,1.05e4,0.30,shoulder
103,BELT_BUCKLE_STALK,7.85e-10,2.40e4,0.30,buckle
104,BELT_RETRACTOR,7.85e-10,9.00e3,0.30,retractor

The values above are illustrative teaching data for a mm–ton–s consistent set: density in t/mm³, modulus in MPa (N/mm²). Note that 7.85e-10 t/mm³ is the familiar steel density expressed in this system — webbing is lighter, but the point of the example is the exponent, not the physical accuracy of the number.

The Writer Function

LS-DYNA's fixed-format cards place each field in a 10-character slot. The robust approach is to format every numeric field with a width-10 specifier and let Python's scientific notation fill it, then verify the result is exactly 10 characters before writing. Anything longer is a real error and should raise, not silently truncate.

import csv
from dataclasses import dataclass

UNIT_SYSTEMS = {
    "mm_ton_s":  {"rho": 1.0, "e": 1.0},   # t/mm^3, MPa
    "m_kg_s":    {"rho": 1.0e12, "e": 1.0e6},  # kg/m^3, Pa
}

@dataclass
class BeltMat:
    mid: int
    name: str
    rho: float
    e: float
    pr: float
    segment: str

def _f10(value: float) -> str:
    s = f"{value:10.4E}"
    if len(s) != 10:
        raise ValueError(f"field overflow: {value!r} -> {s!r}")
    return s

def read_belt_csv(path: str):
    units = None
    rows = []
    with open(path, newline="") as fh:
        for line in fh:
            if line.startswith("# units:"):
                units = line.split(":", 1)[1].strip()
                continue
            if line.startswith("#") or not line.strip():
                continue
            rows.append(line)
    if units not in UNIT_SYSTEMS:
        raise ValueError(f"unknown or missing unit system: {units!r}")
    reader = csv.DictReader(rows)
    mats = [BeltMat(int(r["mid"]), r["name"], float(r["rho"]),
                    float(r["e"]), float(r["pr"]), r["segment"])
            for r in reader]
    return units, mats

def write_mat_elastic(mats, units, target="mm_ton_s"):
    src, dst = UNIT_SYSTEMS[units], UNIT_SYSTEMS[target]
    out = []
    for m in mats:
        rho = m.rho * src["rho"] / dst["rho"]
        e   = m.e   * src["e"]   / dst["e"]
        out.append("*MAT_ELASTIC")
        out.append(f"{m.mid:>10}{m.name:>10}")
        out.append(_f10(rho) + _f10(e) + _f10(m.pr))
    return "\n".join(out) + "\n"

Two details carry most of the engineering value. First, _f10 enforces the 10-character contract at the point of formatting, so a modulus of 1.2e5 that would overflow a narrower field fails loudly. Second, the unit conversion is a ratio of scale factors, not a hard-coded multiplier — adding a new system means adding one dictionary entry, and the same code path handles both directions.

How the Data Flows Through the Writer

The diagram below shows the path from CSV to keyword deck, including the two validation gates that stop a bad card from reaching the solver.

CSV → *MAT_ELASTIC writer: data path and validation gates belt_materials.csv # units: mm_ton_s mid, name, rho, e, pr 101 BELT_LAP … 102 BELT_SHOULDER … units known? raise ValueError read_belt_csv BeltMat records unit scale rho × src/dst e × src/dst field = 10? raise ValueError emitted keyword block *MAT_ELASTIC 101 BELT_LAP 7.8500E-10 1.2000E+04 3.0000E-01 each field exactly 10 chars, right-aligned

Running It and Checking the Output

Call the two functions in sequence and inspect the result before it ever reaches the solver:

units, mats = read_belt_csv("belt_materials.csv")
print(write_mat_elastic(mats, units, target="mm_ton_s"))

For the sample CSV, the first card should render as three lines: the keyword, the ID/name line, and a data line whose three numeric fields each occupy exactly ten columns. Verification is mechanical and worth automating:

  • Column check: every data line must be exactly 30 characters for a three-field card. A quick len(line) == 30 assertion catches overflow and underflow together.
  • Round-trip check: re-read the emitted card with a fixed-width parser and confirm the parsed modulus equals the CSV value within floating-point tolerance.
  • Unit sanity: a webbing density in mm_ton_s should land near 1e-10; a value near 1e-4 almost always means the CSV was authored in m_kg_s but labelled mm_ton_s.
Do not let the writer "fix" an overflowing field by switching to a shorter format specifier. A modulus that needs more than ten characters in %.4E is a signal that the value or its unit is wrong — for example, Pa entered into a MPa column. Raising an error preserves that signal; silently reformatting destroys it.

Common Failure Symptoms

SymptomLikely causeFix
Solver reads Poisson's ratio as the modulusField shifted by one column; a value formatted shorter than 10 charsEnforce _f10 on every numeric field
Belt segments behave identically despite different CSV rowsDuplicate mid values; later card overwrites earlierAssert uniqueness of mid before writing
Inertial response off by ~10³Density authored in kg/m³ but declared mm_ton_sCorrect the # units: header, not the numbers
ValueError: field overflowModulus in Pa in a MPa columnConvert at the CSV level or add a target system
Keep the unit system in the CSV header rather than in the filename or a separate config. The header travels with the data, so a file copied between projects cannot silently lose its unit context — which is exactly the failure this writer is designed to prevent.