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.
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) == 30assertion 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_sshould land near1e-10; a value near1e-4almost always means the CSV was authored inm_kg_sbut labelledmm_ton_s.
%.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
| Symptom | Likely cause | Fix |
|---|---|---|
| Solver reads Poisson's ratio as the modulus | Field shifted by one column; a value formatted shorter than 10 chars | Enforce _f10 on every numeric field |
| Belt segments behave identically despite different CSV rows | Duplicate mid values; later card overwrites earlier | Assert uniqueness of mid before writing |
| Inertial response off by ~10³ | Density authored in kg/m³ but declared mm_ton_s | Correct the # units: header, not the numbers |
ValueError: field overflow | Modulus in Pa in a MPa column | Convert at the CSV level or add a target system |