4. Normalize LS-DYNA Units Across Heterogeneous Decks
Why unit heterogeneity breaks a dataset before it breaks a solver
LS-DYNA is unit-agnostic: the solver never enforces a unit system, so a deck is only self-consistent if every card agrees on the same base units. In a heterogeneous dataset assembled from multiple sources — legacy crash decks in mm-ton-s, supplier subassemblies in m-kg-s, hand-built coupon models in cm-g-µs — the same physical steel appears as 7.85e-9, 7850, or 7.85 depending on the deck. If you concatenate these decks into one feature table without normalization, density, Young's modulus, and yield stress become three different numbers for one material, and any downstream regression or clustering learns the unit system instead of the physics.
The practical task is therefore a detection problem, not a conversion problem: before you can scale anything, you must infer which consistent unit system each deck was authored in. Density is the single most reliable fingerprint, because its magnitude spans roughly twelve orders of magnitude across the common LS-DYNA unit systems, while Young's modulus and yield stress provide corroborating evidence.
The unit-system fingerprint table
The table below lists the consistent systems you will encounter most often in automotive and impact decks. Each row is internally consistent: length, mass, time, and the derived stress unit all agree. Use it as the reference against which detected values are matched.
| System | Length | Mass | Time | Force | Stress | Density (steel) | Young's modulus (steel) |
|---|---|---|---|---|---|---|---|
m-kg-s | m | kg | s | N | Pa | 7850 | 2.10e11 |
mm-ton-s | mm | tonne | s | N | MPa | 7.85e-9 | 2.10e5 |
cm-g-µs | cm | g | µs | — | Mbar | 7.85 | 2.10 |
mm-kg-ms | mm | kg | ms | N | GPa | 7.85e-6 | 210 |
in-lbf-s | in | slug | s | lbf | psi | 7.34e-4 | 3.05e7 |
Note that mm-ton-s and mm-kg-ms both use millimetres but differ in mass and time base, so their densities differ by three orders of magnitude. A detector that only checks length units will confuse them; a detector keyed on density magnitude will not.
Detection logic: density first, modulus as confirmation
The detection routine works on the structured records produced by parsing the deck. For each *MAT_* card, read the density field (RO) and the elastic constants (E, PR). Then:
- Take the log10 of the density magnitude and match it to the nearest reference density in the fingerprint table, using a tolerance of ±0.5 decades. This tolerates alloy variation (aluminium at 2700 vs steel at 7850) without crossing into a neighbouring unit system.
- Confirm with Young's modulus: compute the ratio
E_detected / E_referencefor the candidate system. A ratio within [0.1, 10] confirms the candidate; a ratio outside that band flags the card as inconsistent. - If density matches one system but modulus matches another, the deck is mixed — a real and common defect where a subassembly was merged without unit conversion. Record the conflict rather than silently picking one.
The tolerance bands are deliberately wide because material variation is real: titanium at 4430 kg/m³ and aluminium at 2700 kg/m³ must both resolve to m-kg-s. What the detector must never do is let a factor-of-1000 density error pass as material variation.
Reference implementation
The following function operates on a list of material records (each a dict with mid, ro, e), as produced by the keyword parser. It returns the deck-level unit system plus per-card anomalies. It is self-contained and runnable.
import math
# Reference fingerprints: density (mass/length^3) and Young's modulus in each system's stress unit
REFERENCE = {
"m-kg-s": {"ro": 7850.0, "e": 2.10e11},
"mm-ton-s": {"ro": 7.85e-9, "e": 2.10e5},
"cm-g-us": {"ro": 7.85, "e": 2.10},
"mm-kg-ms": {"ro": 7.85e-6, "e": 210.0},
"in-lbf-s": {"ro": 7.34e-4, "e": 3.05e7},
}
def _decade_distance(a, b):
if a <= 0 or b <= 0:
return float("inf")
return abs(math.log10(a) - math.log10(b))
def detect_deck_units(materials, ro_tol_decades=0.5, e_ratio_band=(0.1, 10.0)):
votes = {}
anomalies = []
for mat in materials:
ro, e = mat.get("ro"), mat.get("e")
if not ro or not e:
anomalies.append({"mid": mat["mid"], "reason": "missing ro or e"})
continue
# 1. nearest density system
best = min(REFERENCE, key=lambda s: _decade_distance(ro, REFERENCE[s]["ro"]))
if _decade_distance(ro, REFERENCE[best]["ro"]) > ro_tol_decades:
anomalies.append({"mid": mat["mid"], "reason": "density off-grid", "ro": ro})
continue
# 2. confirm with modulus
ratio = e / REFERENCE[best]["e"]
if not (e_ratio_band[0] <= ratio <= e_ratio_band[1]):
anomalies.append({"mid": mat["mid"], "reason": "E/density conflict",
"ro": ro, "e": e, "candidate": best, "ratio": ratio})
continue
votes[best] = votes.get(best, 0) + 1
if not votes:
return {"system": None, "confidence": 0.0, "anomalies": anomalies}
system = max(votes, key=votes.get)
total = sum(votes.values())
return {"system": system, "confidence": votes[system] / total, "anomalies": anomalies}
Run it against a three-material deck authored in mm-ton-s (steel ro=7.85e-9, e=2.10e5; aluminium ro=2.70e-9, e=7.0e4; a stray imported material ro=7850, e=2.10e11):
materials = [
{"mid": 1, "ro": 7.85e-9, "e": 2.10e5},
{"mid": 2, "ro": 2.70e-9, "e": 7.0e4},
{"mid": 3, "ro": 7850.0, "e": 2.10e11},
]
print(detect_deck_units(materials))
# {'system': 'mm-ton-s', 'confidence': 0.667,
# 'anomalies': [{'mid': 3, 'reason': 'E/density conflict', ...}]}
The expected output is a deck-level verdict of mm-ton-s with confidence 0.667, and material 3 flagged as an E/density conflict — it is internally consistent with m-kg-s, not with the deck. That flag is the actionable result: material 3 must be converted before the deck enters the dataset.
Reading the result and acting on it
The detector returns three things you must handle differently. The system label tells you which conversion factors to apply to the whole deck. The confidence tells you whether to trust it: a confidence below roughly 0.6 means the deck is genuinely mixed and should be split or repaired before conversion, not converted wholesale. The anomalies list is the repair queue — each entry names the material ID and the reason, so a conversion pass can target exactly those cards.
Typical failure symptoms and their causes:
- All cards flagged "density off-grid". The deck uses a system not in your reference table (e.g.
cm-g-sor a custommm-g-ms). ExtendREFERENCErather than loosening the tolerance. - Confidence near 0.5 with two competing systems. Two subassemblies were merged without conversion. Split the deck by include file or part range and detect each half separately.
- Density matches but E ratio is ~1000. The material card mixes units internally — often a density in
ton/mm³with a modulus inMPacopied from a different source. Flag and correct the card, do not convert the deck.
Once the deck-level system is known, conversion is a deterministic scaling of each field by the ratio of the source system's base units to the target system's. Store the detected system and confidence as columns alongside the material records so that every row in the final dataset carries provenance: a downstream model can then be audited, and any row whose unit system was inferred with low confidence can be excluded or weighted accordingly.