Strain and stress recovery#

femorph-solver recovers elastic strain from a displacement field via femorph_solver.Model.eel(). The evaluation uses

\[\varepsilon(\xi) = B(\xi) \, u_e\]

at each element’s own node positions \(\xi_\text{node}\) — i.e. direct nodal evaluation of \(\varepsilon = B u_e\), not Gauss-point sampling followed by extrapolation. For uniform-strain fields the two approaches agree exactly; for general fields they converge with mesh refinement.

Voigt convention#

Every strain tensor returned by femorph-solver is 6-component Voigt with engineering shears:

\[\varepsilon = \bigl[\varepsilon_{xx},\ \varepsilon_{yy},\ \varepsilon_{zz}, \ \gamma_{xy},\ \gamma_{yz},\ \gamma_{xz}\bigr]\]

with \(\gamma_{xy} = 2\varepsilon_{xy}\) (canonical Voigt strain layout).

API#

u = static.displacement           # from solve_static / Model.solve
eps = m.eel(u)                    # (n_nodes_global, 6), nodal-averaged

eps_x = eps[:, 0]                 # ε_xx at every node
gamma_xy = eps[:, 3]              # engineering shear

Per-element (unaveraged)#

nodal_avg=False returns the un-averaged per-element dict — use it when you want to see gradient discontinuities between elements:

eps_per_elem = m.eel(u, nodal_avg=False)
# {elem_num: (n_nodes_in_elem, 6)}

Skips nodal averaging; useful for diagnosing per-element strain discontinuities.

From a mode shape#

Strain can be recovered from any valid displacement vector, including an eigenvector column from a modal solve:

modal = solve_modal(K, M, prescribed=fixed, n_modes=10,
                    eigen_solver="arpack", sigma=-1.0)
eps_mode7 = m.eel(modal.mode_shapes[:, 7])

Public per-node helpers#

The femorph_solver.recover module exposes (n_points, 6) Voigt strain and stress at every grid node, with multi-element nodes averaged in place (unweighted, same convention as MAPDL’s PRNSOL):

from femorph_solver.recover import (
    compute_nodal_strain,
    compute_nodal_stress,
    stress_invariants,
)

u = static.displacement                     # DOF-indexed displacement
eps = compute_nodal_strain(m, u)            # (n_points, 6) Voigt strain
sig = compute_nodal_stress(m, u)            # (n_points, 6) Voigt stress
inv = stress_invariants(sig)                # von_mises, s1/s2/s3, ...

Both helpers walk every element once, evaluate \(\varepsilon = B(\xi_\text{node})\,u_e\) at the element’s own nodes, optionally apply the per-material \(\sigma = C\varepsilon\), and average across every element that touches a node. Nodes with no incident element come back as zeros.

Stress recovery#

Stress \(\sigma = C \varepsilon\) is also available as a direct contraction from the elastic moduli when only one element’s elasticity matrix is in hand. For an isotropic Hooke’s law:

from femorph_solver.elements.hex8 import _elastic_matrix

C = _elastic_matrix(E=2.1e11, nu=0.30)    # 6x6 isotropic
sigma = eps @ C.T                          # (n_nodes, 6)

Element-specific notes#

  • Solids (HEX8 / HEX20 / TET10) return 3-D Voigt as described above.

  • Shells (QUAD4_SHELL) return membrane + bending + transverse-shear components; details on the QUAD4_SHELL — 4-node Mindlin–Reissner shell reference page.

  • Beams, trusses, springs — currently return placeholder arrays; their strain / stress output is on the roadmap.

See also#