Elements

Frame, truss, and variable elements; end conditions (releases and semi-rigid springs); element geometry queries and stiffness kernels.

Asap.RELEASESConstant
RELEASES

Map 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:

symbolstart endfar endclassical meaning
:fixedfixedrigidrigidfully continuous member (default)
:fixedfreerigidrotations releasedhinge at far end
:freefixedrotations releasedrigidhinge at start end
:freefreerotations releasedrotations releasedaxial-only (truss-like)
:joistbending released, torsion keptsamejoist/purlin idealization
source
Asap.EndConditionsType
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 end
source
Asap.EndSpringsType
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 connection
  • kt: torsional spring [force·length/rad] — twist about the element axis
  • ky: rotational spring about the local y axis [force·length/rad] — bending in the local x–z plane
  • kz: 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.

source
Asap.pinned_endMethod
pinned_end(T = Float64) -> EndSprings{T}

The classical hinge: axial rigid, all rotational stiffnesses zero (torsion and both bending rotations released).

source
Asap.release_symbolMethod
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).

source
Asap.rigid_endMethod
rigid_end(T = Float64) -> EndSprings{T}

The rigid connection: all four end springs infinite — the classical fully fixed element end.

source
Asap.AbstractElementType
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 connected Nodes
  • n_internal_dofs(el) — extra non-nodal DOFs the element requests (0 for primitives; used by super-elements like VariableElement)
  • 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 an SMatrix{n,n}, n = ndofs(el). x1, x2 are 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 from ModelState.X and 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)
source
Asap.n_internal_dofsMethod
n_internal_dofs(el) -> Int

Number 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).

source
Asap.ndofsMethod
ndofs(el) -> Int

Total number of DOF slots the element maps to: six per connected node plus any internal DOFs.

source
Asap.FrameElementType
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 end
  • section::S: cross-section (any AbstractSection); mutable so section swaps in design iteration don't rebuild the element
  • ends::EndConditions{T}: connection stiffnesses at both ends, in local coordinates
  • rollangle::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 queries
  • index::Int: position in the model's element vector; set by process!

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)
source
Asap.TrussElementType
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 nodes
  • section::S: cross-section — only EA and ρA are ever queried, so an axial-only Section(material, A) suffices
  • id::Symbol: user tag
  • index::Int: set by process!

Constructor

TrussElement(nodeStart, nodeEnd, section, id = :element)
source
Asap.dof_signatureMethod
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.

source
Asap.dof_signatureMethod
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.

source
Asap.endpointsMethod
endpoints(el) -> (SVector{3}, SVector{3})

The element's start and end positions.

source
Asap.local_frameMethod
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).

source
Asap.stiffnessMethod
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.

source
Asap.stiffnessMethod
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.

source
Asap.VariableElementType
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 them
  • sections::Vector{<:AbstractSection{T}}: one section per segment, ordered start → end
  • breaks::Vector{T}: interior joint positions as strictly increasing fractions ∈ (0, 1) of the member length; length(breaks) == length(sections) - 1
  • ends::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 elements
  • internal_offset::Int: first global DOF slot of the interior-joint block (assigned by process!; internal bookkeeping)

Constructors

VariableElement(nodeStart, nodeEnd, sections, breaks, id = :variable;
                release = :fixedfixed, rollangle = π/2)
VariableElement(nodeStart, nodeEnd, sections, id; ...)   # equal segments

Element-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.5
source
Asap.dof_signatureMethod
dof_signature(el::VariableElement) -> NTuple

Blockwise 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).

source
Asap.locate_segmentMethod
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).

source
Asap.n_segmentsMethod
n_segments(el) -> Int

Number of prismatic segments (1 for primitive elements).

source
Asap.segment_fractionsMethod
segment_fractions(el) -> Vector{T}

Segment boundary positions as fractions of the member length: [0, breaks..., 1].

source
Asap.stiffnessMethod
stiffness(el::VariableElement, sections, x1, x2) -> Matrix

Global 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.

source
Asap.local_frameMethod
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.

source

Internal kernels

Unexported element kernels referenced by the docstrings above:

Asap.truss_stiffnessFunction
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.

source
Asap.local_stiffnessFunction
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 end kz rotational springs
  • bending in the local x–z plane (EIy): Monforton–Wu block (sign-flipped couplings) with fixity factors from the end ky springs

The five classical releases reproduce the legacy closed-form matrices exactly (characterization-tested); any other spring values give a semi-rigid member.

source
Asap.transform_to_globalFunction
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.

source
Asap.segment_slotsFunction
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).

source