xplt module usage

pyfebiopt.xplt reads FEBio .xplt outputs and exposes them as views that behave like sliceable arrays. The philosophy: keep FEBio’s shapes and let you select by time, region, items, nodes, and components with the same vocabulary everywhere.

Reading scheme (what you get)

  • File contents: header + dictionary (names/types/formats), mesh (domains, surfaces, nodesets), then states (time steps with nodal/element/surface data).

  • xplt("run.xplt") object exposes:

    • mesh with domains, surfaces, nodesets (names and ids kept from FEBio).

    • dictionary mirroring FEBio variable names/types/formats.

    • results registry keyed by variable, with views:

      • NodeResultView / NodeRegionResultView (nodal, global or per-domain)

      • ItemResultView (elements/faces, FMT_ITEM)

      • MultResultView (per-element-node, FMT_MULT)

      • RegionResultView (per-domain vectors, FMT_REGION)

Capabilities snapshot

  • Read all or selected steps: readAllStates() or readSteps([...]).

  • Access mesh names and ids: domains, surfaces, nodesets.

  • Inspect times and dictionary: xp.results.times(), xp.dictionary.

  • Slice lazily with views; evaluation happens on comp(...) or eval().

  • Handle missing data: absent variables on a step return NaN with correct shape.

Quick start

  1. Open the file (parses header, dictionary, mesh).

from pyfebiopt.xplt import xplt

xp = xplt("run.xplt")
print("Domains:", list(xp.mesh.domains.keys()))
print("Surfaces:", list(xp.mesh.surfaces.keys()))
print("Nodesets:", list(xp.mesh.nodesets.keys()))
  1. Load states (all or a subset).

xp.readAllStates()          # read everything
# xp.readSteps([0, -1])     # only first and last step
  1. Inspect registry, times, and dictionary.

r = xp.results
print("Times:", r.times())                      # numpy array of times
print("Variables:", list(xp.dictionary.keys())) # FEBio dictionary entries
print("Results repr:", r)                       # counts per storage kind

Core capabilities

Nodal (global)

Axes: (time, node, component).

U = r["displacement"]            # NodeResultView
U_tip = U.time(-1).nodes(-1).comp("z")
U_fast = U[0, ":", "x"]          # __getitem__ shorthand
U_base = U.nodeset("base").comp(":")

# boolean mask for nodes
mask = U.nodes(np.array([True, False, True]))
sub = mask.comp("x")

Nodal per-domain

Axes: (time, node_in_region, component).

Ur = r["displacement"].region("arteria")  # NodeRegionResultView
print("Region nodes:", Ur.region_nodes()) # node ids in that domain
u_slice = Ur.time(0).nodes(slice(0, 10)).comp("y")

Element/face items (FMT_ITEM)

Axes: (time, item, component) where items are elements or faces.

S = r["von Mises"].domain("arteria")      # ItemResultView on elements
s_last = S.time(-1).items([0, 5]).comp(":")

P = r["pressure"].surface("outlet")       # ItemResultView on faces
p_mid = P.time(":").faces(":").comp(":")

# boolean mask for items
mask = np.array([True, False, True])
masked = S.items(mask).comp(":")

Per-element-node (FMT_MULT)

Axes: (time, item, enode, component) where enode is the position inside an element.

Q = r["strain"].domain("arteria")         # MultResultView
q_block = Q.time(0).items(0).enodes(":").comp("xx")

Per-region vectors (FMT_REGION)

Axes: (time, component).

V = r["domain volume"].domain("arteria")  # RegionResultView
v_all = V.time(":").comp(":")

Dictionary and metadata

  • xp.dictionary mirrors FEBio’s dictionary: {name: {"type": ..., "format": ...}}.

  • xp.version and xp.compression hold header info.

  • Variable names follow FEBio; component names depend on data type ("x", "xx", etc.).

Selections and tokens

  • Use ":" as a string to mean “all” in __getitem__ and selectors.

  • Selectors accept integers, slices, numpy arrays, lists, or boolean masks.

  • Component tokens: "x" "y" "z" for VEC3F, "xx" "yy" "zz" for MAT3FD, "xx" "yy" "zz" "xy" "yz" "xz" for MAT3FS, full 3x3 names for MAT3F, and Voigt-6 pairs ("xxyy") for TENS4FS.

Slicing cheatsheet

All selectors are chainable; the last call for an axis wins. __getitem__ is a shorthand that mirrors the axis order of each view.

Time

view.time(idx) where idx is int/slice/list/ndarray/boolean mask/":".

U.time(0)            # first state
U.time(slice(0, 3))  # first three
U[0, ":", ":"]       # __getitem__ equivalent

Region

  • Nodal per-domain: region(name) / domain(name)

  • Element/face item or mult: region(name) / domain(name) / surface(name)

  • Region vectors: region(name)

S = r["von Mises"].domain("arteria")
P = r["pressure"].surface("outlet")

Items/nodes/enodes

  • Nodal global: nodes(...) or nodeset("name")

  • Nodal per-domain: nodes(...) (indices are local to that region), region_nodes() to inspect ids

  • Item views: items(...) / elems(...) / faces(...)

  • Mult views: items(...) + enodes(...) / nodes(...) for per-element-node positions

U.nodeset("base").comp(":")          # nodeset by name
Ur = r["displacement"].region("arteria")
Ur.nodes(slice(0, 5)).comp(":")      # first 5 nodes of that domain
Q.items([0, 2]).enodes(":").comp(":")# two elements, all element-nodes

Components

comp(...) is common to every view and evaluates the array. Pass an integer, slice, iterable, numpy array, boolean mask, or component names. ":" means “all”. Component name support depends on the FEBio data type:

  • VEC3F → x y z

  • MAT3FD → xx yy zz

  • MAT3FS → xx yy zz xy yz xz

  • MAT3F → full 3x3 row-major names

  • TENS4FS → Voigt-6 pairs like xxyy (order-insensitive)

Shapes (by view)

  • NodeResultView: (time, node, component)

  • NodeRegionResultView: (time, node_in_region, component)

  • ItemResultView: (time, item, component)

  • MultResultView: (time, item, enode, component)

  • RegionResultView: (time, component)

Memory and missing data

  • Views are lazy and cast to float32 to stay compact.

  • Missing data for a step/region returns arrays filled with NaN but with the correct shape, so vectorized code does not break. This happens when FEBio writes a variable only on some steps (e.g., contact variables defined for part of the analysis); the missing steps remain present as NaN placeholders to keep array ranks stable.

Practical snippets

List all regions that have a given variable:

S = r["von Mises"]
print("Domains:", S.regions())   # works for ItemResultView and others

Compute a time history for a nodeset average:

U = r["displacement"]
base = xp.mesh.nodesets["base"]
Uz = U.nodes(base).comp("z")     # shape: (time, nodes)
Uz_mean = Uz.mean(axis=1)

Grab the last state only for a heavy file:

xp = xplt("run.xplt")
xp.readSteps([-1])
stress = xp.results["stress"].domain("arteria").time(-1).comp("xx")