Linear static analysis#

When to use#

Linear static analysis answers the question “what is the displacement and stress field on a structure under a given load, assuming small strains, linear-elastic material, and time- independent loading?”.

It’s the workhorse of structural design — the right answer for >80 % of mechanical design checks. Use it when:

  • Loads are constant in time (or change slowly enough that inertia is negligible).

  • Material is linear-elastic across the load range.

  • Geometry change under load is small (typically < 1 % of characteristic length).

  • You want one snapshot of stress, displacement, and reaction at a given operating point.

For periodic loading, use Harmonic (frequency-response) analysis instead. For shock / impact / time-history, use Transient analysis. For the lowest natural frequencies, use Modal analysis (free vibration).

Boundary conditions and loads#

  • Dirichlet (displacement) constraints via Model.fix(nodes=..., dof=...). Pin individual DOFs (dof="UX"), all three translations (dof="ALL"), or specific values for non-zero displacement. See Boundary-condition pitfalls for the rigid-body-mode diagnostic — every static solve needs enough constraints to remove all six rigid-body modes.

  • Concentrated forces and moments via Model.apply_force(node, fx=..., fy=..., fz=..., mx=..., my=..., mz=...). Use accumulate=True when adding multiple contributions to the same node — the default overwrites, which is the recurring “loads disappear” bug from Boundary-condition pitfalls.

  • Distributed loads are applied as nodal-force equivalents via the trapezoid-rule lumping pattern documented in Cantilever under uniformly distributed load — Euler–Bernoulli closed form. A future version will expose apply_pressure directly; for now, lump manually.

The math (one paragraph)#

Linear static solves \(\mathbf{K}\, \mathbf{u} = \mathbf{f}\) for the global displacement vector \(\mathbf{u}\) given the stiffness matrix \(\mathbf{K}\) and external force vector \(\mathbf{f}\). Constrained DOFs partition out via Boundary-condition elimination and the solve factors only the free-DOF block \(\mathbf{K}_{ff}\). Reactions at constrained DOFs follow from the partitioned second block (Newton’s third law).

Full derivation in Variational form and the discretised equations plus Boundary-condition elimination.

Running the solve#

The high-level path on a configured Model:

result = m.solve_static()

That returns a StaticResult carrying:

  • result.displacement(N,) DOF-indexed displacements.

  • result.reaction(N,) reaction forces, nonzero only at constrained DOFs.

  • result.free_mask(N,) bool, True at the DOFs the solver actually computed.

The lower-level solve_static() takes the assembled K + F directly, useful when you need to re-solve with different RHS vectors against the same factorised stiffness. See Static analysis for the backend / threading details.

Post-processing recipes#

Once the solve has run, the standard recipes:

Common gotchas#

  • Under-constrained → enormous displacements. A static solve on a free-floating model returns garbage (the matrix is singular). Run the Boundary-condition pitfalls rigid-body-mode check.

  • Loads applied to the wrong node set. Always sum the recovered ΣR_z reactions and confirm they match -ΣF_applied. See the example above.

  • Apply-force overwrites. Default Model.apply_force(...) overwrites prior loads on the same node + DOF. Pass accumulate=True when summing across multiple sources.

  • Ill-conditioned stiffness from material disparity. Mixing rubber and steel in the same solve hits \(\kappa(K) > 10^9\); see Conditioning and performance.

End-to-end tutorial#

Tutorial 1 walks through the full design-by-analysis loop on a cantilever bracket: Tutorial 1 — Cantilever beam under combined loading.

Verification examples that exercise this analysis type:

Solver mechanics + performance#

For the matrix-partitioning derivation, backend selection, threading, and convergence details, see Static analysis — that page is the solver-engineering deep-dive on the same Model.solve_static() invocation.