September 8, 2025

About

Gridap is a highly useful finite element solver built with Julia.
However, since the documentation still feels somewhat incomplete, I decided to implement and summarize some code that could serve as commonly used templates. The task I worked on was solving linear structural analysis problems.

Even though structural analysis may sound straightforward, executing it correctly requires effort. You need to write the code and then verify numerically that the results are valid.

Implementation

The simulation itself is very simple: one end of a flat plate is fixed, and the opposite end is pulled.

using Gridap, Gridap.TensorValues, LinearAlgebra
using Gridap.Geometry
using Logging

function set_logger_from_args(args)
    level = get(ARGS, 1, "info")
    lvl = Dict(
        "debug" => Logging.Debug,
        "info"  => Logging.Info,
        "warn"  => Logging.Warn,
        "error" => Logging.Error
    )[lowercase(level)]
    global_logger(ConsoleLogger(stderr, lvl))
end

set_logger_from_args(ARGS)

files_path = "results/linear_elasticity/"
mkpath(files_path)
order  = 1
degree = 2*order


Lx, Ly, Lz = (5.0, 5.0, 1.0)
nx, ny, nz = (10, 10, 2)
domain = (0.0,Lx, 0.0,Ly, 0.0,Lz)
partition = (nx,ny,nz)
model = CartesianDiscreteModel(domain, partition)

d = num_cell_dims(model)
Ω  = Triangulation(model)
Γ  = BoundaryTriangulation(Ω)
labels = get_face_labeling(model)
tol = 1e-5
tol_rough = 0.3

topo   = get_grid_topology(model)
toplab = Gridap.Geometry.face_labeling_from_vertex_filter(
  topo, "surface_top", x -> isapprox(x[2], Ly; atol=tol)
)
bottomlab = Gridap.Geometry.face_labeling_from_vertex_filter(
  topo, "surface_bottom", x -> isapprox(x[2], 0.0; atol=tol)
)

merge!(labels, toplab)
merge!(labels, bottomlab)

∂Ω = Measure(Ω, degree)
∂Γ = Measure(Γ, degree)

println("Registered Tags: ", labels.tag_to_name)


reffe = ReferenceFE(lagrangian, VectorValue{3,Float64}, order)
Vfree = TestFESpace(model, reffe; conformity=:H1)
V = TestFESpace(
    model, reffe; conformity=:H1,
    dirichlet_tags=["surface_bottom"],
    dirichlet_masks=[(true,true,true)]
)

@show num_free_dofs(Vfree), num_free_dofs(V)

g(x) = VectorValue(0.0,0.0,0.0)
U = TrialFESpace(V, [g])

E  = 210e9
ν  = 0.3
λ  = (E*ν)/((1+ν)*(1-2ν))
μ  = E/(2*(1+ν))
ε(u) = symmetric_gradient(u)
# ε(u) = 0.5*(∇(u) + ∇(u)')
dim = num_cell_dims(model)  # 2 or 3

# ε(u) = symmetric(∇(u))
I    = one(TensorValue{dim,dim,Float64})
σ(u) = λ*tr(ε(u))*I + 2μ*ε(u)


pa = 1e6
Γtop  = BoundaryTriangulation(
    Ω;
    tags=["surface_top"]
)
∂Γtop = Measure(Γtop, degree)
@info "num Γtop cells" num_cells(Γtop)
A_top = sum( ∫( 1.0 )∂Γtop )[1]
@info "Γtop area" A_top

n_top = Gridap.CellData.get_normal_vector(Γtop)
l(v) = ∫( pa * (v ⋅ n_top) )∂Γtop
a(u,v) = ∫( ε(v) ⊙ σ(u) )∂Ω

op = AffineFEOperator(a, l, U, V)

uh = solve(op)

εh = ε(uh)
σh = σ(uh)
wh = 0.5*(εh ⊙ σh)

writevtk(
  Ω, files_path*"elasticity_all";
  cellfields = [
    "u" => uh,
    "epsilon" => εh,              # εh = ε(uh)
    "sigma" => σh,                # σh = σ(uh)
    "strain_energy" => wh         # wh = 0.5*(εh ⊙ σh)
  ],
)

d = num_cell_dims(model) - 1
mask_top_global    = get_face_mask(labels, "surface_top",    d)
mask_bottom_global = get_face_mask(labels, "surface_bottom", d)
mask_boundary_global = get_isboundary_face(topo, d) 
boundary_dface_ids = findall(mask_boundary_global)

# global -> local
mask_top_local    = mask_top_global[boundary_dface_ids]
mask_bottom_local = mask_bottom_global[boundary_dface_ids]

writevtk(
  Γ, files_path*"boundary_tags";
  cellfields = [
    "surface_top"    => CellField(Float32.(mask_top_local), Γ),
    "surface_bottom" => CellField(Float32.(mask_bottom_local), Γ),
  ],
)

Result

Displacement in XYZ axis

Thought

In practice, I felt that Gridap is user-friendly, provided one has an understanding of the structure of the finite element method.
However, the distinction between global and local indices is extremely difficult to grasp. In addition, there seems to be little information on where exactly data are stored and which functions should be used to retrieve them. As mentioned earlier, verifying reliability is important.