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). .. code-block:: python 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())) 2) Load states (all or a subset). .. code-block:: python xp.readAllStates() # read everything # xp.readSteps([0, -1]) # only first and last step 3) Inspect registry, times, and dictionary. .. code-block:: python 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)``. .. code-block:: python 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)``. .. code-block:: python 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. .. code-block:: python 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. .. code-block:: python Q = r["strain"].domain("arteria") # MultResultView q_block = Q.time(0).items(0).enodes(":").comp("xx") Per-region vectors (FMT_REGION) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Axes: ``(time, component)``. .. code-block:: python 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.). Print helpers ------------- - ``print(r)`` gives a summary of how many variables exist per storage kind. - ``print(r["displacement"])`` prints shape info and missing-step counts. - ``list(r.node.keys())`` or ``list(r.elem_item.keys())`` to discover variables by kind. 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/``":"``. .. code-block:: python 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)`` .. code-block:: python 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 .. code-block:: python 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: .. code-block:: python S = r["von Mises"] print("Domains:", S.regions()) # works for ItemResultView and others Compute a time history for a nodeset average: .. code-block:: python 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: .. code-block:: python xp = xplt("run.xplt") xp.readSteps([-1]) stress = xp.results["stress"].domain("arteria").time(-1).comp("xx")