Single-hex uniaxial tension — Hooke’s law + Poisson check#

The smallest possible verification: a unit cube of isotropic linear elastic material under a uniform axial traction must reproduce 1-D Hooke’s law \(\sigma_{xx} = E\, \varepsilon_{xx}\) and Poisson contraction \(\varepsilon_{yy} = -\nu\, \varepsilon_{xx}\) to machine precision.

Reference: Hughes, T. J. R. The Finite Element Method, Dover, 2000, §2.7.

This gallery example drives the validation framework — the PublishedValue carries the citation and the closed-form formula; the acceptance test at the bottom mirrors the regression guard in tests/validation/test_single_hex_uniaxial.py.

from __future__ import annotations

import numpy as np
import pyvista as pv

from femorph_solver.validation.problems import SingleHexUniaxial

Instantiate the benchmark#

Reports its published values in the order declared.

problem = SingleHexUniaxial()
print(f"benchmark: {problem.name}")
print(problem.description)
print()
for ref in problem.published_values:
    print(f"  {ref.name:18s}  {ref.value:+.6e} {ref.unit:3s}  [source: {ref.source}]")
    print(f"    formula: {ref.formula}")
benchmark: single_hex_uniaxial
Single 8-node hex under uniaxial tension — verifies 1-D Hooke's law and Poisson contraction to machine precision.

  tip_axial_disp      +5.000000e-06 m    [source: Hughes 2000 §2.7]
    formula: u_x(L) = sigma/E * L = P L / (E A)
  transverse_disp     -1.500000e-06 m    [source: Hughes 2000 §2.7]
    formula: u_y(L) = -nu eps_xx L
  sigma_xx_avg        +1.000000e+06 Pa   [source: Hughes 2000 §2.7]
    formula: sigma_xx = E eps_xx = P / A

Run the validation#

validate() builds the model, solves, and extracts each published quantity. The default single-hex mesh is enough — this problem is a direct consistency check, not a convergence study.

results = problem.validate()
print("\ncomputed vs published:")
print(f"  {'quantity':<18s} {'computed':>14s} {'published':>14s}  {'rel err':>10s}  pass")
for r in results:
    pass_str = "✓" if r.passed else "✗"
    print(
        f"  {r.published.name:<18s} {r.computed:+14.6e} "
        f"{r.published.value:+14.6e}  "
        f"{r.rel_error * 100:+9.2e}%   {pass_str}"
    )
computed vs published:
  quantity                 computed      published     rel err  pass
  tip_axial_disp      +5.000000e-06  +5.000000e-06  +0.00e+00%   ✓
  transverse_disp     -1.500000e-06  -1.500000e-06  -1.41e-13%   ✓
  sigma_xx_avg        +1.000000e+06  +1.000000e+06  +0.00e+00%   ✓

Visualise the deformed shape#

m = problem.build_model()
res = m.solve()
u = np.asarray(res.displacement).reshape(-1, 3)

# Exaggerate the displacement 1000× for visibility on the figure.
warped = m.grid.copy()
warped.points = m.grid.points + 1000.0 * u

plotter = pv.Plotter(off_screen=True)
plotter.add_mesh(m.grid, style="wireframe", color="black", line_width=1.5, label="undeformed")
plotter.add_mesh(warped, scalars=u[:, 0], show_edges=True, cmap="plasma", label="deformed (×1000)")
plotter.view_isometric()
plotter.camera.zoom(1.2)
plotter.show()
example verify single hex uniaxial

Acceptance#

Machine-precision pass is what Hooke’s law demands. Any regression here is either a constitutive-matrix / strain-kernel bug or a change in the solver residual; the framework surfaces both equally well.

for r in results:
    assert r.passed, (
        f"{r.published.name} drifted above the published tolerance: "
        f"rel_err={r.rel_error:.2e}, tol={r.published.tolerance:.2e}"
    )

Total running time of the script: (0 minutes 0.245 seconds)

Gallery generated by Sphinx-Gallery