Files
OneCAD-Simulation/docs/fem.md
T
15a9ac9157 1D elements I: trusses (rod2, rod3), point masses, rotary inertia, springs and dashpots (#38)
* Model, fem and decks: point masses, rotary inertia, springs, dashpots and rod3 trusses

Model: PointMass and Spring definitions (spring | dashpot; to ground or
two-point; along a degree of freedom, a vector or the line of the ends) on a
set or reference point, resolved per node; ElementKind::Rod3. fem: rod2/rod3
truss kernel on Basis::Line2/Line3 declaring ElementDofs (translations).
Decks: Abaqus T3D3, MASS, ROTARYI, SPRING1/2/A, DASHPOT1/2/A; Nastran CONM1,
CELAS2, CDAMP2; LS-DYNA ELEMENT_MASS/INERTIA and ELEMENT_DISCRETE; what a
format cannot take is reported. Commands model.mass.* and model.spring.*.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Implicit solver: trusses, point masses, rotary inertia and springs on the DofLayout

Trusses declare their translations like solids; point masses, inertia and
springs ask the layout for the kinds they need at their nodes (translations,
rotations), and reference points they use get six degrees of freedom. The
matrix pattern takes rows for those points; dashpots are ignored with a
warning. Existing results are bit-identical (hash-checked).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Explicit solver: trusses, point masses, springs and dashpots on the node-major layout

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: fem.md on trusses and discrete elements in the layout; regenerate features.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: trusses in plane models and corotational explicit trusses

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 21:36:46 -04:00

6.7 KiB
Raw Blame History

FEM foundation: degrees of freedom and shape functions

What every element in crates/fem and every solver builds on. Code: crates/fem/src/dof.rs, crates/fem/src/basis.rs, crates/fem/src/jet.rs.

Degrees of freedom

Every unknown is a pair (node, DofKind):

DofKind Meaning Model / deck number
Ux, Uy, Uz translations along X, Y, Z 1, 2, 3
Rx, Ry, Rz rotations about X, Y, Z 4, 5, 6
P pressure (mixed u–p elements) 8
T temperature 11

The enum order (Ux, Uy, Uz, Rx, Ry, Rz, T, P) is the numbering order within a node. DofKind::from_number / number convert to the 1-based numbers boundary conditions use; from_direction / direction to the structural directions 0–5.

Elements declare their unknowns with ElementDofs { node: DofSet, internal: Internal }: the kinds every node of the element carries (DofSet::TRANSLATIONS for solids, PLANE for 2D elements, STRUCTURAL for shells and beams, TEMPERATURE for heat transfer, translations plus PRESSURE for continuous-pressure mixed elements) and the internal unknowns the element condenses out itself (incompatible_modes, discontinuous pressure), which never reach the global system. An element's matrices are ordered node by node, its node kinds in DofKind order within a node (a shell's 24 × 24 stiffness: node 1 Ux Uy Uz Rx Ry Rz, node 2 …). Solid elements: Formulation::dofs() / Solid::dofs().

Numbering (DofLayout): collect what every node needs with DofLayout::builder() (element(labels, dofs), node(label, kinds) for constraints and boundary conditions that need a kind no element gives, point(label, kinds) for reference points), then build(). A node carries the union of what was asked for. Unknowns are numbered mesh nodes in label order, each node's kinds in DofKind order, then the points in the order added; a slot's unknowns are contiguous, so a node pair is one dense block in the assembled matrix. The same model always gives the same numbering.

Lookups: node(label) / slot(label) (a slot is a position: mesh nodes 0..nodes(), points point_slot(k)), dof(slot, kind), range(slot), kinds(slot), locate(dof) -> (slot, kind), element_dofs(slots, kinds), describe(dof), stride() (uniform layouts).

Trusses declare modelcraft_fem::truss::DOFS (translations, on Basis::Line2/Line3). Point masses, springs and dashpots are not mesh elements: the implicit solver asks the layout for what each needs at its nodes (modelcraft_solver::discrete::needs: translations for a mass or a translational spring, rotations for an inertia or a torsional spring; a reference point they use is a point with six) and finds its unknowns with discrete::dof_of.

Solvers: modelcraft_solver::system::Mesh::layout numbers the implicit system (mesh nodes, then six per coupled reference point); assembly (sparse::add_block(k, layout, slots, kinds, data), add_scalar, add_diagonal) works for any mix of kinds per node, so a solid sharing nodes with a shell adds its translation block into the shell nodes' six-unknown blocks. Boundary conditions and loads address (node, kind): a boundary condition holds the kinds a node carries, and a moment needs rotations. The explicit solver has solid nodes only and checks that its layout is the uniform node-major one (NODE_DOFS = 3).

Shape functions

Basis is every family's interpolation:

Family Reference element Nodes
Line2, Line3 ξ ∈ [−1, 1] ends −1, 1; line3 adds the middle
Tri3, Tri6 ξ, η ≥ 0, ξ + η ≤ 1 corners (0,0), (1,0), (0,1); mid-sides of 1-2, 2-3, 3-1
Quad4, Quad8, Quad9 [−1, 1]² corners counter-clockwise from (−1,−1); mid-sides of 1-2, 2-3, 3-4, 4-1; centre
Tet4, Tet10 unit tetrahedron corners (0,0,0), (1,0,0), (0,1,0), (0,0,1); mid-sides of 1-2, 2-3, 3-1, 1-4, 2-4, 3-4
Wedge6, Wedge15 triangle × ζ ∈ [−1, 1] bottom 1-3, top 4-6; mid-sides of the bottom edges, top edges, vertical edges
Pyramid5 base [−1, 1]² at ζ = 0, apex (0, 0, 1) base corners as quad4, then the apex
Hex8, Hex20, Hex27 [−1, 1]³ corners 1-4 at ζ = −1, 5-8 above; hex20 mid-sides (bottom, top, vertical edges); hex27 face centres in ElementKind::Hex8.faces() order, then the centre

Node orders are the model's (ElementKind::edges() / faces() are the single source): corners, mid-side nodes in edge order, face nodes, body nodes. Basis::of(ElementKind) maps a model element; nodes() gives natural node coordinates; dim(), count(), corners(), degree() (the complete polynomial degree reproduced: 1 for linear families including the rational pyramid, 2 for the quadratic ones).

Evaluation at a natural point xi: [f64; 3] (unused coordinates are zero):

  • eval(xi) -> Shape: values n and first derivatives dn[i] = ∂Nᵢ/∂(ξ, η, ζ). tet4, tet10, wedge6, hex8, hex20 use the hand-written functions of shape.rs (the solid kernels' path).
  • eval2(xi) -> Shape2: adds d2n[i][a][b] = ∂²Nᵢ/∂ξₐ∂ξ_b, from second-order forward-mode automatic differentiation of the same formulas (jet::Jet): exact to round-off.
  • gradients(&shape, xyz) -> Gradients: physical dx[i] = ∇ₓNᵢ = J⁻¹ ∇_ξNᵢ and det J, with J_ab = ∂x_b/∂ξₐ; an inverted or collapsed element is Error::Degenerate.
  • hessians(&shape2, xyz) -> Hessians: physical d2x[i] = J⁻¹ (H_ξ − Σ_b (∂N/∂x_b) X_b″) J⁻ᵀ with X_b″ = Σᵢ x_{i,b} H_ξ(Nᵢ), the map's second derivative (T. J. R. Hughes, The Finite Element Method, 2000, §3.9, §4.2), and laplacian(i). Without the X″ term a field linear in x would have a nonzero Hessian on a curved element.

A family of dimension d maps onto the first d physical coordinates (plane elements in x–y, lines along x). Elements embedded in a higher dimension (shells, beams, trusses in 3D) pass node coordinates in their own local frame.

Verification (basis::tests, dof::tests, jet::tests)

For every family: Kronecker delta at the nodes; partition of unity (Σ N = 1, Σ ∇N = 0, Σ H = 0); exact reproduction of every monomial up to degree() with its gradient and Hessian in natural coordinates; first and second derivatives against central differences; on an affine map, physical gradients, Hessians and Laplacians exact for a complete polynomial of the family's degree; on a curved map, linear fields exact with zero Hessian, and the physical Hessian of a quadratic interpolant consistent with finite differences of its physical gradient. The solver and explicit migration to DofLayout was checked bit for bit: every result of the solver, explicit, engine and io test suites hashes identically before and after.