Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Running error analysis for the Neo-Hooke strain energy density

Authors
Affiliations
University of Luxembourg
Rafinex
University of Luxembourg
Basque Center for Applied Mathematics
University of Luxembourg

This demo solves a St. Venant-Kirchhoff problem for a 2D cantilever beam and computes the Neo-Hooke strain energy density. It demonstrates how to use running error analysis to obtain upper bounds or estimates on the running error accumulated during assembly.

In particular, this demo emphasizes:

  • custom assemblers that track rounding errors,

  • worst and exact error modes,

  • catastrophic cancellation in the standard Neo-Hooke energy density at small strains,

  • reformulation of the Neo-Hooke energy in terms of 3rd order expansion.

Background: Running error analysis

Running error analysis is an a posteriori method to estimate the numerical error in floating-point computations, see Chapter 3.3 of Higham (2002). Every floating-point operation f^=fl(x^ op y^)\hat f = \text{fl}(\hat x \text{ op } \hat y) commits a fresh rounding error bounded by ϵmach∣f^∣\epsilon_\text{mach} |\hat f|, with ϵmach\epsilon_\text{mach} the machine epsilon, and on top of that propagates the errors ex=x^−xe_x = \hat x - x, ey=y^−ye_y = \hat y - y that its inputs already carry, with ∣ex∣≤eˉx|e_x| \leq \bar e_x, ∣ey∣≤eˉy|e_y| \leq \bar e_y. To first order the two contributions combine into

∣f^−f∣≤ϵmach∣f^∣+∣∂f∂x∣eˉx+∣∂f∂y∣eˉy+h.o.t.,|\hat f - f| \leq \epsilon_\text{mach} |\hat f| + \left|\frac{\partial f}{\partial x}\right| \bar e_x + \left|\frac{\partial f}{\partial y}\right| \bar e_y + \text{h.o.t.},

where f=x op yf = x \text{ op } y is the exact result for exact inputs, and the derivatives are evaluated at (x^,y^)(\hat x, \hat y). For addition both derivatives are 1, giving ϵmach∣f^∣+eˉx+eˉy\epsilon_\text{mach} |\hat f| + \bar e_x + \bar e_y. For multiplication it gives ϵmach∣f^∣+∣y^∣eˉx+∣x^∣eˉy\epsilon_\text{mach} |\hat f| + |\hat y| \bar e_x + |\hat x| \bar e_y.

The bound can therefore be evaluated on the fly, by pairing every value with its error and updating both at each operation. This is what the running_error_t<T, Mode> type of the header-only C++23 library rea implements (running_error.h, which dolfiny fetches and installs alongside its extension module). It costs one extra scalar per value and requires no change to the algorithm.

Running error modes: “worst” vs “exact”

The ErrorMode template parameter selects between two tracking strategies. In both, the value and its error share the working precision T, so a scalar is a homogeneous pair {T val; T err;} of size 2 * sizeof(T).

ErrorMode::WORST (default), “worst” mode. err is a nonnegative bound, updated by the formula above. Addition, for instance, reads

re_t operator+(const re_t& other) const {
  const T new_val = val + other.val;
  return re_t{new_val, err + other.err + re_eps<T> * re_abs(new_val)};
}

Absolute values are taken throughout, so the bound can never decrease: it is an approximate, first-order worst-case bound, and pessimistic. The local term uses the machine epsilon of T rather than the unit roundoff ϵmach/2\epsilon_\text{mach}/2 of round-to-nearest, which keeps it conservative.

ErrorMode::EXACT, “exact” mode. err is instead a signed estimate: the derivatives are taken with sign, and the local rounding is measured rather than bounded — exactly, by error-free transformations, for + - *, by an FMA-based estimate for /, and by re-evaluating in a wider type (float32 for float16, float64 for float32, long double for float64) for sqrt, log, pow and the trigonometric functions. Errors of opposite sign then cancel, which exposes true cancellation instead of hiding it under a worst-case envelope.

Python interface

dolfiny.fem.form JIT-compiles a UFL form with running_error_t as its scalar type, using the templated FFCx kernels of ffcx-backends and cppjit. dolfiny.fem.assemble_vector then runs the DOLFINx assembly with that type, so every operation in the element kernel tracks its own error.

nanobind and DLPack cannot describe the packed (value, error) struct, so the buffer is exposed without a copy as an opaque carrier of matching width (complex64 for the float32 used here) and reinterpreted by dolfiny.fem.split. Coefficients and constants enter with zero error, i.e. treated as exact, unless an error is passed explicitly as assemble_vector(L, errors={u: u_err}). Assembly returns the value and the accumulated error as two real arrays of dtype T — an approximate upper bound in “worst” mode, a signed estimate in “exact” mode.

We start by generating a rectangular mesh for a cantilever beam.

Source
Info    : Meshing 1D...
Info    : [  0%] Meshing curve 1 (Line)
Info    : [ 30%] Meshing curve 2 (Line)
Info    : [ 60%] Meshing curve 3 (Line)
Info    : [ 80%] Meshing curve 4 (Line)
Info    : Done meshing 1D (Wall 0.000399711s, CPU 0.000604s)
Info    : Meshing 2D...
Info    : Meshing surface 1 (Plane, Frontal-Delaunay)
Info    : Done meshing 2D (Wall 0.0485186s, CPU 0.049203s)
Info    : 1445 nodes 2892 elements
Number of cells: 2708

Pre-processing: St. Venant-Kirchhoff solution

The displacement field u\boldsymbol u is computed with a St. Venant-Kirchhoff material law with steel-like properties, μ≈77\mu \approx 77 GPa and first Lamé parameter λ≈115\lambda \approx 115 GPa (Young’s modulus 200 GPa, Poisson’s ratio ν=0.3\nu = 0.3). The beam is clamped on the left boundary and subjected to a downward traction ty=1t_y = 1 MPa on the right boundary.

Source
Output
# SNES iteration  0 
# sub  0 [  2k] |x|=0.000e+00 |dx|=0.000e+00 |r|=2.539e+04 (displacement_svk)
# all           |x|=0.000e+00 |dx|=0.000e+00 |r|=2.539e+04
# SNES iteration  0, KSP iteration   0       |r|=2.539e+04 
# SNES iteration  0, KSP iteration   1       |r|=5.197e-07 
# SNES iteration  1 
# sub  0 [  2k] |x|=4.369e-03 |dx|=4.369e-03 |r|=5.276e+03 (displacement_svk)
# all           |x|=4.369e-03 |dx|=4.369e-03 |r|=5.276e+03
# SNES iteration  1, KSP iteration   0       |r|=5.276e+03 
# SNES iteration  1, KSP iteration   1       |r|=1.240e-10 
# SNES iteration  2 
# sub  0 [  2k] |x|=4.369e-03 |dx|=1.106e-06 |r|=1.667e-03 (displacement_svk)
# all           |x|=4.369e-03 |dx|=1.106e-06 |r|=1.667e-03
# SNES iteration  2, KSP iteration   0       |r|=1.667e-03 
# SNES iteration  2, KSP iteration   1       |r|=3.214e-17 
# SNES iteration  3 success = CONVERGED_FNORM_RELATIVE
# sub  0 [  2k] |x|=4.369e-03 |dx|=2.678e-13 |r|=1.280e-07 (displacement_svk)
# all           |x|=4.369e-03 |dx|=2.678e-13 |r|=1.280e-07

Neo-Hooke strain energy density with running error bounds

We assemble the strain energy density in two algebraically equivalent forms and compare their running error behaviour.

Naive Neo-Hooke (unstable). We use the variant WaW_a of Pence & Gou (2014), Eq. 2.11, which under a 2D plane-strain kinematic simplification reads

W=μ2(I1−2−2log⁡J)+λ2(J−1)2,W = \frac{\mu}{2}(I_1 - 2 - 2 \log J) + \frac{\lambda}{2}(J - 1)^2,

with I1=tr⁡(C)I_1 = \operatorname{tr}(\boldsymbol C), J=det⁡(F)J = \det(\boldsymbol F), C=FTF\boldsymbol C = \boldsymbol F^\mathsf{T} \boldsymbol F and F=I+∇u\boldsymbol F = \boldsymbol I + \nabla \boldsymbol u. At small strains I1≈2I_1 \approx 2 and J≈1J \approx 1, so both I1−2−2log⁡JI_1 - 2 - 2 \log J and (J−1)2(J - 1)^2 suffer catastrophic cancellation, see Shakeri et al. (2024).

Third-order expansion in the Green-Lagrange strain (stable). Following Habera & Zilian (2026), the cancellation is avoided by rewriting the energy in the Green-Lagrange strain E=12(C−I)\boldsymbol E = \tfrac{1}{2}(\boldsymbol C - \boldsymbol I), which eliminates the identity term of F\boldsymbol F. Splitting E\boldsymbol E into a linear and a nonlinear part,

E=E1+E2,E1=12(∇u+∇uT),E2=12 ∇uT∇u,\boldsymbol E = \boldsymbol E_1 + \boldsymbol E_2, \qquad \boldsymbol E_1 = \tfrac{1}{2}\bigl(\nabla \boldsymbol u + \nabla \boldsymbol u^\mathsf{T}\bigr), \qquad \boldsymbol E_2 = \tfrac{1}{2}\,\nabla \boldsymbol u^\mathsf{T} \nabla \boldsymbol u,

the two contributions scale as E1=O(∥∇u∥)\boldsymbol E_1 = \mathcal{O}(\|\nabla \boldsymbol u\|) and E2=O(∥∇u∥2)\boldsymbol E_2 = \mathcal{O}(\|\nabla \boldsymbol u\|^2). Expanding to third order in ∥∇u∥\|\nabla \boldsymbol u\| gives Wstable=W(2)+W(3)+O(∥∇u∥4)W_\text{stable} = W^{(2)} + W^{(3)} + \mathcal{O}(\|\nabla \boldsymbol u\|^4) with

W(2)=μ tr⁡(E12)⏟Wμ(2)+λ2 tr⁡(E1)2⏟Wλ(2),W(3)=μ(2 E1:E2−43tr⁡(E13))⏟Wμ(3)+λ(tr⁡(E1)tr⁡(E2)+12tr⁡(E1)3−tr⁡(E1)tr⁡(E12))⏟Wλ(3).\begin{aligned} W^{(2)} &= \underbrace{\mu\,\operatorname{tr}(\boldsymbol E_1^2)}_{W^{(2)}_\mu} + \underbrace{\tfrac{\lambda}{2}\,\operatorname{tr}(\boldsymbol E_1)^2}_{W^{(2)}_\lambda}, \\ W^{(3)} &= \underbrace{\mu\bigl(2\,\boldsymbol E_1 : \boldsymbol E_2 - \tfrac{4}{3}\operatorname{tr}(\boldsymbol E_1^3)\bigr)}_{W^{(3)}_\mu} + \underbrace{\lambda\bigl(\operatorname{tr}(\boldsymbol E_1)\operatorname{tr}(\boldsymbol E_2) + \tfrac{1}{2}\operatorname{tr}(\boldsymbol E_1)^3 - \operatorname{tr}(\boldsymbol E_1)\operatorname{tr} (\boldsymbol E_1^2)\bigr)}_{W^{(3)}_\lambda}. \end{aligned}

These are the four terms shear_2, bulk_2, shear_3, bulk_3 below. Every intermediate now carries the same ∥∇u∥\|\nabla \boldsymbol u\|-scaling as the result itself, so no cancellation occurs at small strains.

Both energy densities enter a linear form, which is assembled cell-wise,

L(v;u)=∫ΩW(u)∣K∣−1v dx,Lstable(v;u)=∫ΩWstable(u)∣K∣−1v dx,L(v; \boldsymbol u) = \int_\Omega W(\boldsymbol u) |\mathcal K|^{-1} v \, \mathrm dx, \qquad L_\text{stable}(v; \boldsymbol u) = \int_\Omega W_\text{stable}(\boldsymbol u) |\mathcal K|^{-1} v \, \mathrm dx,

for test functions v∈Whv \in W_h of cell-wise constants, so that the assembled vector bi=L(φi;u)b_i = L(\varphi_i; \boldsymbol u) is a cell-averaged strain energy density. Both forms are assembled in single precision (dtype=np.float32) and compared side-by-side in the visualisation below.

Assembling strain energy with running error bounds

The helper extract_energy_fields compiles a form with dolfiny.fem.form, assembles it with dolfiny.fem.assemble_vector, and returns the energy density together with its absolute and relative error as cell-wise (DG-0) fields, in either error mode. It also times the assembly against the plain DOLFINx one, to show the cost of the error tracking.

The input errors of the displacement degrees-of-freedom are initialized randomly at the scale of machine precision, using a fixed seed. The relative rounding error estimate is

ηi=∣ei∣∣biref∣,\eta_i = \frac{|e_i|}{|b_i^\text{ref}|},

where eie_i is the error estimate of the assembled bib_i, and birefb_i^\text{ref} is the double precision evaluation of the stable expression. For the “worst” mode the estimate is a nonnegative approximate bound, ∣fl(bi)−bi∣≤ei+h.o.t.|\text{fl}(b_i) - b_i| \leq e_i + \text{h.o.t.}, while for the “exact” mode it has a sign, fl(bi)−bi≈ei\text{fl}(b_i) - b_i \approx e_i.

Source
Strain Energy Density                     (worst mode):  0.00081s (slowdown:   1.15x) 
Strain Energy Density                     (exact mode):  0.00058s (slowdown:   1.45x) 
Neo-Hooke Expansion Strain Energy Density (worst mode):  0.00077s (slowdown:   1.58x) 
Neo-Hooke Expansion Strain Energy Density (exact mode):   0.0006s (slowdown:   1.30x) 

Visualisation

All field plots below show the St. Venant-Kirchhoff solution at the nominal load, whose deformation scale Π=∥u∥∞/w∼C∥∇u∥\Pi = \|\boldsymbol u\|_\infty / w \sim C \|\nabla \boldsymbol u\|, reported first, is what drives the cancellation in the unstable energy.

Source
St. Venant-Kirchhoff solution, deformation scale: 
  max |u| = 2.345e-04 m, w = 0.5 m, Pi = 4.691e-04 
Assembled unstable strain energy density W.

(a)Assembled unstable strain energy density W.

Assembled stable strain energy density W_stable.

(b)Assembled stable strain energy density W_stable.

Figure 1:Assembled strain energy densities (in Pa): unstable WW (top) and stable WstableW_\text{stable} (bottom).

Source
Rel. error bound for W and "worst" mode.

(a)Rel. error bound for W and "worst" mode.

Rel. error estimate for W and "exact" mode.

(b)Rel. error estimate for W and "exact" mode.

Figure 2:Relative rounding error ηi\eta_i for the unstable expression WW: “worst” mode bound (top) and “exact” mode estimate (bottom) show the pessimism of the worst-case bound.

Source
Rel. error bound for W_stable and "worst" mode.

(a)Rel. error bound for W_stable and "worst" mode.

Rel. error estimate for W_stable and "exact" mode.

(b)Rel. error estimate for W_stable and "exact" mode.

Figure 3:Relative rounding error ηi\eta_i for the stable expression WstableW_\text{stable}: “worst” mode bound (top) and “exact” mode estimate (bottom) demonstrate significantly lower error accumulation due to improved numerical stability.

References
  1. Higham, N. J. (2002). Accuracy and Stability of Numerical Algorithms (2nd ed.). Society for Industrial. 10.1137/1.9780898718027
  2. Pence, T. J., & Gou, K. (2014). On compressible versions of the incompressible neo-Hookean material. Mathematics and Mechanics of Solids, 20(2), 157–182. 10.1177/1081286514544258
  3. Shakeri, R., Ghaffari, L., Stengel, K., Thompson, J. L., & Brown, J. (2024). Stable numerics for finite-strain elasticity. International Journal for Numerical Methods in Engineering, 125(24). 10.1002/nme.7563
  4. Habera, M., & Zilian, A. (2026). Automated dimensional analysis for PDEs. arXiv. 10.48550/ARXIV.2601.06535