include("Rotations.jl")
Euler beam element
using StaticArrays, LinearAlgebra, Muscade
Data structure containing the cross section material properties
struct BeamCrossSection
EA :: 𝕣 # axial stiffness
EI₂ :: 𝕣 # bending stiffness about second axis
EI₃ :: 𝕣 # bending stiffness about third axis
GJ :: 𝕣 # torsional stiffness (about longitudinal axis)
μ :: 𝕣 # mass per unit length
ι₁ :: 𝕣 # (mass) moment of inertia for rotation about the element longitudinal axis per unit length
end
BeamCrossSection(;EA=EA,EI₂=EI₂,EI₃=EI₃,GJ=GJ,μ=μ,ι₁=ι₁) = BeamCrossSection(EA,EI₂,EI₃,GJ,μ,ι₁);
Resultant function that computes the internal loads from the strains and curvatures, and external loads on the element.
@espy function resultants(o::BeamCrossSection,ε,κ,xᵧ,rₛₘ,vᵢ)
r₀ = ∂0(rₛₘ) # orientation of the element's local refsys
vᵢ₁ = ∂1(vᵢ) # intrinsic rotation rate of the element's local refsys
vᵢ₂ = ∂2(vᵢ) # intrinsic rotation acceleration of the element's local refsys
xᵧ₀,xᵧ₁,xᵧ₂ = ∂0(xᵧ),∂1(xᵧ),∂2(xᵧ)
xₗ₁ = xᵧ₁ ∘₁ r₀
xₗ₂ = xᵧ₂ ∘₁ r₀
# Compute drag force (example) and added-mass force (example)
# fa = ρ * Ca .* xₗ₂
# fd = .5 * ρ * A .* Cd .* xₗ₁ #.* abs.(xₗ₁)
# Compute translational inertia force
fi = o.μ * xᵧ₂
☼fₑ = fi # external forces at Gauss point.
# Compute roll inertia moment
m₁ₗ = o.ι₁*vᵢ₂[1] #local
mᵧ = ∂0(rₛₘ)[:,1] * m₁ₗ #global
☼mₑ = mᵧ # external couples at Gauss point.
# Compute internal loads
☼fᵢ = o.EA*∂0(ε)
# WARNING: curvatures are defined as rate of rotation along the element, not second derivatives of deflection.
# Hence κ[3]>0 implies +2 direction is inside curve,
# κ[2]>0 implies -3 direction is inside curve.
☼mᵢ = SVector(o.GJ*∂0(κ)[1],o.EI₃*∂0(κ)[2],o.EI₂*∂0(κ)[3])
return fᵢ,mᵢ,fₑ,mₑ
end;
# Static Euler beam element, with two nodes, two Gauss points and 12 degrees of freedom.
const ngp = 4
const ndim = 3
const nXdof = 12
const nUdof = 3
const nXnod = 2;
Shape functions for a beam element with support ζ∈[-1/2,1/2]. Though the shape function matrices are sparse, do not "unroll" them. That would be applyer but considerably clutter the code
yₐ(ζ) = 2ζ # differential axial displacement or roll field
yᵤ(ζ) = -4ζ^3 +3ζ # deflection due to differential nodal transverse translation
yᵥ(ζ) = ζ^2 - 1/4 # deflection due to differenttial rotation (bending, not torsion)
κₐ(ζ) = 2 # torsion . κₐ = yₐ′ . Divide by L .
κᵤ(ζ) = -24ζ # curvature. κᵤ = yᵤ′′. Divide by L².
κᵥ(ζ) = 2; # curvature. κᵥ = yᵥ′′. Divide by L .
Data structure describing an EulerBeam3D element as meshed
struct EulerBeam3D{Mat,Uforce} <: AbstractElement
cₘ :: SVector{3,𝕣} # Position of the middle of the element, as meshed
rₘ :: Mat33{𝕣} # Orientation of the element, as meshed, represented by a rotation matrix (from global to local)
ζgp :: SVector{ngp,𝕣} # Location of the Gauss points for the normalized element with length 1
ζnod :: SVector{nXnod,𝕣} # Location of the nodes for the normalized element with length 1
tgₘ :: SVector{ndim,𝕣} # Vector connecting the nodes of the element in the global coordinate system
tgₑ :: SVector{ndim,𝕣} # Vector connecting the nodes of the element in the local coordinate system
yₐ :: SVector{ngp,𝕣} # Value at gp of shape function for differential axial displacement or roll field
yᵤ :: SVector{ngp,𝕣} # Value at gp of shape function for deflection due to differential nodal transverse translation
yᵥ :: SVector{ngp,𝕣} # Value at gp of shape function for deflection due to differenttial rotation (bending, not torsion)
κₐ :: SVector{ngp,𝕣} # Value at gp of shape function for torsion . κₐ = yₐ′ . Divided by L .
κᵤ :: SVector{ngp,𝕣} # Value at gp of shape function for curvature. κᵤ = yᵤ′′. Divided by L².
κᵥ :: SVector{ngp,𝕣} # Value at gp of shape function for curvature. κᵥ = yᵥ′′. Divided by L .
L :: 𝕣 # as meshed length of the element
dL :: SVector{ngp,𝕣} # length associated to each Gauss point
mat :: Mat # used to store material properties (BeamCrossSection, for example)
end;
For performance, residual
will only accept differentiation to first order
Muscade.nosecondorder(::Type{<:EulerBeam3D}) = Val(true)
Define nodes, classes, and field names of dofs
Muscade.doflist( ::Type{EulerBeam3D{Mat,false}}) where{Mat} =
(inod = (1,1,1,1,1,1, 2,2,2,2,2,2),
#class= ntuple(i->:X,nXdof), # not typestable
class= (:X,:X,:X,:X,:X,:X,:X,:X,:X,:X,:X,:X),
field= (:t1,:t2,:t3,:r1,:r2,:r3, :t1,:t2,:t3,:r1,:r2,:r3) )
Muscade.doflist( ::Type{EulerBeam3D{Mat,true}}) where{Mat} =
(inod = (1,1,1,1,1,1, 2,2,2,2,2,2, 3,3,3),
#class= (ntuple(i->:X,nXdof)...,ntuple(i->:U,nUdof)...), # not typestable
class= (:X,:X,:X,:X,:X,:X,:X,:X,:X,:X,:X,:X,:U,:U,:U),
field= (:t1,:t2,:t3,:r1,:r2,:r3, :t1,:t2,:t3,:r1,:r2,:r3, :t1,:t2,:t3) )
ElementType for the EulerBeam3D element. Arguments: node list, material, and direction of the first bending axis in the global coordinate system.
EulerBeam3D(nod;kwargs...) = EulerBeam3D{false}(nod;kwargs...) # by default, EulerBeam3D does not have Udof.
function EulerBeam3D{Udof}(nod::Vector{Node};mat,orient2::SVector{ndim,𝕣}=SVector(0.,1.,0.)) where {Udof}
c = coord(nod)
# Position of the middle of the element in the global coordinate system (as-meshed)
cₘ = SVector{ndim}((c[1]+c[2])/2)
# Length and tangential vector to the element in the global coordinate system
tgₘ = SVector{ndim}( c[2]-c[1] )
L = norm(tgₘ)
t = tgₘ/L
# Create t, n, b which are the longitudinal and two transverse unit vectors to the element (as-meshed).
# NB: orient2, provided by the user, will define the first bending axis.
orient2/= norm(orient2)
n = orient2 - t*dot(orient2,t)
nn = norm(n)
nn>1e-3 || muscadeerror("Provide a 'orient' input that is not nearly parallel to the element")
n /= nn
b = cross(t,n)
rₘ = SMatrix{ndim,ndim}(t...,n...,b...)
# Tangential vector and node coordinates in the local coordinate system
tgₑ = SVector{ndim}(L,0,0)
# Weight associated to each Gauss point
dL = SVector{ngp}(L/2*(18-sqrt(30))/36,L/2*(18+sqrt(30))/36 ,L/2*(18+sqrt(30))/36,L/2*(18-sqrt(30))/36 )
# Location ζgp of the Gauss points for a unit-length beam element, with nodes at ζnod=±1/2.
ζgp = SVector{ngp }(-1/2*sqrt(3/7+2/7*sqrt(6/5)),-1/2*sqrt(3/7-2/7*sqrt(6/5)), +1/2*sqrt(3/7-2/7*sqrt(6/5)),+1/2*sqrt(3/7+2/7*sqrt(6/5)))
ζnod = SVector{nXnod}(-1/2 ,1/2 )
shapes = (yₐ.(ζgp), yᵤ.(ζgp), yᵥ.(ζgp)*L, κₐ.(ζgp)/L, κᵤ.(ζgp)/L^2, κᵥ.(ζgp)/L)
return EulerBeam3D{typeof(mat),Udof}(cₘ,rₘ,ζgp,ζnod,tgₘ,tgₑ,shapes...,L,dL,mat)
end;
Define now the residual function for the EulerBeam3D element.
@espy function Muscade.residual(o::EulerBeam3D{Mat,Udof}, X,U,A,t,SP,dbg) where{Mat,Udof}
P,ND = constants(X),length(X)
# Compute all quantities at Gauss point, their time derivatives, including intrinsic roll rate and acceleration
gp_,ε_,vₛₘ_,rₛₘ_,vₗ₂_,_,_ = kinematics{:direct}(o,motion{P}(X))
gpval,☼ε,☼rₛₘ = motion⁻¹{P,ND}(gp_,ε_,rₛₘ_)
vᵢ = intrinsicrotationrates(rₛₘ)
# compute all Jacobians of the above quantities with respect to X₀
X₀ = ∂0(X) # returns concrete type
TX₀ = revariate{1}(X₀) # returns ::Any
Tgp,Tε,Tvₛₘ,_,_,_,_ = kinematics{:compose}(o,TX₀) # the crux
gp∂X₀,ε∂X₀,vₛₘ∂X₀ = composeJacobian{P}((Tgp,Tε,Tvₛₘ),X₀)
# Quadrature loop: compute resultants
gp = ntuple(ngp) do igp
☼x,☼κgp = gpval[igp].x, gpval[igp].κ
x∂X₀,κ∂X₀ = gp∂X₀[igp].x, gp∂X₀[igp].κ
fᵢ,mᵢ,fₑ,mₑ = ☼resultants(o.mat,ε,κgp,x,rₛₘ,vᵢ) # call the "resultant" function to compute loads (local coordinates) from strains/curvatures/etc. using material properties. Note that output is dual of input.
fₑ = Udof ? fₑ-∂0(U) : fₑ # U is per unit length
R_ = (fᵢ ∘₀ ε∂X₀ + mᵢ ∘₁ κ∂X₀ + fₑ ∘₁ x∂X₀ + mₑ ∘₁ vₛₘ∂X₀) * o.dL[igp] # Contribution to the local nodal load of this Gauss point [nXdof] = scalar*[nXdof] + [ndim]⋅[ndim,nXdof] + [ndim]⋅[ndim,nXdof]
@named(R_)
end
R = sum(gpᵢ.R_ for gpᵢ∈gp)
♢κ = motion⁻¹{P,ND}(SVector(vₗ₂_[1],vₗ₂_[3],-vₗ₂_[2])).*(2/o.L)
return R,noFB
end;
struct kinematics{Mode} end
function kinematics{Mode}(o::EulerBeam3D,X₀) where{Mode}
cₘ,rₘ,tgₘ,tgₑ,ζnod,ζgp,L = o.cₘ,o.rₘ,o.tgₘ,o.tgₑ,o.ζnod,o.ζgp,o.L # As-meshed element coordinates and describing tangential vector
vₛₘ,rₛₘ,uₗ₂,vₗ₂,cₛₘ = corotated{Mode}(o,X₀)
ε = √((uₗ₂[1]+L/2)^2+uₗ₂[2]^2+uₗ₂[3]^2)*2/L - 1.
gp = ntuple(ngp) do igp # gp[igp].κ, gp[igp].x
yₐ,yᵤ,yᵥ,κₐ,κᵤ,κᵥ = o.yₐ[igp],o.yᵤ[igp],o.yᵥ[igp],o.κₐ[igp],o.κᵤ[igp],o.κᵥ[igp]
κ = SVector( κₐ*vₗ₂[1], κᵤ*uₗ₂[2]+κᵥ*vₗ₂[3], κᵤ*uₗ₂[3]-κᵥ*vₗ₂[2])
y = SVector(yₐ*uₗ₂[1] , yᵤ*uₗ₂[2]+yᵥ*vₗ₂[3], yᵤ*uₗ₂[3]-yᵥ*vₗ₂[2])
x = rₛₘ∘₁(tgₑ*ζgp[igp]+y)+cₛₘ
(κ=κ,x=x)
end
return gp,ε,vₛₘ,rₛₘ,vₗ₂,uₗ₂,cₛₘ
end
vec3(v,ind) = SVector{3}(v[i] for i∈ind);
struct corotated{Mode} end
function corotated{Mode}(o::EulerBeam3D,X₀) where{Mode}
cₘ,rₘ,tgₘ,tgₑ,ζnod,ζgp,L = o.cₘ,o.rₘ,o.tgₘ,o.tgₑ,o.ζnod,o.ζgp,o.L # As-meshed element coordinates and describing tangential vector
uᵧ₁,uᵧ₂,vᵧ = vec3(X₀,1:3), vec3(X₀,7:9), SVector(X₀[4],X₀[5],X₀[6],X₀[10],X₀[11],X₀[12])
Δvᵧ,rₛₘ,vₛₘ = apply{Mode}(vᵧ) do v
vᵧ₁,vᵧ₂ = vec3(v,1:3), vec3(v,4:6)
rₛ₁ = apply{Mode}(Rodrigues,vᵧ₁)
rₛ₂ = apply{Mode}(Rodrigues,vᵧ₂)
Δvᵧ_ = 0.5*Rodrigues⁻¹(rₛ₂ ∘₁ rₛ₁')
rₛₘ_ = apply{Mode}(Rodrigues,Δvᵧ_) ∘₁ rₛ₁ ∘₁ o.rₘ
vₛₘ_ = Rodrigues⁻¹(rₛₘ_)
return Δvᵧ_,rₛₘ_,vₛₘ_
end
cₛ = 0.5*(uᵧ₁+uᵧ₂)
uₗ₂ = rₛₘ' ∘₁ (uᵧ₂+tgₘ*ζnod[2]-cₛ)-tgₑ*ζnod[2] #Local displacement of node 2
vₗ₂ = rₛₘ' ∘₁ Δvᵧ
return vₛₘ,rₛₘ,uₗ₂,vₗ₂,cₛ+cₘ
end;
The following functions explain how the beam element should be drawn
using GLMakie
function Muscade.allocate_drawing(axis,o::AbstractVector{EulerBeam3D{Tmat,Udof}};kwargs...) where{Tmat,Udof}
args = default{:EulerBeam3D }(kwargs,(;) )
section = default{:section }(args,zeros(2,0))
nsec = size(section,2)
opt = (default(args,(style=:shape,draw_frame=false,draw_marking=true,nseg=1,
solid_color=:yellow,line_color=:black,Uscale=1.,Udof=Udof))...,
nel = length(o) ,
nsec = nsec ,
section = section ,
markrad = nsec==0 ? 0. : 1.01*maximum(section[1,:])
)
opt.style==:solid && nsec<2 && muscadeerror("An section description must be provided for 'solid' plot")
nel_shape = opt.style==:shape ? opt.nel : 0
nel_shape_frame = opt.draw_frame ? nel_shape : 0
nel_solid = opt.style==:solid ? opt.nel : 0
nel_solid_marking = opt.draw_marking ? nel_solid : 0
nel_udof = opt.Udof ? opt.nel : 0
mut=(
node = 𝕣2(undef,3,3*opt.nel) ,
shape_x = 𝕣2(undef,3,(opt.nseg+2)*nel_shape) ,
shape_frame = 𝕣2(undef,3,3*3*nel_shape_frame) , # idim, point-point-lift, ivec, iel
solid_vertex = 𝕣2(undef,3,opt.nsec*(opt.nseg+1)*nel_solid) ,
solid_face = 𝕫2(undef,2*opt.nsec* opt.nseg *nel_solid,3),
solid_mark = 𝕣2(undef,3,(opt.nseg+2)*nel_solid_marking) ,
ucrest = 𝕣2(undef,3,5*nel_udof) , # idim, 6point-lift,iel
)
return mut,opt
end
function Muscade.update_drawing(axis,o::AbstractVector{EulerBeam3D{Tmat,Udof}},oldmut,opt, Λ,X,U,A,t,SP,dbg) where{Tmat,Udof}
mut = oldmut
X₀ = ∂0(X)
U₀ = ∂0(U)
it1,ir1,it2,ir2 = SVector{3}(1:3),SVector{3}(4:6),SVector{3}(7:9),SVector{3}(10:12)
nsec = size(opt.section,2)
node = reshape(mut.node,(3,3,opt.nel))
for (iel,oᵢ) = enumerate(o)
node[:,1,iel] = oᵢ.cₘ - oᵢ.tgₘ/2 + X₀[it1,iel]
node[:,2,iel] = oᵢ.cₘ + oᵢ.tgₘ/2 + X₀[it2,iel]
node[:,3,iel].= NaN
end
if opt.style==:shape
ζ = range(-1/2,1/2,opt.nseg+1)
if opt.draw_frame shape_frame = reshape(mut.shape_frame ,(3,3,3 ,opt.nel)) end
if opt.Udof ucrest = reshape(mut.ucrest, (3,5 ,opt.nel)) end
shape_x = reshape(mut.shape_x ,(3,opt.nseg+2,opt.nel))
for (iel,oᵢ) = enumerate(o)
cₘ,rₘ,tgₘ,tgₑ,ζnod,ζgp,L = oᵢ.cₘ,oᵢ.rₘ,oᵢ.tgₘ,oᵢ.tgₑ,oᵢ.ζnod,oᵢ.ζgp,oᵢ.L
X₀ₑ = view(X₀,:,iel)
vₛₘ,rₛₘ,uₗ₂,vₗ₂,cₛₘ = corotated{:direct}(oᵢ,X₀ₑ)
if opt.draw_frame
for ivec = 1:3
shape_frame[:,1,ivec,iel] = cₛₘ
shape_frame[:,2,ivec,iel] = cₛₘ + oᵢ.L/3*rₛₘ[:,ivec]
shape_frame[:,3,ivec,iel].= NaN
end
end
if opt.Udof
ucrest[:,1,iel] = node[:,1,iel]
ucrest[:,2,iel] = node[:,1,iel] + rₛₘ ∘₁ view(U₀,:,iel) * opt.Uscale
ucrest[:,3,iel] = node[:,2,iel] + rₛₘ ∘₁ view(U₀,:,iel) * opt.Uscale
ucrest[:,4,iel] = node[:,2,iel]
ucrest[:,5,iel].= NaN
end
for (i,ζᵢ) ∈ enumerate(ζ)
y = SVector(yₐ(ζᵢ)*uₗ₂[1] , yᵤ(ζᵢ)*uₗ₂[2]+L*yᵥ(ζᵢ)*vₗ₂[3], yᵤ(ζᵢ)*uₗ₂[3]-L*yᵥ(ζᵢ)*vₗ₂[2])
shape_x[:,i ,iel] = rₛₘ∘₁(tgₑ*ζᵢ+y)+cₛₘ
shape_x[:,opt.nseg+2,iel].= NaN
end
end
elseif opt.style==:solid
ζ = range(-1/2,1/2,opt.nseg+1)
idx(iel,iseg,isec) = mod_onebased(isec,opt.nsec)+opt.nsec*(iseg-1+(opt.nseg+1)*(iel-1)) # 1st index into rvertex
if opt.Udof ucrest = reshape(mut.ucrest ,(3,5 ,opt.nel)) end
if opt.draw_marking solid_mark = reshape(mut.solid_mark ,(3,opt.nseg+2 ,opt.nel)) end
solid_face = reshape(mut.solid_face ,(2,opt.nsec, opt.nseg ,opt.nel,3))
solid_vertex = reshape(mut.solid_vertex,(3,opt.nsec, opt.nseg+1 ,opt.nel))
for (iel,oᵢ) = enumerate(o)
cₘ,rₘ,tgₘ,tgₑ,ζnod,ζgp,L = oᵢ.cₘ,oᵢ.rₘ,oᵢ.tgₘ,oᵢ.tgₑ,oᵢ.ζnod,oᵢ.ζgp,oᵢ.L
X₀ₑ = view(X₀,:,iel)
vₛₘ,rₛₘ,uₗ₂,vₗ₂,cₛₘ = corotated{:direct}(oᵢ,X₀ₑ)
vᵧ₁,vᵧ₂ = vec3(X₀ₑ,4:6), vec3(X₀ₑ,10:12)
rₛ₁ = Rodrigues(vᵧ₁)
rₛ₂ = Rodrigues(vᵧ₂)
if opt.Udof
ucrest[:,1,iel] = node[:,1,iel]
ucrest[:,2,iel] = node[:,1,iel] + rₛₘ ∘₁ view(U₀,:,iel) * opt.Uscale
ucrest[:,3,iel] = node[:,2,iel] + rₛₘ ∘₁ view(U₀,:,iel) * opt.Uscale
ucrest[:,4,iel] = node[:,2,iel]
ucrest[:,5,iel].= NaN
end
Δv = Rodrigues⁻¹(rₛ₂ ∘₁ rₛ₁')/opt.nseg
for (iseg,ζᵢ) ∈ enumerate(ζ) # actualy iterating over nseg+1 segment boundaries
y = SVector(yₐ(ζᵢ)*uₗ₂[1] , yᵤ(ζᵢ)*uₗ₂[2]+L*yᵥ(ζᵢ)*vₗ₂[3], yᵤ(ζᵢ)*uₗ₂[3]-L*yᵥ(ζᵢ)*vₗ₂[2]) # interpolate
xn = rₛₘ∘₁(tgₑ*ζᵢ+y)+cₛₘ # point on neutral axis
r = Rodrigues((iseg-1)*Δv) ∘₁ rₛ₁ ∘₁ rₘ
if opt.draw_marking
solid_mark[:, iseg ,iel] = xn .+ r[:,2]*opt.markrad
solid_mark[:,opt.nseg+2,iel].= NaN
end
for isec = 1:opt.nsec
solid_vertex[:,isec,iseg,iel] = xn .+ r[:,2]*opt.section[1,isec] + r[:,3]*opt.section[2,isec]
if iseg≤opt.nseg
i1,i2,i3,i4 = idx(iel,iseg,isec),idx(iel,iseg ,isec+1),idx(iel,iseg+1,isec ),idx(iel,iseg+1,isec+1)
solid_face[1,isec,iseg,iel,:] = SVector(i1,i2,i4)
solid_face[2,isec,iseg,iel,:] = SVector(i1,i4,i3)
end
end
end
end
end
return mut
end
function Muscade.display_drawing!(axis,::Type{EulerBeam3D{Tmat,Udof}},obs,opt) where{Tmat,Udof}
scatter!( axis, obs.node ,color = opt.line_color , marker=:circle,markersize=3)
opt.style==:shape && lines!( axis, obs.shape_x ,color = opt.line_color ,linewidth=.5 )
opt.style==:shape && opt.draw_frame && lines!( axis, obs.shape_frame ,color = :grey ,linewidth=.5 )
opt.style==:solid && mesh!( axis, obs.solid_vertex, obs.solid_face ,color = opt.solid_color )
opt.style==:solid && opt.draw_marking && lines!( axis, obs.solid_mark ,color = opt.line_color )
opt.Udof && lines!( axis, obs.ucrest ,color = :red ,linewidth=.5 )
end
This page was generated using Literate.jl.