4. Compute von Mises stress from d3plot str

Why the von Mises value must be recomputed, not read

LS-DYNA writes the six stress components (σxx, σyy, σzz, σxy, σyz, σzx) per element per state into d3plot. The equivalent (von Mises) stress is a derived scalar, so any extraction pipeline that needs it must compute it from the tensor. The practical risk is not the formula — it is the component ordering, the sign convention for shear, and the element type (solid vs. shell), all of which can silently produce a plausible but wrong peak. This case builds a small, self-contained extractor that reads the tensor for a solid part, computes von Mises at every state, and then reconciles the computed peak against the value LS-PrePost reports for the same part and state.

Reference convention used throughout

All formulas below use the standard continuum-mechanics convention: tensile normal stresses positive, and shear components ordered as LS-DYNA stores them in the d3plot stress block for solids — sigxx, sigyy, sigzz, sigxy, sigyz, sigzx. The von Mises equivalent stress is

sigma_vm = sqrt( 0.5 * [ (sxx-syy)^2 + (syy-szz)^2 + (szz-sxx)^2 ]
                     + 3 * (sxy^2 + syz^2 + szx^2) )

This is invariant to the sign of each shear component individually (they enter squared), but it is not invariant to swapping which component is which — so the ordering check is the real correctness gate. If your reader returns sigxy, sigyz, sigzx in a different order than the writer stored them, the peak will be wrong even though every individual number looks reasonable.

Reading the tensor and computing the field

The extractor below uses lasso-python (the maintained reader for LS-DYNA binary results). It selects a solid part by part ID, pulls the full tensor at every state, computes von Mises per element, and records the global peak together with the element ID and state where it occurs. The part ID and the state range are the only inputs you need to change for a different model.

import numpy as np
from lasso.dyna import D3plot, ArrayType

D3PLOT = "run01/d3plot"
PART_ID = 42          # solid part of interest
STATE_LO, STATE_HI = 0, None   # None = all states

d3 = D3plot(D3PLOT)

# element ids belonging to the part
part_ids   = d3.arrays[ArrayType.part_ids]                 # per part
elem_part  = d3.arrays[ArrayType.element_solid_part_id]    # per solid element
elem_ids   = d3.arrays[ArrayType.element_solid_ids]
mask = (elem_part == PART_ID)
sel_ids = elem_ids[mask]
if sel_ids.size == 0:
    raise SystemExit(f"part {PART_ID} has no solid elements")

# stress: shape (n_states, n_solid_elements, 6)
stress = d3.arrays[ArrayType.element_solid_stress][:, mask, :]
states = np.arange(stress.shape[0])
if STATE_HI is not None:
    states = states[STATE_LO:STATE_HI]
    stress = stress[STATE_LO:STATE_HI]

sxx, syy, szz, sxy, syz, szx = (stress[..., i] for i in range(6))

vm = np.sqrt(
    0.5 * ((sxx - syy)**2 + (syy - szz)**2 + (szz - sxx)**2)
    + 3.0 * (sxy**2 + syz**2 + szx**2)
)

# global peak over selected states and elements
flat = vm.reshape(-1)
k = int(np.argmax(flat))
st_idx, el_idx = np.unravel_index(k, vm.shape)
peak_vm   = float(vm[st_idx, el_idx])
peak_elem = int(sel_ids[el_idx])
peak_state = int(states[st_idx])

print(f"peak von Mises = {peak_vm:.3f}")
print(f"element {peak_elem}, state {peak_state}")

Two details matter for reproducibility. First, element_solid_stress is indexed by the solid element array, not by the global element array — the mask must be built from element_solid_part_id, not from a mixed element list. Second, the six components are returned in the order LS-DYNA wrote them; do not re-sort them by name. If you are unsure of the ordering for your LS-DYNA version, verify it once against a single element whose tensor you can read in LS-PrePost, then keep that ordering fixed.

d3plot (binary) element_solid_stress shape: (n_states, n_solid_elem, 6) order: xx,yy,zz,xy,yz,zx Part selection element_solid_part_id == PART_ID (42) → boolean mask selects solid elements only von Mises per element 0.5[(sxx−syy)²+(syy−szz)² +(szz−sxx)²] + 3(sxy²+syz²+szx²) → sqrt(...) Peak search argmax over (state, element) peak_vm, elem, state Reconcile against LS-PrePost 1. Open same d3plot, select part 42 2. Fringe → von Mises, jump to peak state 3. Read max value + element id 4. Compare to computed peak_vm Match within tolerance → ordering & sign OK Mismatch → suspect component order or shell/solid mix Common failure modes • shear order swapped • shell tensor (5 comp) read as solid (6 comp) • part mask on global element array • state index off-by-one

Reconciling the peak against LS-PrePost

LS-PrePost reports the maximum von Mises value for a part as a fringe maximum, together with the element and state where it occurs. The reconciliation is a three-way check, not a single number comparison:

  1. Open the same d3plot in LS-PrePost, select part 42, and switch the fringe to von Mises stress.
  2. Step to the state returned by the script (peak_state) and read the fringe maximum and the element ID at that maximum.
  3. Compare peak_vm to the fringe maximum, and peak_elem to the reported element ID.

A correct pipeline matches on all three: value, element, and state. A value match with an element mismatch usually means two elements are within rounding of each other and the tie is broken differently — acceptable. A value mismatch beyond a few tenths of a percent is a real defect and should be traced to one of the failure modes below.

SymptomLikely causeFix
Peak is ~10–30% low, element ID plausibleShear components read in wrong order (e.g. sxz where syz expected)Confirm ordering against a single element in LS-PrePost; do not re-sort by name
Peak is wildly high or NaNShell elements included in a solid mask, or 5-component shell tensor read as 6Build mask from element_solid_part_id; handle shells separately
Peak matches but at wrong stateState indexing off-by-one (0-based vs 1-based)Print states[peak_state] and compare to LS-PrePost state number
Peak matches at one state, drifts at othersPart mask includes elements from a neighboring part with same ID rangeVerify sel_ids.size equals the solid element count for the part
Before trusting any peak, run the script on a single state and a single element whose tensor you can read directly in LS-PrePost. If the six components and the resulting von Mises value agree for that one element, the ordering and sign convention are locked in for the whole model.
Do not compare a computed peak against a fringe maximum taken at a different state. LS-PrePost's fringe maximum is per-state; the script's peak_vm is over the selected state range. If STATE_HI is set, restrict the LS-PrePost comparison to the same range or the numbers will not be comparable.

Deliverable: peak reconciliation record

For each part you validate, record the following so the check is auditable and repeatable across models:

FieldExample valueSource
d3plot pathrun01/d3plotinput
Part ID42input
Solid element count in part1,284sel_ids.size
State range0–199input
Computed peak von Mises412.7 MPascript output
Peak element / stateelem 10573 / state 87script output
LS-PrePost fringe max412.7 MPamanual read
Relative difference0.0%computed
VerdictPASSjudgment

The example values above are illustrative; substitute the numbers your own run produces. A PASS requires the relative difference to be within your model's rounding tolerance (typically ≤0.5% for single-precision d3plot output) and the peak element and state to agree. Once this record passes for one part, the same script and the same ordering assumption can be applied to every solid part in the model without re-validating the convention.