Elements
Frame, truss, and variable elements; end conditions (releases and semi-rigid springs); element geometry queries and stiffness kernels.
Asap.RELEASES — Constant
RELEASESMap from the classical release symbols to their exact end-spring limits, as (start, end) tuples of (kx, kt, ky, kz). These are the same five release types the legacy library implemented as distinct stiffness matrices; here they are just data:
| symbol | start end | far end | classical meaning |
|---|---|---|---|
:fixedfixed | rigid | rigid | fully continuous member (default) |
:fixedfree | rigid | rotations released | hinge at far end |
:freefixed | rotations released | rigid | hinge at start end |
:freefree | rotations released | rotations released | axial-only (truss-like) |
:joist | bending released, torsion kept | same | joist/purlin idealization |
Asap.EndConditions — Type
EndConditions{T<:Real}The pair of EndSprings at the start (e1) and end (e2) of a frame element, in element local coordinates.
Constructed either explicitly from two EndSprings, or from a classical release symbol (see RELEASES):
EndConditions(e1::EndSprings, e2::EndSprings)
EndConditions(release::Symbol; T = Float64)Examples
julia> EndConditions(:fixedfree) # hinge at the far end
julia> EndConditions(EndSprings(Inf, Inf, 5e4, 5e4), # semi-rigid start
rigid_end()) # rigid endAsap.EndSprings — Type
EndSprings{T<:Real}Connection stiffness at ONE end of a frame element, expressed in the element's local coordinate system.
This generalizes the classical binary release: instead of a DOF being either rigidly tied to the node or fully released, each end carries a finite spring stiffness for the four end actions that can meaningfully be released or softened in a line element:
kx: axial spring [force/length] — stiffness of the axial connectionkt: torsional spring [force·length/rad] — twist about the element axisky: rotational spring about the local y axis [force·length/rad] — bending in the local x–z planekz: rotational spring about the local z axis [force·length/rad] — bending in the local x–y plane
The limits recover classical behavior exactly: Inf = rigid connection (the DOF is fully coupled to the node), 0 = ideal release (hinge/slide). Intermediate values model semi-rigid connections — bolted end plates, concrete joint regions with partial fixity, etc. — and are legitimate, differentiable design variables.
Transverse shear releases are deliberately not supported (as in the legacy library): elastic supports are modeled with nodal springs instead.
See also EndConditions, RELEASES.
Asap.pinned_end — Method
pinned_end(T = Float64) -> EndSprings{T}The classical hinge: axial rigid, all rotational stiffnesses zero (torsion and both bending rotations released).
Asap.release_symbol — Method
release_symbol(ends::EndConditions) -> Union{Symbol, Nothing}If the end conditions exactly match one of the classical RELEASES, return its symbol; otherwise return nothing (a genuinely semi-rigid connection).
Asap.rigid_end — Method
rigid_end(T = Float64) -> EndSprings{T}The rigid connection: all four end springs infinite — the classical fully fixed element end.
Asap.AbstractElement — Type
AbstractElement{T<:Real}Supertype of all structural elements.
An element is pure definition data — which nodes it connects, its section, its end conditions. All analysis state of the legacy library (cached stiffness, transformation, global DOF ids, forces) lives elsewhere: the analysis structure in the model's AnalysisCache, results in LinearResults.
The element interface
Every element type implements this contract; the analysis layer is written against it, so adding an element type (or, later, mass and geometric stiffness) touches no assembly code.
Topology:
nodes(el)— tuple of connectedNodesn_internal_dofs(el)— extra non-nodal DOFs the element requests (0 for primitives; used by super-elements likeVariableElement)ndofs(el)— total DOF count =6 × length(nodes(el)) + n_internal_dofs(el)dof_signature(el)—NTuple{ndofs, Bool}: which of its DOF slots the element actually couples stiffness to. Drives global DOF activity: a slot no element touches never enters the solve. A truss element's signature is true only on translations — this is what removes the rotational DOFs of truss-only nodes, and the free-torsion singularity of hinge-released members, structurally.
Physics (pure kernels of positions and properties — shared by the in-place and AD assembly paths):
stiffness(el, x1, x2)— element stiffness in GLOBAL coordinates as anSMatrix{n,n},n = ndofs(el).x1,x2are the global position vectors of the element's endpoints — for everyday use,stiffness(el, el.nodeStart.position, el.nodeEnd.position). Positions are explicit arguments (rather than read from the nodes) so the kernel is a pure function: in the differentiable path they come fromModelState.Xand gradients flow through the same implementation.
Future hooks (declared now so dynamics/nonlinearity reuse the assembly machinery; implementations arrive with those analyses):
mass(el, x1, x2)geometric_stiffness(el, x1, x2, N)
Asap.n_internal_dofs — Method
n_internal_dofs(el) -> IntNumber of non-nodal (internal) DOFs the element requests from the model's global DOF space. Zero for primitive elements; super-elements with interior joints (e.g. VariableElement) return 6 × (number of interior joints).
Asap.ndofs — Method
ndofs(el) -> IntTotal number of DOF slots the element maps to: six per connected node plus any internal DOFs.
Asap.FrameElement — Type
FrameElement{T, S<:AbstractSection{T}} <: AbstractElement{T}A 3D frame (beam-column) element: carries axial force, biaxial bending, transverse shear, and torsion between two nodes. The successor of the legacy Element — renamed for what it is, and stripped of all analysis state (stiffness, transformation, DOF ids, and forces live in the analysis cache and results objects, not on the element).
Formulated as an Euler-Bernoulli member with end springs (EndConditions): classical releases are the spring limits, and finite values model semi-rigid connections. Its stiffness couples all 12 nodal DOFs except those decoupled by zero end springs — reflected in its dof_signature, so fully released rotations never poison the global system.
Fields
nodeStart::Node{T},nodeEnd::Node{T}: connected nodes; the local x axis runs from start to endsection::S: cross-section (anyAbstractSection); mutable so section swaps in design iteration don't rebuild the elementends::EndConditions{T}: connection stiffnesses at both ends, in local coordinatesrollangle::T: roll angle [rad] — rotation of the section about the element axis. Defaultπ/2(legacy convention: local strong axis resists vertical load for typical horizontal members)id::Symbol: user tag for group queriesindex::Int: position in the model's element vector; set byprocess!
Constructors
FrameElement(nodeStart, nodeEnd, section, id = :element;
release = :fixedfixed, rollangle = π/2)
FrameElement(nodeStart, nodeEnd, section, ends::EndConditions, id; rollangle = π/2)Examples
julia> beam = FrameElement(n1, n2, sec, :girder)
julia> hinged = FrameElement(n1, n2, sec; release = :fixedfree)
julia> semirigid = FrameElement(n1, n2, sec,
EndConditions(EndSprings(Inf, Inf, 5e4, 5e4), rigid_end()), :connection)Asap.TrussElement — Type
TrussElement{T, S<:AbstractSection{T}} <: AbstractElement{T}An axial-only (two-force) element: resists elongation along its axis and nothing else. Its dof_signature touches only the six nodal translations — rotational DOF slots are simply never marked active, which is how truss-only models solve a translations-only system and how truss and frame elements mix freely in one model.
Fields
nodeStart::Node{T},nodeEnd::Node{T}: connected nodessection::S: cross-section — onlyEAandρAare ever queried, so an axial-onlySection(material, A)sufficesid::Symbol: user tagindex::Int: set byprocess!
Constructor
TrussElement(nodeStart, nodeEnd, section, id = :element)Asap.dof_signature — Method
dof_signature(el::FrameElement) -> NTuple{12,Bool}Which of the element's 12 nodal DOF slots its GLOBAL stiffness can couple.
Activity is decided per rotation block, not per slot: the local-to-global rotation mixes torsion and bending rotations within a node's 3-slot rotation block, so a single released local rotation still couples all three global rotational DOFs through the other two. A node's rotation block is inactive only when the element's entire local rotational row block at that end is zero — i.e. both bending springs released AND torsion released (torsion releases whenever either end's torsional spring is zero, since a torsion chain with a free end restrains nothing).
Consequences: a :fixedfree element leaves its far node's rotations entirely untouched (no singular mode if nothing else connects there — Keith's hinge/torsion pain point, solved structurally); a :joist element keeps torsional coupling and therefore marks whole rotation blocks active. Translations are always coupled.
Asap.dof_signature — Method
dof_signature(el::TrussElement) -> NTuple{12,Bool}Truss elements couple only the translational slots of their two nodes; all six rotational slots are false and never activate global DOFs.
Asap.endpoints — Method
endpoints(el) -> (SVector{3}, SVector{3})The element's start and end positions.
Asap.local_frame — Method
local_frame(el; tol = 1e-6) -> SMatrix{3,3}The element's local coordinate frame (rows = local x, y, z in global coordinates). Frame elements use their roll angle rollangle; truss elements have no roll (rollangle = 0).
Asap.midpoint — Method
midpoint(el) -> SVector{3}Position of the element's midpoint.
Asap.stiffness — Method
stiffness(el::FrameElement, x1, x2) -> SMatrix{12,12}Global-coordinate stiffness of the frame element with its end positions passed explicitly (the fast path passes node positions; the AD path passes entries of a differentiable state). Delegates to the pure frame_stiffness kernel.
Asap.stiffness — Method
stiffness(el::TrussElement, x1, x2) -> SMatrix{12,12}Global-coordinate stiffness of the truss element, embedded in the full 12-slot (two-node) DOF layout with zeros on all rotational slots. The active 6×6 translational block is the pure truss_stiffness kernel; assembly consults dof_signature so the zero rotational rows are never scattered into the global matrix.
Asap.VariableElement — Type
VariableElement{T} <: AbstractElement{T}A super-element: one user-facing member whose cross-section varies along its length as a chain of prismatic segments. The successor of the legacy BridgeElement-era workflow of manually shattering members — here the model is never mutated and no phantom nodes appear in model.nodes.
Interior joints between segments become internal degrees of freedom: process! allocates them 6 slots each in the global DOF space after all nodal slots. Assembly, solving, and recovery treat them exactly like nodal DOFs (they are exact, not condensed — which keeps future mass and geometric stiffness formulations exact too), but they are invisible to the user: the element is queried as a single piece.
Fields
nodeStart::Node{T},nodeEnd::Node{T}: the member's real end nodes; interior joints lie on the straight line between themsections::Vector{<:AbstractSection{T}}: one section per segment, ordered start → endbreaks::Vector{T}: interior joint positions as strictly increasing fractions ∈ (0, 1) of the member length;length(breaks) == length(sections) - 1ends::EndConditions{T}: end conditions at the OUTER ends only (interior joints are rigid by construction)rollangle::T: roll angle, shared by all segments [rad]id::Symbol,index::Int: as for other elementsinternal_offset::Int: first global DOF slot of the interior-joint block (assigned byprocess!; internal bookkeeping)
Constructors
VariableElement(nodeStart, nodeEnd, sections, breaks, id = :variable;
release = :fixedfixed, rollangle = π/2)
VariableElement(nodeStart, nodeEnd, sections, id; ...) # equal segmentsElement-DOF layout
Slots 1–6: start node; 7–12: end node; 13 onward: interior joints in order. segment_slots maps each segment's 12 DOFs into this layout.
Examples
julia> haunched = VariableElement(n1, n2, [deep, mid, shallow], [0.2, 0.5], :girder)
julia> stepped = VariableElement(n1, n2, [big, small], :column) # break at 0.5Asap.dof_signature — Method
dof_signature(el::VariableElement) -> NTupleBlockwise activity over the element's full slot layout: translations always active; outer rotation blocks follow the same blockwise logic as FrameElement (using the outer end springs; the torsion chain releases if either outer torsional spring is zero); interior joint blocks are fully active (rigidly connected on both sides).
Asap.locate_segment — Method
locate_segment(el, t) -> (s, τ)Map a fraction t ∈ [0, 1] of the whole member to its segment index s and the local fraction τ ∈ [0, 1] within that segment — the resolution step behind unified queries like moment_at(el, 0.5).
Asap.n_segments — Method
n_segments(el) -> IntNumber of prismatic segments (1 for primitive elements).
Asap.segment_fractions — Method
segment_fractions(el) -> Vector{T}Segment boundary positions as fractions of the member length: [0, breaks..., 1].
Asap.stiffness — Method
stiffness(el::VariableElement, sections, x1, x2) -> MatrixGlobal stiffness over the element's full DOF layout: the sum of each segment's frame_stiffness embedded at its segment_slots. Interior joint positions derive from the end positions (xᵢ = x1 + tᵢ(x2−x1)), so geometry gradients flow through interior joints automatically. Built as a pure sum of selector embeddings (AD-transparent); returns a dense Matrix since the size varies with segment count.
Asap.local_frame — Method
local_frame(x1, x2, rollangle; tol = 1e-6) -> SMatrix{3,3}Rotation matrix Λ from global to element-local coordinates for a line element from position x1 to x2 with roll angle rollangle.
The rows of Λ are the element's local unit axes expressed in global coordinates:
- row 1: local x — along the element, from start to end
- row 2: local y — a transverse axis; with
rollangle = π/2(the constructor default) it aligns with "strong-axis bending carries vertical load" for typical members - row 3: local z — completes the right-handed triad
rollangle [rad] rotates the local y–z pair about the element axis — the "roll" of the section. tol triggers the special-case frame for members parallel to the global Y axis, where the general formula degenerates (its denominator √(CXx² + CZx²) → 0).
The math reproduces the legacy R! exactly (same branch tolerance, same special case), so transformation matrices match the pinned characterization oracles to machine precision.
Internal kernels
Unexported element kernels referenced by the docstrings above:
Asap.frame_stiffness — Function
frame_stiffness(section, ends, x1, x2, rollangle) -> SMatrix{12,12}GLOBAL-coordinate stiffness of a 3D frame element: the local local_stiffness rotated through local_frame via transform_to_global. Pure function of positions and properties — the single kernel shared by the in-place and AD assembly paths.
Asap.truss_stiffness — Function
truss_stiffness(section, x1, x2) -> SMatrix{6,6}GLOBAL-coordinate stiffness of an axial-only (truss) element between positions x1 and x2: EA/L · [nnᵀ −nnᵀ; −nnᵀ nnᵀ] where n is the unit vector along the element.
Returned directly in global coordinates on the 6 translational DOFs (3 per node) — a truss element never touches rotational DOF slots, which is what keeps those slots inactive (and out of the solve) in truss-only models.
Asap.local_stiffness — Function
local_stiffness(section, L, ends::EndConditions) -> SMatrix{12,12}Element stiffness matrix in LOCAL coordinates for a 3D frame element of length L with cross-section section and end conditions ends.
Composed from four independent actions, each consuming one rigidity accessor of the section contract:
- axial (
EA): series combination with the end axial springs - torsion (
GJ): series combination with the end torsional springs - bending in the local x–y plane (
EIx): Monforton–Wu block with fixity factors from the endkzrotational springs - bending in the local x–z plane (
EIy): Monforton–Wu block (sign-flipped couplings) with fixity factors from the endkysprings
The five classical releases reproduce the legacy closed-form matrices exactly (characterization-tested); any other spring values give a semi-rigid member.
Asap.transform_to_global — Function
transform_to_global(k_local::SMatrix{12,12}, Λ::SMatrix{3,3}) -> SMatrix{12,12}Rotate an element stiffness matrix from local to global coordinates: K = Rᵀ k R where R = blockdiag(Λ, Λ, Λ, Λ).
Computed blockwise over the sixteen 3×3 sub-blocks (K[a,b] = Λᵀ k[a,b] Λ) — algebraically identical to the dense triple product but cheaper, and pure SMatrix arithmetic so it is stack-allocated and AD-transparent.
Asap.segment_slots — Function
segment_slots(el, s) -> Vector{Int}The 12 element-DOF layout slots of segment s (its start block then end block), in the element's slot layout (1–6 start node, 7–12 end node, 13+ interior joints).