Incompressible Elasticity
This example is the adoption of Ferrite.jl
s example To adapt, we make a few changes, specifically that we don't use BlockArrays
The full code, without comments, can be found in the next section.
using Ferrite
using FerriteAssembly, FerriteProblems
using FESolvers
import FerriteProblems as FP
import FerriteAssembly as FA
Problem setup
First we generate a simple grid, specifying the 4 corners of Cooks membrane.
function create_cook_grid(nx, ny)
corners = [Vec{2}((0.0, 0.0)),
Vec{2}((48.0, 44.0)),
Vec{2}((48.0, 60.0)),
Vec{2}((0.0, 44.0))]
grid = generate_grid(Triangle, (nx, ny), corners);
# facesets for boundary conditions
addfaceset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0);
addfaceset!(grid, "traction", x -> norm(x[1]) ≈ 48.0);
return grid
end;
Next we define a function to set up our cell- and facevalues.
function create_values(ipu, ipp)
RefShape = Ferrite.getrefshape(ipu)
# quadrature rules
qr = QuadratureRule{RefShape}(3)
face_qr = FaceQuadratureRule{RefShape}(3)
# geometric interpolation
ip_geo = Lagrange{RefShape,1}()
# cell and facevalues for u
cellvalues_u = CellValues(qr, ipu, ip_geo)
facevalues_u = FaceValues(face_qr, ipu, ip_geo)
# cellvalues for p
cellvalues_p = CellValues(qr, ipp, ip_geo)
return cellvalues_u, cellvalues_p, facevalues_u
end;
We create a DofHandler, with two fields, :u
and :p
, with possibly different interpolations
function create_dofhandler(grid, ipu, ipp)
dh = DofHandler(grid)
add!(dh, :u, ipu) # displacement
add!(dh, :p, ipp) # pressure
close!(dh)
return dh
end;
The material is linear elastic, which is here specified by the shear and bulk moduli
struct LinearElasticity{T}
G::T
K::T
end
function LinearElasticity(;Emod, ν)
Gmod = Emod / 2(1 + ν)
Kmod = Emod * ν / ((1+ν) * (1-2ν))
return LinearElasticity(Gmod, Kmod)
end
Main.LinearElasticity
Define a cache for the material, as used in the original example
function FerriteAssembly.allocate_cell_cache(::LinearElasticity, cv::NamedTuple)
cellvalues_u = cv[:u]
return collect([symmetric(shape_gradient(cellvalues_u, 1, i)) for i in 1:getnbasefunctions(cellvalues_u)])
end
function create_definition(ν, ip_u, ip_p)
grid = create_cook_grid(50, 50)
dh = create_dofhandler(grid, ip_u, ip_p)
ch = ConstraintHandler(dh)
add!(ch, Dirichlet(:u, getfaceset(dh.grid, "clamped"), Returns(zero(Vec{2}))))
close!(ch)
update!(ch, 0.0)
cv_u, cv_p, fv = create_values(ip_u, ip_p)
cv = (u=cv_u, p=cv_p) # Create NamedTuple
lh = LoadHandler(dh)
add!(lh, Neumann(:u, fv, getfaceset(grid, "traction"), (x,t,n)->Vec{2}((0.0, 1/16))))
m = LinearElasticity(;Emod=1.0, ν=ν)
# Create and return the `FEDefinition`
domainspec = DomainSpec(dh, m, cv)
return FEDefinition(domainspec; ch, lh)
end;
Physics (element routine)
We define the element according to FerriteAssembly
, and without any Neumann contributions. We restrict this element to only work with FESolvers.LinearProblemSolver
by not including the internal forces in residual. Since the problem results in a symmetric matrix we choose to only assemble the lower part, and then symmetrize it after the loop over the quadrature points.
function FerriteAssembly.element_routine!(
Ke, re, state, ue, mp::LinearElasticity, cv::NamedTuple, buffer
)
cellvalues_u = cv[:u]
cellvalues_p = cv[:p]
# Get the local indices for each field
udofs = dof_range(buffer, :u)
pdofs = dof_range(buffer, :p)
# Extract cached gradients
∇Nu_sym_dev = FA.get_user_cache(buffer)
# We only assemble lower half triangle of the stiffness matrix and then symmetrize it.
for q_point in 1:getnquadpoints(cellvalues_u)
for i in 1:length(udofs)
∇Nu_sym_dev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))
end
dΩ = getdetJdV(cellvalues_u, q_point)
for (i_u, i) in enumerate(udofs)
for (j_u, j) in enumerate(udofs[1:i_u])
Ke[i,j] += 2 * mp.G * ∇Nu_sym_dev[i_u] ⊡ ∇Nu_sym_dev[j_u] * dΩ
end
end
for (i_p, i) in enumerate(pdofs)
δNp = shape_value(cellvalues_p, q_point, i_p)
for (j_u, j) in enumerate(udofs)
divδNu = shape_divergence(cellvalues_u, q_point, j_u)
Ke[i,j] += -δNp * divδNu * dΩ
end
for (j_p, j) in enumerate(pdofs[1:i_p])
Np = shape_value(cellvalues_p, q_point, j_p)
Ke[i,j] += - 1/mp.K * δNp * Np * dΩ
end
end
end
symmetrize_lower!(Ke)
end
function symmetrize_lower!(K)
for i in 1:size(K,1)
for j in i+1:size(K,1)
K[i,j] = K[j,i]
end
end
end;
Postprocessing
struct IE_PostProcessing
vtk_file::String
end
function FESolvers.postprocess!(post::IE_PostProcessing, p, solver)
step = FESolvers.get_step(solver)
step == 1 && return nothing # We don't want to save the initial conditions.
dh = FP.get_dofhandler(p)
vtk_grid(post.vtk_file, dh) do vtkfile
vtk_point_data(vtkfile, dh, FP.getunknowns(p))
end
end;
Solving the problem
function build_problem(;ν, ip_u, ip_p)
def = create_definition(ν, ip_u, ip_p)
ip_u_string = isa(ip_u, Lagrange{2,RefTetrahedron,1}) ? "linear" : "quadratic"
post = IE_PostProcessing("cook_$(ip_u_string)_linear")
return FerriteProblem(def, post)
end
solver = QuasiStaticSolver(;nlsolver=LinearProblemSolver(), timestepper=FixedTimeStepper([0.0,1.0]))
ν = 0.4999999
linear = Lagrange{2,RefTetrahedron,1}()
quadratic = Lagrange{2,RefTetrahedron,2}()
p1 = build_problem(;ν, ip_u=linear^2, ip_p=linear)
p2 = build_problem(;ν, ip_u=quadratic^2, ip_p=linear)
solve_problem!(p1, solver)
solve_problem!(p2, solver)
Plain program
Here follows a version of the program without any comments. The file is also available here: incompressible_elasticity.jl
.
using Ferrite
using FerriteAssembly, FerriteProblems
using FESolvers
import FerriteProblems as FP
import FerriteAssembly as FA
function create_cook_grid(nx, ny)
corners = [Vec{2}((0.0, 0.0)),
Vec{2}((48.0, 44.0)),
Vec{2}((48.0, 60.0)),
Vec{2}((0.0, 44.0))]
grid = generate_grid(Triangle, (nx, ny), corners);
# facesets for boundary conditions
addfaceset!(grid, "clamped", x -> norm(x[1]) ≈ 0.0);
addfaceset!(grid, "traction", x -> norm(x[1]) ≈ 48.0);
return grid
end;
function create_values(ipu, ipp)
RefShape = Ferrite.getrefshape(ipu)
# quadrature rules
qr = QuadratureRule{RefShape}(3)
face_qr = FaceQuadratureRule{RefShape}(3)
# geometric interpolation
ip_geo = Lagrange{RefShape,1}()
# cell and facevalues for u
cellvalues_u = CellValues(qr, ipu, ip_geo)
facevalues_u = FaceValues(face_qr, ipu, ip_geo)
# cellvalues for p
cellvalues_p = CellValues(qr, ipp, ip_geo)
return cellvalues_u, cellvalues_p, facevalues_u
end;
function create_dofhandler(grid, ipu, ipp)
dh = DofHandler(grid)
add!(dh, :u, ipu) # displacement
add!(dh, :p, ipp) # pressure
close!(dh)
return dh
end;
struct LinearElasticity{T}
G::T
K::T
end
function LinearElasticity(;Emod, ν)
Gmod = Emod / 2(1 + ν)
Kmod = Emod * ν / ((1+ν) * (1-2ν))
return LinearElasticity(Gmod, Kmod)
end
function FerriteAssembly.allocate_cell_cache(::LinearElasticity, cv::NamedTuple)
cellvalues_u = cv[:u]
return collect([symmetric(shape_gradient(cellvalues_u, 1, i)) for i in 1:getnbasefunctions(cellvalues_u)])
end
function create_definition(ν, ip_u, ip_p)
grid = create_cook_grid(50, 50)
dh = create_dofhandler(grid, ip_u, ip_p)
ch = ConstraintHandler(dh)
add!(ch, Dirichlet(:u, getfaceset(dh.grid, "clamped"), Returns(zero(Vec{2}))))
close!(ch)
update!(ch, 0.0)
cv_u, cv_p, fv = create_values(ip_u, ip_p)
cv = (u=cv_u, p=cv_p) # Create NamedTuple
lh = LoadHandler(dh)
add!(lh, Neumann(:u, fv, getfaceset(grid, "traction"), (x,t,n)->Vec{2}((0.0, 1/16))))
m = LinearElasticity(;Emod=1.0, ν=ν)
# Create and return the `FEDefinition`
domainspec = DomainSpec(dh, m, cv)
return FEDefinition(domainspec; ch, lh)
end;
function FerriteAssembly.element_routine!(
Ke, re, state, ue, mp::LinearElasticity, cv::NamedTuple, buffer
)
cellvalues_u = cv[:u]
cellvalues_p = cv[:p]
# Get the local indices for each field
udofs = dof_range(buffer, :u)
pdofs = dof_range(buffer, :p)
# Extract cached gradients
∇Nu_sym_dev = FA.get_user_cache(buffer)
# We only assemble lower half triangle of the stiffness matrix and then symmetrize it.
for q_point in 1:getnquadpoints(cellvalues_u)
for i in 1:length(udofs)
∇Nu_sym_dev[i] = dev(symmetric(shape_gradient(cellvalues_u, q_point, i)))
end
dΩ = getdetJdV(cellvalues_u, q_point)
for (i_u, i) in enumerate(udofs)
for (j_u, j) in enumerate(udofs[1:i_u])
Ke[i,j] += 2 * mp.G * ∇Nu_sym_dev[i_u] ⊡ ∇Nu_sym_dev[j_u] * dΩ
end
end
for (i_p, i) in enumerate(pdofs)
δNp = shape_value(cellvalues_p, q_point, i_p)
for (j_u, j) in enumerate(udofs)
divδNu = shape_divergence(cellvalues_u, q_point, j_u)
Ke[i,j] += -δNp * divδNu * dΩ
end
for (j_p, j) in enumerate(pdofs[1:i_p])
Np = shape_value(cellvalues_p, q_point, j_p)
Ke[i,j] += - 1/mp.K * δNp * Np * dΩ
end
end
end
symmetrize_lower!(Ke)
end
function symmetrize_lower!(K)
for i in 1:size(K,1)
for j in i+1:size(K,1)
K[i,j] = K[j,i]
end
end
end;
struct IE_PostProcessing
vtk_file::String
end
function FESolvers.postprocess!(post::IE_PostProcessing, p, solver)
step = FESolvers.get_step(solver)
step == 1 && return nothing # We don't want to save the initial conditions.
dh = FP.get_dofhandler(p)
vtk_grid(post.vtk_file, dh) do vtkfile
vtk_point_data(vtkfile, dh, FP.getunknowns(p))
end
end;
function build_problem(;ν, ip_u, ip_p)
def = create_definition(ν, ip_u, ip_p)
ip_u_string = isa(ip_u, Lagrange{2,RefTetrahedron,1}) ? "linear" : "quadratic"
post = IE_PostProcessing("cook_$(ip_u_string)_linear")
return FerriteProblem(def, post)
end
solver = QuasiStaticSolver(;nlsolver=LinearProblemSolver(), timestepper=FixedTimeStepper([0.0,1.0]))
ν = 0.4999999
linear = Lagrange{2,RefTetrahedron,1}()
quadratic = Lagrange{2,RefTetrahedron,2}()
p1 = build_problem(;ν, ip_u=linear^2, ip_p=linear)
p2 = build_problem(;ν, ip_u=quadratic^2, ip_p=linear)
solve_problem!(p1, solver)
solve_problem!(p2, solver)
# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl
This page was generated using Literate.jl.