Plotting and Postprocessing#
This chapter covers stress recovery, reaction extraction, and visualization in femlabpy.
1. Stress Recovery Mathematics#
1.1 Gauss point to node extrapolation#
For isoparametric elements, stresses are most accurate at Gauss points. To obtain nodal values, we extrapolate using the inverse of the shape function matrix evaluated at Gauss points.
For a Q4 element with \(2 \times 2\) Gauss integration, the Gauss point coordinates are \((\pm 1/\sqrt{3}, \pm 1/\sqrt{3})\). The extrapolation uses the shape functions evaluated at corner nodes (\(\xi, \eta = \pm 1\)):
where \(\mathbf{E}\) is the \(4 \times 4\) extrapolation matrix:
evaluated with corner shape functions at Gauss point locations.
1.2 Nodal averaging#
When multiple elements share a node, the extrapolated values are averaged:
This smoothing reduces discontinuities between elements.
1.3 Stress components#
The Voigt notation stress vector contains:
2D (plane stress/strain): $\( \boldsymbol{\sigma} = [\sigma_{xx}, \sigma_{yy}, \sigma_{xy}]^T \)$
3D: $\( \boldsymbol{\sigma} = [\sigma_{xx}, \sigma_{yy}, \sigma_{zz}, \sigma_{xy}, \sigma_{yz}, \sigma_{zx}]^T \)$
2. Reaction Forces#
2.1 Theory#
At constrained DOFs, the reaction force equals the internal force:
For equilibrium: \(\mathbf{R} + \mathbf{F}^{ext} = \mathbf{F}^{int}\)
At free DOFs: \(R_i = 0\) (by definition)
At constrained DOFs: \(R_i = F_i^{int} - F_i^{ext}\)
2.2 Implementation#
The reaction() function extracts reactions from the assembled internal force vector:
R = fp.reaction(q, C, dof)
Returns [node, local_dof, reaction] for each constrained DOF.
3. Mesh and Field Plotting#
The plotting helpers in plotting.py work directly on the topology and
coordinate arrays used elsewhere in the package.
plotelem#
plotelem(T, X, ...) draws the undeformed mesh. It reads each element row,
subtracts one from the one-based node ids, and plots the resulting polygon or
polyline.
The helper also supports optional node and element labels. That is useful when you are checking topology ordering or debugging a bad mesh import.
plotforces#
plotforces(T, X, P, ...) draws nodal loads as arrows. The element table is not
used directly; it is accepted only for a consistent plotting signature.
The arrow size is scaled from the mesh span so a small model and a large model remain readable with the same function call.
plotbc#
plotbc(T, X, C, ...) visualizes prescribed displacements. Zero-valued
constraints are shown as markers, while nonzero prescribed values are drawn as
arrows.
That distinction matches the actual solver behavior:
zero values are fixed degrees of freedom;
nonzero values are enforced displacements.
plotu#
plotu(T, X, u, dof=None, component=0, ...) draws a nodal contour field. It
accepts either:
one scalar value per node,
a nodal array with several components per node, or
a flattened global vector with length
nn * dof.
When several components are available, component=0 plots the nodal magnitude
and component=1, 2, ... selects a specific component. Flat Gmsh coordinate
arrays of the form (x, y, z) are reduced automatically to a 2D plotting view
when all out-of-plane coordinates are constant.
The contour still works the same way internally: each element is colored by the
mean of its nodal samples, using PolyCollection in 2D and
Poly3DCollection in 3D.
Element Contours#
plotq4#
plotq4(T, X, S, scomp, ...) reconstructs a nodal contour from Q4 Gauss-point
results. The code expects the Q4 storage layout used by the Q4 recovery
functions: each component is stored at four Gauss points per element.
The implementation does three things:
it extracts the requested component with one-based
scomp;it maps the four Gauss-point values to corner values with a small extrapolation matrix;
it averages contributions from adjacent elements at each node.
The final contour is displayed with tripcolor(..., shading="gouraud") so the
result looks smooth across the mesh.
plott3#
plott3(T, X, S, scomp, ...) follows the same idea for T3 output. The recovery
is simpler because the T3 storage is already element-centered in the form used
by the rest of the codebase.
Reactions#
The reaction() function in postprocess.py reads the internal force vector
and extracts the support reactions associated with constrained degrees of
freedom.
It does not recompute the finite element solution. It assumes you already have
the assembled internal force vector q and the constraint table C.
Inputs and outputs#
qis the global internal force vector.Cis the constraint table.doftells the helper how to flatten node and local DOF ids.compoptionally filters the returned rows by a single constrained component.
The output is either:
[node, local_dof, reaction], or[constraint_row, reaction]whencompis supplied.
Why this helper is small#
Support reactions are a recovery task, not a solve task. The helper exists so the solve path can stay focused on matrix assembly while the postprocess layer handles the bookkeeping required to report reactions cleanly.
Typical Workflow#
import numpy as np
import femlabpy as fp
K, p, q = fp.init(nn, dof)
K = fp.kq4e(K, T, X, G)
p = fp.setload(p, P)
K, p, _ = fp.setbc(K, p, C, dof)
u = np.linalg.solve(K, p)
q, S, E = fp.qq4e(q, T, X, G, u)
R = fp.reaction(q, C, dof)
Then use the plotting helpers to inspect the result:
plotelemfor geometry,plotforcesfor loads,plotbcfor constraints,plotufor scalar nodal fields or displacement-component contours,plotq4orplott3for stress or strain contours.