Force density method
The self-contained FDM form-finding subsystem: nodes, elements, loads, networks, analysis, and translation to/from truss models.
Asap.FDMnode — Type
FDMnode{T}A node in an FDM network: a spatial position and a PER-AXIS degree of freedom. fixity follows the same convention as the core Node (true = free, false = fixed), so a node may be free in some axes and fixed in others — e.g. [false, false, true] prescribes the plan position and lets the height find equilibrium (vault form-finding).
Form-finding note: solve! updates position in place on every free axis — the network geometry IS the analysis result. Reactions are NOT stored here; query them with reaction(network.results, node).
Fields
position::SVector{3,T}: the [x, y, z] position [length]fixity::SVector{3,Bool}: per-axis freedom (true= free)id::Symbol: identifier (optional)index::Int: global index (internal, assigned byprocess!)
Constructors
FDMnode(x, y, z, dof::Bool, id = :node)
FDMnode(pos, dof::Bool, id = :node) # all three axes at once
FDMnode(pos, fixity::AbstractVector{Bool}, id = :node)Asap.FDMelement — Type
FDMelement{T}An element in a Force Density Method network: it connects two nodes with a force density q = N/L [force/length]. The member force follows the geometry: force(el) = q · length(el), always evaluated at the CURRENT node positions.
q is mutable design data — change it freely and re-solve!; assembly reads it fresh every time (see update_q! for the batch version).
Fields
pStart,pEnd::FDMnode{T}: the connected nodesq::T: force density [force/length]id::Symbol: identifier (optional)iStart,iEnd::Int: node indices (internal, assigned byprocess!)index::Int: element index (internal)
Constructors
FDMelement(pointStart, pointEnd, q, id = :element)
FDMelement(points, iStart::Int, iEnd::Int, q, id = :element)
FDMelement(points, indices::Vector{Int}, q, id = :element)Asap.force — Method
force(element::FDMelement) -> TMember axial force at the current geometry: q · L [force] (tension + for positive force densities).
Asap.local_x — Method
local_x(element; unit = true) -> SVector{3}The element's direction vector at the CURRENT node positions (unit by default).
Asap.FDMload — Type
FDMload{T}An external force applied to an FDM node.
Fields
point::FDMnode{T}: the loaded nodeforce::SVector{3,T}: the applied force [Px, Py, Pz] [force]
Constructors
FDMload(point, force)
FDMload(points, i::Int, force)Asap.Network — Type
Network{T}A Force Density Method network: nodes, elements, and loads — pure definition data, mirroring the frame core's three-layer separation. The analysis structure (NetworkCache, built by process!) and the per-solve NetworkResults live in their own layers:
network = Network(nodes, elements, loads)
solve!(network) # geometry updates in place
member_force(network.results, el)
reaction(network.results, anchor)Force densities, anchor positions, and loads are read FRESH at every solve! — mutate them freely and re-solve at the cost of a numeric-only refactorization on the frozen pattern. Adding/removing nodes, elements, or loads, or changing fixity, requires solve!(network; reprocess = true).
Fields
nodes::Vector{FDMnode{T}}elements::Vector{FDMelement{T}}loads::Vector{FDMload{T}}cache::Union{Nothing,NetworkCache{T}}: per-topology analysis structureresults::Union{Nothing,NetworkResults{T}}: forces/reactions of the last solve
Asap.NetworkCache — Type
NetworkCache{T}Everything the FDM solver derives from TOPOLOGY, built once by process! and reused across solves: connectivity, node partitions, and one frozen-pattern AxisSystem per distinct axis partition.
Force densities, node positions (anchors included), and loads are read FRESH from the network at every solve! — change them freely; only adding/removing nodes/elements/loads or changing fixity requires re-processing.
Fields
C::SparseMatrixCSC{Int,Int}: element/node incidence (−1 start, +1 end)N,F::Vector{Int}: fully-free / remaining node indices (uniform split)Naxis,Faxis::Vector{Vector{Int}}: per-axis partitionsmixed::Bool: any node with non-uniform fixity?systems::Vector{AxisSystem{T}}: the solvable systemsxyz::Matrix{T}: [n × 3] working geometry (refreshed from nodes each solve)P::Matrix{T}: [n × 3] load matrix (refreshed from loads each solve)
Asap.process! — Method
process!(network::Network) -> networkBuild the network's analysis structure: assign indices, partition nodes per axis, and freeze one force-density pattern per distinct partition. Call once per topology — geometry, force-density, and load VALUES are picked up fresh by every solve!.
Asap.NetworkResults — Type
NetworkResults{T}Per-solve results of an FDM network, queried through accessors (the solved GEOMETRY lives on the nodes — form-finding's deliverable — but forces and reactions live here):
member_force(res, element)/res.forces[el.index]— axial forceq·L[force] at the equilibrium geometry (tension + for positive q)reaction(res, node)— anchor force on the node's FIXED axes [force] (free-axis components are zero: equilibrium holds there)lengths— member lengths at equilibrium [length]
Asap.member_force — Method
member_force(res::NetworkResults, element::FDMelement) -> TAxial force in element at the solved geometry [force].
Asap.reaction — Method
reaction(res::NetworkResults, node::FDMnode) -> SVector{3}Anchor force on node's fixed axes [force]; zero on free axes.
CommonSolve.solve! — Method
solve!(network::Network; reprocess = false, solver = nothing)Find the network's equilibrium geometry and update the node positions in place (free axes only — fixed coordinates are boundary data). Forces and reactions land in network.results.
Every solve reads the CURRENT force densities, anchor positions, and loads; the force-density pattern is frozen per topology, so assembly is a pure scatter and the factorization reuses its symbolic analysis (numeric-only refactorization — same solver seam as the frame core, so solver accepts any LinearSolve algorithm when LinearSolve.jl is loaded).
FDM equilibrium is separable per coordinate: axes sharing a free/fixed partition solve as one multi-RHS system (uniform networks: a single system for x, y, z); per-axis fixity solves each partition independently.
CommonSolve.solve — Method
solve(network::Network, q::Vector{<:Real}) -> xyzEquilibrium positions for a NEW vector of force densities without mutating the network (same per-axis semantics as solve!; fresh factorization, default backend).
Asap.forces — Method
forces(network::Network) -> VectorMember axial forces q·L at the current geometry [force].
Asap.initial_lengths — Method
initial_lengths(network::Network, E::Real, A::Real)
initial_lengths(network::Network, E::Vector, A::Vector)Required unstressed member lengths so that, at the solved geometry, members of axial stiffness E·A carry exactly the form-found forces q·L [length]. Solve the network first.
Asap.update_q! — Method
update_q!(network::Network, q)
update_q!(network::Network, q::Vector, indices::Vector{Int})Set force densities and re-solve — a convenience for form-finding sweeps (assembly reads q fresh, so this is just assignment plus solve!).
Asap.to_network — Method
to_network(model::Model)Convert a solved (truss) model into an equivalent FDM Network.
Asap.to_truss — Method
to_truss(network::Network, section::AbstractSection)Convert a solved FDM Network into an equivalent truss model with a given section. All fixed nodes are converted into pinned boundary conditions.
Internals
Asap.AxisSystem — Type
AxisSystem{T}One force-density system D · x = b: the free/fixed node partition of one or more coordinate axes (axes with identical partitions share a system, so uniform networks hold a single system solving all three coordinates as one multi-RHS problem).
D = CₙᵀQCₙ is the free-node force-density Laplacian: its sparsity pattern depends only on topology, so it is FROZEN here and numeric assembly is a pure scatter of the current force densities through nzmap — D[i,i] = Σq at node i, D[i,j] = −Σq of members connecting i and j.
Fields
axes::Vector{Int}: which coordinates (1 = x, 2 = y, 3 = z) this system solvesNa,Fa::Vector{Int}: free/fixed node indices of this partitionD::SparseMatrixCSC{T,Int}: the frozen-pattern system matrixnzmap::Vector{NTuple{4,Int}}: per ELEMENT, thenzvalpositions of its (ii, jj, ij, ji) contributions (0 = that end is fixed here)bnd::Vector{Tuple{Int,Int,Int}}: (element index, local row, fixed node index) triples — boundary members contributeq · x_fixedto the RHSfact: the seamFactorizationCache— reused across solves (numeric-only refactorization; any solver backend)