I am experimenting with Gridap package for solving partial differential equations (PDEs) in Julia. I am creating my meshes with GMSH, using the API provided by the Gmsh package in Julia. I save them as *.msh files, and then import them with GridapGmsh package. Finally, I solve the PDE using Gridap package and export the results as *.vtk files.
I am using the following package versions in julia 1.11.3:
[705231aa] Gmsh v0.3.1
⌃ [56d4f2e9] Gridap v0.18.8
[3025c34a] GridapGmsh v0.7.2
In Gmsh, the function gmsh.model.mesh.setOrder(order) is said to “Change the order of the elements in the mesh of the current model to order
” . So, using the API for julia, I could specify second order elements as follows:
#=
Sample code to create a quarter of circle 2D domain and mesh it with GMSH using the API in Julia.
Change the element_order
=#
import Gmsh: gmsh
gmsh.initialize()
gmsh.model.add("Simple mesh with Gmsh API in Julia")
mesh_size = 0.4
element_order = 2 # Modify this to change order of the finite elements.
# Define points of a quarter of circle
p1 = gmsh.model.geo.addPoint(0, 0, 0, mesh_size)
p2 = gmsh.model.geo.addPoint(0, 1, 0, mesh_size)
p3 = gmsh.model.geo.addPoint(1, 0, 0, mesh_size)
# borders of the triangle
bottom = gmsh.model.geo.add_line(p1, p2) # bottom border
arc1 = gmsh.model.geo.addCircleArc(p2, p1, p3) # cicle 1 (start=p2, center=p1, end=p3)
left = gmsh.model.geo.add_line(p3, p1) # left border
gmsh.model.geo.synchronize()
# Combine all borders into a curved loop
quarter_circle = gmsh.model.geo.add_curve_loop([bottom, arc1, left])
gmsh.model.geo.synchronize()
quarter_circle_surface = gmsh.model.geo.addPlaneSurface([quarter_circle])
gmsh.model.geo.synchronize()
# Define tags for the boundaries using Gmsh interface in Julia
gmsh.model.addPhysicalGroup(1, [arc1])
gmsh.model.setPhysicalName(1, 1, "arc1")
gmsh.model.addPhysicalGroup(1, [bottom])
gmsh.model.setPhysicalName(1, 2, "bottom")
gmsh.model.addPhysicalGroup(1, [left])
gmsh.model.setPhysicalName(1, 3, "left")
gmsh.model.addPhysicalGroup(2, [quarter_circle_surface])
gmsh.model.setPhysicalName(2, 4, "quarter_circle_surface")
# Create mesh (order = 1 by default)
gmsh.model.geo.synchronize()
gmsh.model.mesh.generate(2)
gmsh.model.geo.synchronize()
# Change order of mesh elements
gmsh.model.mesh.setOrder(element_order)
gmsh.model.geo.synchronize()
gmsh.write("my_quartercircle_order"*string(element_order)*".msh")
elementOrder = 1
# Set mesh faces visible, mesh points invisible, set geometrical elements all visible with description
gmsh.option.setNumber("Mesh.SurfaceFaces", 1) # Make mesh faces visible
gmsh.option.setNumber("Mesh.Points", 1) # Make mesh points visible
gmsh.option.setNumber("Mesh.NumSubEdges", 6) # Subdivisions of mesh elements to use while rendering (does not affect model)
gmsh.option.setNumber("Geometry.PointNumbers", 1) # Show geometrical elements with descriptions
gmsh.option.setNumber("Geometry.CurveNumbers", 1)
gmsh.option.setNumber("Geometry.SurfaceNumbers", 1)
gmsh.fltk.run()
gmsh.finalize()
As we increase the order of the element from 1 to 3, more points are added and the curved boundaries are more accurately represented.
Mesh examples in GMSH with different order of mesh elements
Mesh file “my_quartercircle_order1.msh”
Mesh file “my_quartercircle_order2.msh”
Mesh file “my_quartercircle_order3.msh”
Importing meshes with GridapGmsh
Notice that GridapGmsh can import meshes with elements of order 1 or 2, but not greater, so my_quartercicle_order3.msh cannot be imported.
Actually, as far as I understand, the input to gridap should be a mesh with element order 1, and importing my_quartercicle_order2.msh in the code below raises some issues.
Next, when I define the test functions, can assign an order for the polynomials of the finite elements discretization in the functions ReferenceFE and TestFESpace. Can I use polynomials of any degree here, regardless of the order of the mesh elements?
#=
Solve Heat a transfer equation in 2D : ∇²T = 0
=#
using Gridap
using GridapGmsh
using GridapMakie, CairoMakie
using FileIO
# Step 1: Load the GMSH mesh
mesh_elements_order = 1 # try setting this equal 2 for an unexpected behaviour.
order_str = string(mesh_elements_order)
mesh_file = "my_quartercicle"*order_str*".msh"
# Create a DiscreteModel from the GMSH mesh file
model = GmshDiscreteModel(mesh_file)
writevtk(model,"model_my_quartercicle_order"*order_str)
# Step 2: Set up a finite element space for H^1 functions
order = 2
reffe = ReferenceFE(lagrangian,Float64,order)
V = TestFESpace(
model,
reffe;conformity=:H1,
dirichlet_tags = ["bottom", "left"]
)
# Step 3: Define the weak form for the Laplace equation
Ω = Triangulation(model)
dΩ = Measure(Ω,2*order)
# Neumann boundary conditions
degree = order + 1
Γ_arc = BoundaryTriangulation(model, tags=["arc1"])
dΓ_arc = Measure(Γ_arc, degree)
# Bilinear form for Laplace operator
a(u, v) = ∫( ∇(v) ⋅ ∇(u) )dΩ
# Right-hand side (homogeneous)
h_arc(x) = -10.0
l(v) = ∫( v*h_arc )dΓ_arc
println("Number of cells in Ω: ", num_cells(Ω))
# Step 4: Define the Dirichlet boundary condition values in a verctor
T_bottom(x) = 40.0
T_left(x) = 20.0
U = TrialFESpace(V, [T_left, T_bottom])
# Step 5: Solve the system
op = AffineFEOperator(a, l, U, V)
ls = LUSolver()
solver = LinearFESolver(ls)
println("Solving PDE with mesh elements of order "*order_str*"and Reffe of order "*string(order));
uh = solve(solver,op)
κ = 5.0
heat_flux = -κ * ∇(uh)
# Step 6: Write the solution to a VTK file (works only with meshes of order 1)
if mesh_elements_order==1
println("Writing solution to a Paraview vtk file");
writevtk(Ω, "solution_pde01", cellfields = ["uh" => uh, "heat_flux" => heat_flux])
end
# Step 7: Plot the solution in Julia (optional)
# Plot the values
figure = Figure()
axis = Axis(
figure[1, 1],
xlabel="x", ylabel="y",
title="Mesh file: $mesh_file, Reffe order: $order",
aspect=DataAspect())
plt = plot!(Ω, uh)
if mesh_elements_order ==1
# order 2 raises an error
wireframe!(Ω, color=:black, linewidth=2)
end
colorbar = Colorbar(figure[1, 2], plt, label="Temperature (°C)")
save("fig_solution_mesh_order"*order_str*"reffe_order"*string(order)*".png", figure)
figure # display
The number of nodes changes when the model is imported with GmshDiscreteModel:
Element order = 1 in GMSH
Info : Reading 'my_quartercicle1.msh'...
Info : 7 entities
Info : 13 nodes
Info : 24 elements
Info : Done reading 'my_quartercicle1.msh'
Element order = 2 in GMSH
Info : Reading 'my_quartercicle2.msh'...
Info : 7 entities
Info : 39 nodes
Info : 24 elements
Info : Done reading 'my_quartercicle2.msh'
For the rest of this question, refer to this mesh of order 1: ‘my_quartercicle1.msh’
Suppose I have created a mesh with elements of order 1 and saved it as “my_quartercicle_order1.msh”. Now I import this mesh into Julia using the function GmshDiscreteModel from the package GridapGmsh.
So, using elements of order 1 in the mesh, I can change the order of the ReferenceFE and solve the same PDE. As I increase the order of the polynomials in Gridap, the accuracy of the results improves as expected eventhough I start with the same “my_quartercicle_order1.msh” file.
I can plot the mesh using ReferenceFE elements of order 1 and 2, and it does not show curved edges.
Questions
- If I understand correctly, the order of the FEM elements defines the order of the polynomials, so I am not sure what happens when I import a mesh with elements of order 1 from Gmsh and then use ReferenceFE of order 2.
- Please confirm if I should only import elements of order 1 from Gmsh into Gridap
- I suppose using ReferenceFE of order 2 creates more nodes in the mesh for gridap. How can I plot the inner nodes in my FEM discretization when I set the ReferenceFE order to 2?
- Can Gridap handle curved geometries like Gmsh does?