diff --git a/src/Analyses.jl b/src/Analyses.jl index 6e59eb0..66da183 100644 --- a/src/Analyses.jl +++ b/src/Analyses.jl @@ -28,6 +28,24 @@ function solve(model::Model, analysis::O1EAnalysis) for (i, element) in enumerate(values(model.elements)) e_ID_mapping[element.ID] = i end + + # Assemble the global elastic stiffness matrix: + K_e = _assemble_K_e(model, n_ID_mapping) + + # Assemble the global force vector: + F = _assemble_F(model, n_ID_mapping) + + # Assemble the global fixed-end force vector: + P = _assemble_P(model, n_ID_mapping) + + # Get the partition indices: + indices_f, indices_s = _get_partition_indices(model, n_ID_mapping) + + # Solve for the displacements: + U_f = K_e[indices_f, indices_f] \ (F[indices_f] - P[indices_f]) + + # Return the displacements: + return U_f end function _assemble_K_e(model::Model, n_ID_mapping::Dict{Int, Int}) @@ -76,4 +94,74 @@ function _assemble_K_g(model::Model, n_ID_mapping::Dict{Int, Int}) # Return the global geometric stiffness matrix: return K_g +end + +function _assemble_F(model::Model, n_ID_mapping::Dict{Int, Int}) + if isempty(model.concentrated_loads) + F = zeros(6 * length(model.nodes)) + else + # Preallocate the global force vector: + T = promote_type([eltype(concentrated_load) for concentrated_load in values(model.concentrated_loads)]...) + F = zeros(T, 6 * length(model.nodes)) + + # Assemble the global force vector: + for concentrated_load in model.concentrated_loads + # Extract the internal node ID of a node: + node_ID_i = n_ID_mapping[concentrated_load.first] + + range_i = (6 * (node_ID_i - 1) + 1):(6 * node_ID_i) + + @inbounds F[range_i] += concentrated_load.second + end + end + + # Return the global force vector: + return F +end + +function _assemble_P(model::Model, n_ID_mapping::Dict{Int, Int}) + if isempty(model.p_g) + P = zeros(6 * length(model.nodes)) + else + # Preallocate the global fixed-end force vector: + T = promote_type([eltype(p_g) for p_g in values(model.p_g)]...) + P = zeros(T, 6 * length(model.nodes)) + + # Assemble the global fixed-end force vector: + for p_g in model.p_g + # Extract the internal node ID of a node: + node_ID_i = n_ID_mapping[p_g.first] + + range_i = (6 * (node_ID_i - 1) + 1):(6 * node_ID_i) + + @inbounds P[range_i] += p_g.second + end + end + + # Return the global fixed-end force vector: + return P +end + +function _get_partition_indices(model::Model, n_ID_mapping::Dict{Int, Int}) + # Preallocate: + indices_f = Int[] # List of free DOFs + indices_s = Int[] # List of supported DOFs + + # Get the partition indices: + for node in values(model.nodes) + node_tag_i = n_ID_mapping[node.ID] + + if haskey(model.supports, node.ID) # Check if the node's DOFs are fixed + for i in 1:6 + model.supports[node.ID][i] ? push!(indices_s, 6 * (node_tag_i - 1) + i) : push!(indices_f, 6 * (node_tag_i - 1) + i) + end + else + for i in 1:6 + push!(indices_f, 6 * (node_tag_i - 1) + i) + end + end + end + + # Return the partition indices: + return indices_f, indices_s end \ No newline at end of file diff --git a/src/Elements.jl b/src/Elements.jl index 8e05c34..aa29ec4 100644 --- a/src/Elements.jl +++ b/src/Elements.jl @@ -54,14 +54,18 @@ struct Element{CTI<:Real, CTJ<:Real, MPT<:Real, SPT<:Real} # ADDITIONAL ELEMENT INFORMATION THAT CAN BE PRECOMPUTED: "Length of the element, ``L``" L ::Real - "Local-to-global sub-transformation matrix of the element, ``[\\gamma]``" + "Global-to-local sub-transformation matrix of the element, ``[\\gamma]``" γ ::Matrix{<:Real} - "Local-to-global transformation matrix of the element, ``[T]``" + "Global-to-local transformation matrix of the element, ``[T]``" T ::Matrix{<:Real} "Element's elastic stiffness matrix in its local coordinate system, ``[k_{e, l}]``" k_e_l ::Matrix{<:Real} "Element's geometric stiffness matrix in its local coordinate system, ``[k_{g, l}]``" k_g_l ::Matrix{<:Real} + "Element's elastic stiffness matrix in its global coordinate system, ``[k_{e, g}]``" + k_e_g ::Matrix{<:Real} + "Element's geometric stiffness matrix in its global coordinate system, ``[k_{g, g}]``" + k_g_g ::Matrix{<:Real} # "Condensed element's elastic stiffness matrix in its local coordinate system, ``[k_{e, l, c}]``" # k_e_l_c ::Matrix{<:Real} # "Condensed element's geometric stiffness matrix in its local coordinate system, ``[k_{g, l, c}]``" @@ -203,4 +207,29 @@ function _compute_k_g_l( # Return the element geometric stiffness matrix: return k_g_l +end + +function _compute_p_l( + w_x::DLTX, w_y::DLTY, w_z::DLTZ, + L::ELT) where {DLTX<:Real, DLTY<:Real, DLTZ<:Real, ELT<:Real} + # Compute the element fixed-end forces: + p_l = [ + -w_x * L / 2 ; # F_x_i + -w_y * L / 2 ; # F_y_i + -w_z * L / 2 ; # F_z_i + 0 ; # M_x_i + -w_z * L ^ 2 / 12; # M_y_i + -w_y * L ^ 2 / 12; # M_z_i + +w_x * L / 2 ; # F_x_j + -w_y * L / 2 ; # F_y_j + -w_z * L / 2 ; # F_z_j + 0 ; # M_x_j + +w_z * L ^ 2 / 12; # M_y_j + +w_y * L ^ 2 / 12] # M_z_j + + # Remove small values if any: + map!(x -> abs(x) < 1E-12 ? 0 : x, p_l, p_l) + + # Return the element fixed-end forces: + return p_l end \ No newline at end of file diff --git a/src/Hephaestus.jl b/src/Hephaestus.jl index 0434ff4..d59abc5 100644 --- a/src/Hephaestus.jl +++ b/src/Hephaestus.jl @@ -1,4 +1,5 @@ module Hephaestus +using LinearAlgebra using StyledStrings using OrderedCollections using DocStringExtensions @@ -8,6 +9,10 @@ include("Materials.jl") include("Sections.jl") include("Elements.jl") include("Models.jl") +include("Analyses.jl") export Node, Material, Section, Element, Model -export add_node!, add_material!, add_section!, add_element!, add_support!, add_concetrated_load!, add_distributed_load! +export add_node!, add_material!, add_section!, add_element!, add_support!, add_concentrated_load!, add_distributed_load! +export O1EAnalysis, O1ECache +export O2EAnalysis, O2ECache +export solve end \ No newline at end of file diff --git a/src/Models.jl b/src/Models.jl index b28fbb9..e57a858 100644 --- a/src/Models.jl +++ b/src/Models.jl @@ -8,7 +8,7 @@ To create a model, use the [`Model()`](@ref) constructor. # Fields $(FIELDS) """ -@kwdef mutable struct Model +@kwdef struct Model "Dictionary of nodes in the model." nodes ::OrderedDict{Int, Node } = OrderedDict{Int, Node }() "Dictionary of materials in the model." @@ -21,10 +21,15 @@ $(FIELDS) "Dictionary of supports in the model." supports ::OrderedDict{Int, Vector{Bool}} = OrderedDict{Int, Vector{Bool}}() - "Dictionary of concetrated loads (acting on nodes) in the model." - concetrated_loads ::OrderedDict{Int, Vector{<:Real}} = OrderedDict{Int, Vector{<:Real}}() + "Dictionary of concentrated loads (acting on nodes) in the model." + concentrated_loads ::OrderedDict{Int, Vector{<:Real}} = OrderedDict{Int, Vector{<:Real}}() "Dictionary of distributed loads (acting on elements) in the model." distributed_loads ::OrderedDict{Int, Vector{<:Real}} = OrderedDict{Int, Vector{<:Real}}() + + "Dictionary of element fixed-end forces in the element's local coordinate system." + p_l ::OrderedDict{Int, Vector{<:Real}} = OrderedDict{Int, Vector{<:Real}}() + "Dictionary of element fixed-end forces in the global coordinate system." + p_g ::OrderedDict{Int, Vector{<:Real}} = OrderedDict{Int, Vector{<:Real}}() end function Base.show(io::IO, model::Model) @@ -134,6 +139,9 @@ function add_element!(model::Model, ID::Int, node_i_ID::Int, node_j_ID::Int, mat A, I_zz, I_yy, J, L) + # Compute the element's elastic stiffness matrix in the global coordinate system: + k_e_g = T' * k_e_l * T + # Compute the condensed element's elastic stiffness matrix in its local coordinate system: # k_e_l_c = _compute_k_e_l_c(k_e_l) @@ -142,6 +150,9 @@ function add_element!(model::Model, ID::Int, node_i_ID::Int, node_j_ID::Int, mat A, I_zz, I_yy, L) + # Compute the element's geometric stiffness matrix in the global coordinate system: + k_g_g = T' * k_g_l * T + # Compute the condensed element's geometric stiffness matrix in its local coordinate system: # k_g_l_c = _compute_k_g_l_c(k_g_l) @@ -153,7 +164,7 @@ function add_element!(model::Model, ID::Int, node_i_ID::Int, node_j_ID::Int, mat E, ν, ρ, A, I_zz, I_yy, J, ω, releases, - L, γ, T, k_e_l, k_g_l) + L, γ, T, k_e_l, k_g_l, k_e_g, k_g_g) # Return the updated model: return model @@ -177,19 +188,19 @@ function add_support!(model::Model, ID::Int, u_x::Bool, u_y::Bool, u_z::Bool, θ return model end -function add_concetrated_load!(model::Model, ID::Int, F_x::Real, F_y::Real, F_z::Real, M_x::Real, M_y::Real, M_z::Real) +function add_concentrated_load!(model::Model, ID::Int, F_x::Real, F_y::Real, F_z::Real, M_x::Real, M_y::Real, M_z::Real) # Check if the node exists in the model: if !haskey(model.nodes, ID) throw(ArgumentError("Node with ID = $(ID) does not exist in the model.")) end - # Check if the concetrated load already exists in the model: - if haskey(model.concetrated_loads, ID) - @warn "Concetrated loads at node with ID = $(ID) already exist in the model. Overwriting them." + # Check if the concentrated load already exists in the model: + if haskey(model.concentrated_loads, ID) + @warn "Concentrated loads at node with ID = $(ID) already exist in the model. Overwriting them." end # Add the concetrated load to the model: - model.concetrated_loads[ID] = [F_x, F_y, F_z, M_x, M_y, M_z] + model.concentrated_loads[ID] = [F_x, F_y, F_z, M_x, M_y, M_z] # Return the updated model: return model @@ -208,6 +219,10 @@ function add_distributed_load!(model::Model, ID::Int, w_x::Real, w_y::Real, w_z: # Add the distributed loads to the model: if CS == :local # If the distributed loads are provided in the local coordinate system of the element + # Extract the information of the element: + L = model.elements[ID].L + T = model.elements[ID].T + # Add the distributed loads to the model: model.distributed_loads[ID] = [w_x, w_y, w_z] elseif CS == :global # If the distributed loads are provided in the global coordinate system @@ -216,30 +231,44 @@ function add_distributed_load!(model::Model, ID::Int, w_x::Real, w_y::Real, w_z: x_j, y_j, z_j = model.elements[ID].x_j, model.elements[ID].y_j, model.elements[ID].z_j L = model.elements[ID].L γ = model.elements[ID].γ - - # Compute the length of the element along each axis in the global coordinate system: - L_x = abs(x_j - x_i) - L_y = abs(y_j - y_i) - L_z = abs(z_j - z_i) + T = model.elements[ID].T # Compute the resultants of the distributed loads: - R_x = w_x * L_x - R_y = w_y * L_y - R_z = w_z * L_z + Δ_x = x_j - x_i + Δ_y = y_j - y_i + Δ_z = z_j - z_i + R_x = w_x * norm([0, Δ_y, Δ_z]) + R_y = w_y * norm([Δ_x, 0, Δ_z]) + R_z = w_z * norm([Δ_x, Δ_y, 0]) R = [R_x, R_y, R_z] # Transform the resultants to the local coordinate system of the element: - r = γ \ R + r = γ * R # Resolve the resultants in the local coordinate system of the element into distributed loads: - w_xl, w_yl, w_zl = r / L + w_x, w_y, w_z = r / L # Add the distributed loads to the model: - model.distributed_loads[ID] = [w_xl, w_yl, w_zl] + model.distributed_loads[ID] = [w_x, w_y, w_z] else throw(ArgumentError("Coordinate system must be either `:local` or `:global`.")) end + # Compute the element fixed-end forces in the element's local coordinate system: + p_l = _compute_p_l( + w_x, w_y, w_z, + L) + + # Transform the element fixed-end forces to the global coordinate system: + p_g = T * p_l + + # Remove small values if any: + map!(x -> abs(x) < 1E-12 ? 0 : x, p_g, p_g) + + # Add the element fixed-end forces to the model: + model.p_l[ID] = p_l + model.p_g[ID] = p_g + # Return the updated model: return model end \ No newline at end of file diff --git a/test/Test.jl b/test/Test.jl index b4b525c..b19c043 100644 --- a/test/Test.jl +++ b/test/Test.jl @@ -22,14 +22,16 @@ add_element!(M, 3, 3, 4, 1, 1) add_element!(M, 4, 4, 5, 1, 1) add_element!(M, 5, 5, 6, 1, 1) -add_support!(M, 1, true , true, true, true, true, true ) -add_support!(M, 2, false, true, true, true, true, false) -add_support!(M, 3, false, true, true, true, true, false) -add_support!(M, 4, false, true, true, true, true, false) -add_support!(M, 5, false, true, true, true, true, false) -add_support!(M, 6, false, true, true, true, true, false) - -add_concetrated_load!(M, 6, 0, -1000, 0, 0, 0, 0) +add_support!(M, 1, true , true , true, true, true, true ) +add_support!(M, 2, false, false, true, true, true, false) +add_support!(M, 3, false, false, true, true, true, false) +add_support!(M, 4, false, false, true, true, true, false) +add_support!(M, 5, false, false, true, true, true, false) +add_support!(M, 6, false, false, true, true, true, false) + +add_concentrated_load!(M, 6, 0, -1000, 0, 0, 0, 0) + +U_f = solve(M, O1EAnalysis()) # @benchmark FiniteDiff.finite_difference_derivative(f, -1000.0) # @benchmark ForwardDiff.derivative(f, -1000.0) \ No newline at end of file