-
Notifications
You must be signed in to change notification settings - Fork 0
ch2_theory.md
This chapter presents the element formulation, global assembly, solution procedures, stress recovery, and the optimisation algorithm implemented in ShepherdLab LW FEA-OPT.
Each node carries three degrees of freedom (DOFs):
where u_x and u_y are translational displacements and θ is the in-plane rotation. A single element connecting nodes i and j therefore has a 6×1 displacement vector:
In the local coordinate system aligned with the element axis, the standard 2-D Euler–Bernoulli frame stiffness matrix is:
where E is the elastic modulus, A = w·t is the cross-sectional area, I = w·t³/12 is the second moment of area for a rectangular section of width w and thickness t, and L is the element length.
All local stiffness matrices are built simultaneously as a (M, 6, 6) NumPy array using vectorised operations, avoiding per-element Python loops.
An element oriented at angle α (measured counter-clockwise from the global x-axis) is rotated into the global frame by:
The global element stiffness matrix is then:
The batched matrix product for all elements is computed as a single (M, 6, 6) operation using NumPy's @ operator on 3-D arrays.
Rigid elements (e.g. attachment fixtures) are modelled using a stiffness penalty method: the elastic modulus is multiplied by a large factor β (default 10²), giving E_rigid = β · E. This is simpler than Lagrange multiplier approaches and integrates directly with the standard assembly procedure.
All elements use a rectangular cross-section:
where w is the (constant) out-of-plane width and t is the thickness. During optimisation, only t varies; w remains fixed.
The global stiffness matrix is assembled from element contributions by the standard direct stiffness method:
where L⁽ᵉ⁾ is the Boolean localisation (scatter) matrix that maps the six element DOFs to the global DOF vector. In practice, the assembly is implemented by building vectorised row, column, and value arrays from all elements simultaneously and constructing a scipy.sparse.csr_matrix in a single call.
The global system K u = P is partitioned into free (subscript f) and fixed (subscript c) DOFs:
For prescribed zero displacements (
which is solved by scipy.sparse.linalg.spsolve.
For problems with large displacements, the equilibrium equation becomes nonlinear because the element geometry (lengths and orientations) changes with deformation. LW FEA-OPT uses an incremental load-stepping scheme with Newton–Raphson iterations at each step.
The total load P is divided into N equal increments:
At load step n, the goal is to find displacements u such that the internal forces balance the external forces:
The internal force vector is assembled element-by-element:
where T⁽ᵉ⁾ and k⁽ᵉ⁾ are evaluated at the current deformed configuration. The assembly uses np.einsum for batched matrix–vector products and np.add.at for scatter-addition into the global vector.
At each Newton–Raphson iteration k:
- Update element geometry (lengths, orientations) from the current displacement field.
- Assemble the tangent stiffness matrix K_t = Σ_e T⁽ᵉ⁾ᵀ k⁽ᵉ⁾ T⁽ᵉ⁾.
- Compute the residual R⁽ᵏ⁾ = f_ext − f_int⁽ᵏ⁾.
- Solve for the displacement increment: K_{t,ff} Δu_f = R_f⁽ᵏ⁾.
- Update: u_f⁽ᵏ⁺¹⁾ = u_f⁽ᵏ⁾ + Δu_f.
Three criteria are checked simultaneously:
Convergence is declared when the force criterion is satisfied and either the displacement or energy criterion is met. On the first iteration, only the force criterion is used (the displacement increment has no meaningful predecessor).
The default tolerances are ε_f = 10⁻², ε_u = 10⁻⁶, and ε_E = 10⁻⁶.
After solving for displacements, member end forces are recovered in local coordinates:
giving force components f_local = [N_i, V_i, M_i, N_j, V_j, M_j]ᵀ at the start (i) and end (j) nodes. This is computed for all elements simultaneously using np.einsum.
Stresses at each node of an element are:
where the shear stress uses the rectangular-section maximum (at the neutral axis). The combined von Mises stress is:
The factor of safety (FOS) for element e is:
The optimiser implements a fully stressed design (FSD) approach, which iteratively adjusts element thicknesses so that every element operates near its allowable stress. The result is a more uniform stress distribution and, typically, a lighter structure.
At iteration k, the thickness of element e is updated as:
where σ⁽ᵏ⁾_e is the maximum von Mises stress in element e at the current iteration, σ_allow = σ_ult / FOS_target, and α ∈ (0, 1] is a damping exponent that controls aggressiveness.
Choosing α: Smaller values of α produce more cautious updates and are less likely to oscillate, at the cost of slower convergence. A typical range is 0.3–0.5. For stiff structures, α = 0.4 works well; for flexible trusses, α = 0.3 may be more stable.
To prevent oscillation, the relative change per iteration is clamped:
where δ is the move limit (default 0.2, i.e. ±20% per iteration).
After applying the move limit, thickness is clamped to user-specified bounds:
Setting a small t_min (e.g. 0.2 mm) allows elements to effectively vanish, enabling topology-like optimisation where load paths emerge organically.
Convergence is declared when the maximum relative thickness change across all optimisable elements falls below a tolerance ε_t:
The quality of the optimised design is measured by the coefficient of variation (CoV) of the maximum element stresses:
A lower CoV indicates more uniform material utilisation.
To enable topology-like optimisation of truss structures, each original member can be subdivided into n smaller frame elements using subdivide_truss. This creates n−1 intermediate nodes per member, uniformly spaced along the original member axis.
The fine mesh then allows the thickness optimiser to vary material along each member, not just between members. Combined with a small t_min, the optimiser can effectively remove material from low-stress regions, producing organic, topology-optimised shapes.
The member_map output tracks which original member each sub-element belongs to, which is useful for grouping, colouring, and interpreting results.