Asap is...
- the anti-SAP (2000)
- results as Soon As Possible
- another Structural Analysis Package
Designed first-and-foremost for information-rich data structures and ease of querying, but always with performance in mind.
See also: AsapToolkit, AsapOptim, AsapHarmonics.
Asap.jl is now a registered Julia package. Install through package mode in the REPL:
pkg> add Asap
or
using Pkg
Pkg.Add("Asap")
When using or extending this software for research purposes, please cite using the following:
@software{lee_2024_10581560,
author = {Lee, Keith Janghyun},
title = {Asap.jl},
month = jan,
year = 2024,
publisher = {Zenodo},
version = {v0.1},
doi = {10.5281/zenodo.10581560},
url = {https://doi.org/10.5281/zenodo.10581560}
}
Or find a pre-written citation in the style of your choice here (see the Citation box on the right side). E.g., for APA:
Lee, K. J. (2024). Asap.jl (v0.1). Zenodo. https://doi.org/10.5281/zenodo.10581560
See AsapToolkit.jl for even more utility and post-processing functions.
A structural model is defined by:
model = Model(nodes, elements, loads)
and solved via:
solve!(model)
Which finds the unknown nodal displacement field,
-
$S$ is the global stiffness matrix (often called$K$ ) -
$P$ is the global external load vector -
$P_f$ is the fixed end forces induced by element loads (such as a line load on an element)
mutable struct Node <: AbstractNode
position::Vector{Float64}
dof::Vector{Bool}
nodeID::Int64
globalID::Vector{Int64}
reaction::Vector{Float64}
displacement::Vector{Float64}
id::Symbol
end
We begin with the primary information carrier for structural analysis: nodes with n independent degrees of freedom (DOF). They are defined by a spatial position in
node = Node([0, 15.5, 12.0], [false, false, false, true, true, true])
This defines a node at
Some common boundary conditions are provided to you as symbols to use in the constructor:
node = Node([0.,0.,0.], :free) # all free DOFs
node = Node([0.,0.,0.], :fixed) # all fixed DOFs
node = Node([0.,0.,0.], :xfree) # all DOFs are fixed except Tx. You can also do :yfree or :zfree
node = Node([0.,0.,0.], :xfixed) # all DOFs are free except Tx. You can also do :yfixed or :zfixed
Nodes can also include an optional identifier represented as a symbol:
pin_support = Node(zeros(3), :pinned, :pinsupport)
roller_support = Node([15.1, 0, 0], :xfree, :rollersupport)
free_nodes = [Node(rand(3), :free, :freenodes) for _ = 1:10]
nodes = [pin_support; roller_support; free_nodes]
This allows you to index into a vector of nodes using the identifier:
all_free_nodes = nodes[:freenodes] #returns a vector of nodes with the :freenode identifier
Or find the indices of nodes in a vector of nodes that have a given id:
i_free_nodes = findall(nodes, :freenodes)
mutable struct Element{R<:Release} <: FrameElement{R}
section::Section #cross section
nodeStart::Node #start node
nodeEnd::Node #end position
elementID::Int64
globalID::Vector{Int64} #element global DOFs
length::Float64 #length of element
K::Matrix{Float64} # stiffness matrix in GCS
Q::Vector{Float64} # fixed end forces in GCS
R::Matrix{Float64} # transformation matrix
Ψ::Float64 #roll angle
LCS::Vector{Vector{Float64}} #local coordinate frame (X, y, z)
forces::Vector{Float64} #elemental forces in LCS
id::Symbol #optional identifier
end
Elements are defined by their start and end nodes, a cross-section, and an optional identifier.
A section defines the mechanical and material properties of an element:
ibeam_section = Section(A, E, G, Ix, Iy, J)
where:
A
: areaE
: Young's ModulusG
: Shear ModulusIx
: Moment of inertia in strong axis (often denoted asIz
in other FEA programs)Iy
: Moment of inertia in weak axisJ
: Torsional constant
It is up to you to ensure unit consistency.
You can then define an element via:
element = Element(pin_support, roller_support, ibeam_section)
element_with_id = Element(pin_support, rand(free_nodes), ibeam_section, :randomelement)
Elements have a default roll angle with respect to its longitudinal axis of
element.Ψ = pi
Elements can have partial DOF releases to decouple nodal displacements from element end displacements. This is the process of adding hinges to one or both ends of the beam. You can do this in the construction of an element through the optional argument release
:
released_element = Element(pin_support, roller_support, ibeam_section; release = :freefixed)
By default, no releases are performed (i.e., release = :fixedfixed
). You can choose between:
:freefixed
create a hinge in the beginning node of the element:fixedfree
create a hinge in the ending node of the element:freefree
create hinges on both ends of the element:joist
create hinges on both ends of the element with the exception of torsional DOFs.
Loads can be applied to nodes and elements.
A NodeForce
is defined on a node with a force vector:
load1 = NodeForce(free_nodes[1], [0., 0., -150.0])
A NodeMoment
is defined on a node with a moment vector (
load2 = NodeForce(free_nodes[5], [40., 0., 0.])
A LineLoad
is defined on an element with a force vector, whose magnitude indicates the length-normalized force value,
snow_load = LineLoad(element, [0., 0., -10.])
A PointLoad
is defined on an element with a normalized position
sideways_load = PointLoad(element, 0.25, [20.0, 0., 0.])
mutable struct Model{E,L} <: AbstractModel
nodes::Vector{Node}
elements::Vector{E}
loads::Vector{L}
nNodes::Int64
nElements::Int64
DOFs::Vector{Bool} #vector of DOFs
nDOFs::Int64
freeDOFs::Vector{Int64} #free DOF indices
fixedDOFs::Vector{Int64}
S::SparseMatrixCSC{Float64,Int64} # global stiffness
P::Vector{Float64} # external loads
Pf::Vector{Float64} # element end forces
u::Vector{Float64} # nodal displacements
reactions::Vector{Float64} # reaction forces
compliance::Float64 #structural compliance
tol::Float64
processed::Bool
end
A model is assembled from a collection of nodes, elements, and loads:
nodes = [pin_support; roller_support; free_nodes]
elements = [element, released_element]
loads = [load1, load2, snow_load, sideways_load]
model = Model(nodes, elements, loads)
The primary unknown field we are trying to find is u
, the vector of all nodal DOFs (in order of assembly) in which equilibrium holds. We can find this via:
solve!(model)
(Note that in this nonsensical example, this will result in a singular error).
You can access the solved field via:
u = model.u
Or directly from the populated fields in the nodes:
node2_displacement = model.nodes[2].displacement
If a node has a restrained DOF, you can find its reaction from:
roller_reaction_forces = roller_support.reaction
You can also find the end forces acting on an element via:
element_forces = model.elements[2].forces
Which gives a vector:
If you have a new set of loads, directly get the corresponding displacement via:
u_new = solve!(model, new_loads)
Or replace the vector of loads associated with the model and solve in place via:
solve!(model, new_loads)
If you change a value, such as the position of a node, reprocess the fields before solving by:
#change 1
model.nodes[2].position .+= [5, 0, 0]
solve!(model;reprocess = true)
For truss structures, with only 3 translational DOFs per node, there are separate data structures for TrussNode
, TrussElement
, TrussSection
, and TrussModel
, which can be defined similarily as above except:
TrussNode
s are constructed using only a length 3 vector of booleans if you are explicitly defining the DOF restrictions:TrussNode(rand(3), [true, true, false])
.TrussSection
s only require the area and length,TrussSection(A, E)
. You can use a regularSection
to define aTrussElement
.TrussElement
s do not have releases or roll angles. By definition they are equivalent toElement(...; release = :freefree)
- Only
NodeForce
s can be applied as loads forTrussModel
s.
mutable struct TrussNode <: AbstractNode
position::Vector{Float64}
dof::Vector{Bool}
nodeID::Int64
globalID::Vector{Int64}
reaction::Vector{Float64}
displacement::Vector{Float64}
id::Symbol
end
mutable struct TrussElement <: AbstractElement
section::Union{TrussSection,Section} #cross section
nodeStart::TrussNode #start position
nodeEnd::TrussNode #end position
elementID::Int64
globalID::Vector{Int64} #element global DOFs
length::Float64 #length of element
K::Matrix{Float64} # stiffness matrix in GCS
R::Matrix{Float64} # transformation matrix
forces::Vector{Float64} #elemental forces in LCS
Ψ::Float64
LCS::Vector{Vector{Float64}}
id::Union{Symbol, Nothing} #optional identifier
end
mutable struct TrussModel <: AbstractModel
nodes::Vector{TrussNode}
elements::Vector{TrussElement}
loads::Vector{NodeForce}
nNodes::Int64
nElements::Int64
DOFs::Vector{Bool} #vector of DOFs
nDOFs::Int64
freeDOFs::Vector{Int64} #free DOF indices
fixedDOFs::Vector{Int64}
S::SparseMatrixCSC{Float64,Int64} # global stiffness
P::Vector{Float64} # external loads
u::Vector{Float64} # nodal displacements
reactions::Vector{Float64} # reaction forces
compliance::Float64 #structural compliance
tol::Float64
processed::Bool
end