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.
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.
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:
- Zero at t = 0. The first displacement row must be
(0, 0, 0). A non-zero value indicates a reference-state or indexing error. - 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.
- 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.
ux/uy/uz.Typical failure symptoms and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
KeyError: node ID not found | ID is a part-local or 0-based index, not the global node ID | Print node_ids[:10] and confirm the ID range; use the global ID |
| First row not zero | Reference taken from a later state, or wrong node index | Use coords[0, idx, :] as reference; re-verify idx |
| CSV rows ≠ state count | Loop bound uses len(times) but coords has different first dimension | Assert coords.shape[0] == times.shape[0] before looping |
| Displacement magnitude implausibly large | Units mismatch (mm vs. m) or coordinate array is deformed geometry in a different frame | Confirm 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.
| Field | Example value | Notes |
|---|---|---|
| d3plot path | model.d3plot | Include full path in production |
| Target node ID | 1042 | Global LS-DYNA node ID |
| Resolved array index | 317 | From IDs.index(target) |
| State count | 51 | From reader |
| Reference state | 0 | Coordinate source for x(0) |
| Coordinate system | global | Confirm before interpreting components |
| Output CSV | node_1042.csv | Columns: time, ux, uy, uz |
| Peak |u| and time | 2.31 mm @ 0.0042 s | Cross-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.