Skip to content

Commit

Permalink
- Add enforced nodal displacements
Browse files Browse the repository at this point in the history
- First-order elastic analysis is now working and is auto-differentiable
- Fixed an error in the element elastic stiffness matrix
  • Loading branch information
AkchurinDA committed Sep 16, 2024
1 parent 00f2f48 commit 97ac9e4
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 67 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
## Roadmap

- [ ] Analyses
- [ ] 1nd-order elastic analysis
- [x] 1nd-order elastic analysis
- [ ] 2nd-order elastic analysis
- [ ] Elastic buckling analysis
- [ ] Elements
- [ ] Truss element
- [ ] Beam-column element (Euler-Bernoulli)
- [x] Beam-column element (Euler-Bernoulli)
- [ ] Beam-column element (Timoshenko)

## Acknowledgements
Expand Down
134 changes: 96 additions & 38 deletions src/Analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,26 @@ function solve(model::Model, type::Symbol; log::Bool = true)
# Assemble the global force vector:
model.F = _assemble_F(model)

# Partition the global stiffness matrix:
K_ff, K_fs, K_sf, K_ss = _partition_K(model, model.K_e)

# Partition the global force vector:
F_f, F_s = _partition_F(model, model.F)
# Partition the DOFs into free and supported:
indices_f, indices_s, U_s = _partition_U(model)

# Solve the system of equations:
U_f = K_ff \ F_f
K_ff = model.K_e[indices_f, indices_f]
K_fs = model.K_e[indices_f, indices_s]
F_f = model.F[indices_f]
display(K_fs)
display(U_s)
U_f = K_ff \ (F_f - K_fs * U_s)

# Solve for the reactions:
K_sf = model.K_e[indices_s, indices_f]
K_ss = model.K_e[indices_s, indices_s]
F_s = K_sf * U_f + K_ss * U_s

# Unpartition the global displacement vector:
U = _unpartition_U(model, indices_f, indices_s, U_f, U_s)

return U
end

function _assemble_K_e(model::Model)
Expand Down Expand Up @@ -81,52 +93,98 @@ function _assemble_F(model::Model)
return F
end

function _partition_K(model::Model, K::Matrix{<:Real})
function _partition_U(model::Model)
# Preallocate:
indices_f = Int[] # Free DOFs
indices_s = Int[] # Supported DOFs

# Preallocate:
T = promote_type([typeof(node.u_x_enforced) for node in values(model.nodes)]...)
U_s = T[] # Supported DOFs

# Find the free and supported DOFs:
for node in values(model.nodes)
ID_i = node.ID_i
node.u_x_supported ? push!(indices_f, 6 * ID_i - 5) : push!(indices_s, 6 * ID_i - 5)
node.u_y_supported ? push!(indices_f, 6 * ID_i - 4) : push!(indices_s, 6 * ID_i - 4)
node.u_z_supported ? push!(indices_f, 6 * ID_i - 3) : push!(indices_s, 6 * ID_i - 3)
node.θ_x_supported ? push!(indices_f, 6 * ID_i - 2) : push!(indices_s, 6 * ID_i - 2)
node.θ_y_supported ? push!(indices_f, 6 * ID_i - 1) : push!(indices_s, 6 * ID_i - 1)
node.θ_z_supported ? push!(indices_f, 6 * ID_i ) : push!(indices_s, 6 * ID_i )
end

# Partition the global stiffness matrix:
K_ff = K[indices_f, indices_f]
K_fs = K[indices_f, indices_s]
K_sf = K[indices_s, indices_f]
K_ss = K[indices_s, indices_s]
if !node.u_x_supported && node.u_x_enforced == 0 # If the DOF is free and has no enforced displacement
push!(indices_f, 6 * ID_i - 5)
elseif node.u_x_enforced != 0 # If the DOF has an enforced displacement
push!(indices_s, 6 * ID_i - 5)
push!(U_s, node.u_x_enforced)
else # If the DOF is supported
push!(indices_s, 6 * ID_i - 5)
push!(U_s, 0)
end

if !node.u_y_supported && node.u_y_enforced == 0 # If the DOF is free and has no enforced displacement
push!(indices_f, 6 * ID_i - 4)
elseif node.u_y_enforced != 0 # If the DOF has an enforced displacement
push!(indices_s, 6 * ID_i - 4)
push!(U_s, node.u_y_enforced)
else # If the DOF is supported
push!(indices_s, 6 * ID_i - 4)
push!(U_s, 0)
end

if !node.u_z_supported && node.u_z_enforced == 0 # If the DOF is free and has no enforced displacement
push!(indices_f, 6 * ID_i - 3)
elseif node.u_z_enforced != 0 # If the DOF has an enforced displacement
push!(indices_s, 6 * ID_i - 3)
push!(U_s, node.u_z_enforced)
else # If the DOF is supported
push!(indices_s, 6 * ID_i - 3)
push!(U_s, 0)
end

if !node.θ_x_supported && node.θ_x_enforced == 0 # If the DOF is free and has no enforced displacement
push!(indices_f, 6 * ID_i - 2)
elseif node.θ_x_enforced != 0 # If the DOF has an enforced displacement
push!(indices_s, 6 * ID_i - 2)
push!(U_s, node.θ_x_enforced)
else # If the DOF is supported
push!(indices_s, 6 * ID_i - 2)
push!(U_s, 0)
end

if !node.θ_y_supported && node.θ_y_enforced == 0 # If the DOF is free and has no enforced displacement
push!(indices_f, 6 * ID_i - 1)
elseif node.θ_y_enforced != 0 # If the DOF has an enforced displacement
push!(indices_s, 6 * ID_i - 1)
push!(U_s, node.θ_y_enforced)
else # If the DOF is supported
push!(indices_s, 6 * ID_i - 1)
push!(U_s, 0)
end

if !node.θ_z_supported && node.θ_z_enforced == 0 # If the DOF is free and has no enforced displacement
push!(indices_f, 6 * ID_i)
elseif node.θ_z_enforced != 0 # If the DOF has an enforced displacement
push!(indices_s, 6 * ID_i)
push!(U_s, node.θ_z_enforced)
else # If the DOF is supported
push!(indices_s, 6 * ID_i)
push!(U_s, 0)
end
end

# Return the partitioned stiffness matrix:
return K_ff, K_fs, K_sf, K_ss
return indices_f, indices_s, U_s
end

function _partition_F(model::Model, F::Vector{<:Real})
function _unpartition_U(model::Model, indices_f::Vector{<:Int}, indices_s::Vector{<:Int}, U_f::Vector{FDT}, U_s::Vector{SDT}) where {FDT<:Real, SDT<:Real}
# Preallocate:
indices_f = Int[] # Free DOFs
indices_s = Int[] # Supported DOFs
T = promote_type(FDT, SDT)
U = zeros(T, 6 * length(model.nodes))

# Find the free and supported DOFs:
for node in values(model.nodes)
ID_i = node.ID_i
node.u_x_supported ? push!(indices_f, 6 * ID_i - 5) : push!(indices_s, 6 * ID_i - 5)
node.u_y_supported ? push!(indices_f, 6 * ID_i - 4) : push!(indices_s, 6 * ID_i - 4)
node.u_z_supported ? push!(indices_f, 6 * ID_i - 3) : push!(indices_s, 6 * ID_i - 3)
node.θ_x_supported ? push!(indices_f, 6 * ID_i - 2) : push!(indices_s, 6 * ID_i - 2)
node.θ_y_supported ? push!(indices_f, 6 * ID_i - 1) : push!(indices_s, 6 * ID_i - 1)
node.θ_z_supported ? push!(indices_f, 6 * ID_i ) : push!(indices_s, 6 * ID_i )
# Assign the free DOFs:
for (i, index) in enumerate(indices_f)
U[index] = U_f[i]
end

# Partition the global force vector:
F_f = F[indices_f]
F_s = F[indices_s]
# Assign the supported DOFs:
for (i, index) in enumerate(indices_s)
U[index] = U_s[i]
end

# Return the partitioned force vector:
return F_f, F_s
# Return the unpartitioned global displacement vector:
return U
end
4 changes: 2 additions & 2 deletions src/Element.jl
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ function _compute_k_e_l(
@inbounds k_e_l[6 , 8 ] = -6 * E * I_zz / L^2
@inbounds k_e_l[6 , 12] = +2 * E * I_zz / L
@inbounds k_e_l[7 , 7 ] = +E * A / L
@inbounds k_e_l[8 , 8 ] = -12 * E * I_zz / L^3
@inbounds k_e_l[8 , 8 ] = +12 * E * I_zz / L^3
@inbounds k_e_l[8 , 12] = -6 * E * I_zz / L^2
@inbounds k_e_l[9 , 9 ] = +12 * E * I_yy / L^3
@inbounds k_e_l[9 , 11] = +6 * E * I_yy / L^2
@inbounds k_e_l[10, 10] = +E * J / (2 * (1 + ν) * L)
@inbounds k_e_l[11, 11] = +12 * E * I_yy / L^3
@inbounds k_e_l[11, 11] = +4 * E * I_yy / L
@inbounds k_e_l[12, 12] = +4 * E * I_zz / L

# Compute the components of the element elastic stiffness matrix in its lower triangular part:
Expand Down
4 changes: 2 additions & 2 deletions src/Hephaestus.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ include("Element.jl")
include("Model.jl")
include("Analysis.jl")
export Node, Material, Section, Element, Model
export add_node!, add_material!, add_section!, add_element!, add_support!, add_nodal_load!
export del_node!, del_material!, del_section!, del_element!, del_support!, del_nodal_load!
export add_node!, add_material!, add_section!, add_element!, add_support!, add_nodal_load!, add_nodal_disp!
export del_node!, del_material!, del_section!, del_element!, del_support!, del_nodal_load!, del_nodal_disp!
export reset_model!
export solve
end
49 changes: 48 additions & 1 deletion src/Model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ end
add_nodal_load!(model::Model, ID::Int,
F_x::Real, F_y::Real, F_z::Real,
M_x::Real, M_y::Real, M_z::Real) =
add_nodal_load!(model, ID, promote(F_x, F_y, F_z, M_x, M_y, M_z)...)
add_nodal_load!(model, ID, promote(F_x, F_y, F_z, M_x, M_y, M_z)...)

"""
del_nodal_load!(model, ID)
Expand All @@ -334,6 +334,53 @@ function del_nodal_load!(model::Model, ID::Int)
return model
end

function add_nodal_disp!(model::Model, ID::Int,
u_x_enforced::Real, u_y_enforced::Real, u_z_enforced::Real,
θ_x_enforced::Real, θ_y_enforced::Real, θ_z_enforced::Real)
# Check if the ID exists:
!haskey(model.nodes, ID) && throw(ArgumentError("Node ID $ID does not exist."))

# Check if the node already has enforced displacements applied to it:
if model.nodes[ID].u_x_enforced != 0 || model.nodes[ID].u_y_enforced != 0 || model.nodes[ID].u_z_enforced != 0 ||
model.nodes[ID].θ_x_enforced != 0 || model.nodes[ID].θ_y_enforced != 0 || model.nodes[ID].θ_z_enforced != 0
@warn "Node with ID = $ID already has enforced displacements applied to it. Overwriting them with the new values."
end

# Add the enforced displacements to the node:
model.nodes[ID].u_x_enforced = u_x_enforced
model.nodes[ID].u_y_enforced = u_y_enforced
model.nodes[ID].u_z_enforced = u_z_enforced
model.nodes[ID].θ_x_enforced = θ_x_enforced
model.nodes[ID].θ_y_enforced = θ_y_enforced
model.nodes[ID].θ_z_enforced = θ_z_enforced

# Return the updated model:
return model
end

add_nodal_disp!(model::Model, ID::Int,
u_x_enforced::Real, u_y_enforced::Real, u_z_enforced::Real,
θ_x_enforced::Real, θ_y_enforced::Real, θ_z_enforced::Real) =
add_nodal_disp!(model, ID, promote(u_x_enforced, u_y_enforced, u_z_enforced, θ_x_enforced, θ_y_enforced, θ_z_enforced)...)

function del_nodal_disp!(model::Model, ID::Int,
u_x_enforced::NLT, u_y_enforced::NLT, u_z_enforced::NLT,
θ_x_enforced::NLT, θ_y_enforced::NLT, θ_z_enforced::NLT) where {NLT <: Real}
# Check if the ID exists:
!haskey(model.nodes, ID) && throw(ArgumentError("Node ID $ID does not exist."))

# Set the enforced displacements to zero:
model.nodes[ID].u_x_enforced = 0
model.nodes[ID].u_y_enforced = 0
model.nodes[ID].u_z_enforced = 0
model.nodes[ID].θ_x_enforced = 0
model.nodes[ID].θ_y_enforced = 0
model.nodes[ID].θ_z_enforced = 0

# Return the updated model:
return model
end

"""
reset_model!(model)
Expand Down
7 changes: 7 additions & 0 deletions src/Node.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,19 @@ mutable struct Node{CT <: Real}
M_y ::Real
"Nodal moment about the global ``z``-axis"
M_z ::Real
u_x_enforced ::Real
u_y_enforced ::Real
u_z_enforced ::Real
θ_x_enforced ::Real
θ_y_enforced ::Real
θ_z_enforced ::Real

function Node(ID::Int, x::CT, y::CT, z::CT) where {CT <: Real}
new{CT}(ID, nothing,
x, y, z,
fill(0 , 6)...,
fill(false, 6)...,
fill(0 , 6)...,
fill(0 , 6)...)
end
end
Expand Down
57 changes: 35 additions & 22 deletions test/Test.jl
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
using Hephaestus
using ForwardDiff

# Create a new model:
M = Model()
function f(x)
# Create a new model:
M = Model()

# Add nodes:
add_node!(M, 1, 0 , 0 , 0)
add_node!(M, 2, 0 , 5000, 0)
add_node!(M, 3, 7416 , 8000, 0)
add_node!(M, 4, 11416, 5000, 0)
add_node!(M, 5, 11416, 0 , 0)
# Add nodes:
add_node!(M, 1, (1 - 1) * 1000, 0, 0)
add_node!(M, 2, (2 - 1) * 1000, 0, 0)
add_node!(M, 3, (3 - 1) * 1000, 0, 0)
add_node!(M, 4, (4 - 1) * 1000, 0, 0)
add_node!(M, 5, (5 - 1) * 1000, 0, 0)
add_node!(M, 6, (6 - 1) * 1000, 0, 0)

# Add materials:
add_material!(M, 1, 200000, 0.3, 9.81 * 7850)
# Add materials:
add_material!(M, 1, 200_000, 0.3, 9.81 * 7850)

# Add sections:
add_section!(M, 1, 4E3, 50E6 , 0, 0)
add_section!(M, 2, 6E3, 200E6, 0, 0)
# Add sections:
add_section!(M, 1, 4E3, 50E6, 0, 0)

# Add elements:
add_element!(M, 1, 1, 2, 1, 1)
add_element!(M, 2, 2, 3, 1, 2)
add_element!(M, 3, 3, 4, 1, 1)
add_element!(M, 4, 4, 5, 1, 1)
# Add elements:
add_element!(M, 1, 1, 2, 1, 1)
add_element!(M, 2, 2, 3, 1, 1)
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_nodal_load!(M, 2, 0, -5000.0, π, 0, 0, 0)
add_nodal_load!(M, 6, 0, x, 0, 0, 0, 0)

# Add supports:
add_support!(M, 1, true, true, true, true, true, true)
# Add supports:
add_support!(M, 1, true, true , true, true, true, true )
add_support!(M, 2, true, false, true, true, true, false)
add_support!(M, 3, true, false, true, true, true, false)
add_support!(M, 4, true, false, true, true, true, false)
add_support!(M, 5, true, false, true, true, true, false)
add_support!(M, 6, true, false, true, true, true, false)

solve(M, :O1E)
U = solve(M, :O1E)

return U[end]
end

@time ForwardDiff.derivative(f, -1000)

0 comments on commit 97ac9e4

Please sign in to comment.