2. Extract nodal displacement time history

Objective and data model

This case extracts a single node's displacement time history from an LS-DYNA d3plot database. The d3plot family stores results as a sequence of states; each state corresponds to one output time and contains the full nodal geometry and (optionally) nodal result vectors. For displacement, the relevant quantity is the nodal coordinate at each state relative to the reference (initial) coordinate. LS-DYNA writes nodal coordinates per state, so displacement is derived as u(t) = x(t) − x(0), where x(0) is the coordinate in the first state (or the reference geometry, depending on the reader's convention).

Two practical points govern correctness:

  • Node identity vs. index. LS-DYNA node IDs are not guaranteed to be contiguous or 1-based. A reader typically exposes an array of node IDs and a parallel array of coordinates. You must map the requested node ID to its array index before indexing coordinates.
  • State indexing. State 0 is the reference/initial state in most readers; the number of states equals the number of output intervals plus one. Confirm the state count and time values before extracting, so the time vector and displacement vector have matching lengths.

Choosing a reader

Two common paths exist. LS-PrePost provides an interactive route (open the database, select ASCII or Plot output for a node), which is fine for a one-off check. For repeatable extraction across many nodes or cases, a Python reader is preferable. The lasso-python package (LS-DYNA's Python reader) exposes d3plot contents directly; alternatively, qd (LS-DYNA's Python library) or a custom reader can be used. The example below uses lasso-python, which is the maintained reader distributed with recent LS-DYNA installations.

If lasso-python is unavailable, the same logic applies with any reader that exposes per-state nodal coordinates and a node-ID array. The mapping and subtraction steps are reader-independent.

Extraction workflow

The task decomposes into four concrete steps: open the database, resolve the target node ID to an array index, loop over states accumulating the coordinate, and write a two-column time/displacement table. The SVG below shows the data flow and the two failure points that most often break this task: an unresolved node ID and a state/time length mismatch.

d3plot states × nodes coords + time Reader node IDs[] coords[state][node] Resolve node ID idx = IDs.index(target) fail → KeyError Loop states u = x(t) − x(0) append t, u check len(t)==len(u) CSV output time, ux, uy, uz one row per state Common failures node ID not in IDs[] · state count ≠ time count · wrong reference state

Reference implementation

The script below is self-contained: it opens a d3plot, resolves a target node ID, extracts the displacement history, and writes a CSV. It assumes lasso-python is installed and that the database path is provided. The node ID is a parameter; the script fails loudly if the ID is absent rather than silently returning zeros.

import csv
import sys
from lasso.dyna import D3plot, ArrayType

def extract_node_displacement(d3plot_path, node_id, out_csv):
    d3 = D3plot(d3plot_path)

    # Node IDs and per-state coordinates
    node_ids = d3.arrays[ArrayType.node_ids]          # shape (n_nodes,)
    coords   = d3.arrays[ArrayType.node_coordinates]  # shape (n_states, n_nodes, 3)
    times    = d3.arrays[ArrayType.global_timesteps]  # shape (n_states,)

    # Resolve node ID -> array index
    matches = (node_ids == node_id).nonzero()[0]
    if matches.size == 0:
        raise KeyError(f"node ID {node_id} not found in d3plot")
    idx = int(matches[0])

    # Reference coordinate from state 0
    x0 = coords[0, idx, :]

    # Accumulate displacement per state
    rows = []
    for s in range(coords.shape[0]):
        u = coords[s, idx, :] - x0
        rows.append((float(times[s]), float(u[0]), float(u[1]), float(u[2])))

    if len(rows) != len(times):
        raise RuntimeError("state/time length mismatch")

    with open(out_csv, "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["time", "ux", "uy", "uz"])
        w.writerows(rows)

    return rows

if __name__ == "__main__":
    path = sys.argv[1]
    nid  = int(sys.argv[2])
    out  = sys.argv[3]
    extract_node_displacement(path, nid, out)
    print(f"wrote {out}")

Run it as python extract_node.py model.d3plot 1042 node_1042.csv. The output has one row per state, with the first row at t = 0 and displacement exactly zero (by construction, since x(0) − x(0) = 0). That zero first row is a useful sanity check: if it is not zero, the reference state is wrong or the coordinate array is being indexed incorrectly.

Verification and expected observations

Three checks confirm the extraction is sound:

  1. Zero at t = 0. The first displacement row must be (0, 0, 0). A non-zero value indicates a reference-state or indexing error.
  2. Length agreement. The number of CSV rows equals the number of states reported by the reader. If the reader reports 51 states, the CSV has 51 data rows plus a header.
  3. Cross-check against LS-PrePost. Plot the same node's displacement in LS-PrePost (ASCII → node → displacement) and compare the peak magnitude and the time of the peak. Agreement to within output precision confirms the mapping and sign convention.
Sign convention: LS-DYNA nodal coordinates are in the global system. Displacement components follow the same axes. If your model uses a local coordinate system for output, the reader may return local components; confirm the coordinate system before interpreting ux/uy/uz.

Typical failure symptoms and fixes

SymptomLikely causeFix
KeyError: node ID not foundID is a part-local or 0-based index, not the global node IDPrint node_ids[:10] and confirm the ID range; use the global ID
First row not zeroReference taken from a later state, or wrong node indexUse coords[0, idx, :] as reference; re-verify idx
CSV rows ≠ state countLoop bound uses len(times) but coords has different first dimensionAssert coords.shape[0] == times.shape[0] before looping
Displacement magnitude implausibly largeUnits mismatch (mm vs. m) or coordinate array is deformed geometry in a different frameConfirm model units and that node_coordinates is the intended array

Deliverable: extraction record

For traceability across many cases, record the following alongside the CSV. This is a compact, fillable record that captures the parameters needed to reproduce the extraction.

FieldExample valueNotes
d3plot pathmodel.d3plotInclude full path in production
Target node ID1042Global LS-DYNA node ID
Resolved array index317From IDs.index(target)
State count51From reader
Reference state0Coordinate source for x(0)
Coordinate systemglobalConfirm before interpreting components
Output CSVnode_1042.csvColumns: time, ux, uy, uz
Peak |u| and time2.31 mm @ 0.0042 sCross-checked in LS-PrePost

With the CSV in hand, the displacement history is ready for downstream use: peak-displacement screening, frequency content analysis, or correlation against experimental data. The same mapping-and-subtraction pattern extends directly to velocity and acceleration arrays if the reader exposes them, but displacement is the most robust starting point because it is derived from stored coordinates rather than differentiated quantities.