DMPlex (Unstructured Meshes + FEM)
DMPlex manages unstructured meshes and provides a complete finite element workflow: mesh creation, field discretisation, residual/Jacobian assembly via pointwise callbacks, boundary conditions, VTK output, and Lagrangian mesh advection.
Overview
The typical workflow is:
- Create a mesh — from a Gmsh file or a built-in box (or triangular) mesh.
- Set up finite elements — velocity, pressure, auxiliary fields.
- Define the PetscDS — attach pointwise residual/Jacobian callbacks.
- Set boundary conditions — Dirichlet via
add_boundary!. - Configure SNES — attach the FEM assembly and null spaces to solve nonlinear problems.
- Time loop — solve, update history fields, write VTK.
A complete, worked example is in examples/ex62b.jl (2D/3D Stokes with Maxwell viscoelasticity, power-law rheology, and Lagrangian advection).
Creating a Mesh
From a Gmsh file
using PETSc, MPI
MPI.Init()
petsclib = PETSc.getlib(; PetscScalar = Float64)
PETSc.initialize(petsclib)
dm = PETSc.DMPlex(petsclib, MPI.COMM_WORLD; dm_plex_filename = "mesh.msh")Any option accepted by DMSetFromOptions can be passed as a keyword argument. Useful options include -dm_refine N (uniform refinement) and -dm_plex_dim D to force the mesh dimension.
Box mesh (built-in)
dm = PETSc.DMPlex(petsclib, MPI.COMM_WORLD, 2, true, (10, 10))
# ^dim ^simplex ^facessimplex = true produces triangles (2D) / tetrahedra (3D); false gives quads / hexahedra.
Finite Element Setup
Creating FE spaces
dim = LibPETSc.DMGetDimension(petsclib, dm)
simplex = PETSc.isplexsimplex(dm)
# P2 velocity (dim components, degree 2)
fe_vel = PETSc.fe_create_default(petsclib, MPI.COMM_SELF, dim, dim, simplex;
degree = 2, prefix = "vel_")
# Discontinuous P1 pressure (1 component, degree 1)
let opts = PETSc.Options(petsclib; pres_petscdualspace_lagrange_continuity = 0)
push!(opts)
global fe_pres = PETSc.fe_create_default(petsclib, MPI.COMM_SELF, dim, 1, simplex;
degree = 1, prefix = "pres_")
pop!(opts)
end
# Lagrange P1 (explicit degree, no PetscOptions lookup)
fe_p1 = PETSc.fe_create_lagrange(petsclib, MPI.COMM_SELF, dim, dim, simplex, 1)
# Copy quadrature rule from velocity to pressure for consistency
PETSc.fe_copy_quadrature!(petsclib, fe_vel, fe_pres)Attaching FE spaces to the DM
PETSc.setfield!(dm, 0, fe_vel) # field 0 = velocity
PETSc.setfield!(dm, 1, fe_pres) # field 1 = pressure
PETSc.createds!(dm) # finalise the PetscDSNaming objects
LibPETSc.PetscObjectSetName(petsclib, convert(Ptr{Cvoid}, fe_vel), "velocity")
PETSc.petsc_setname!(petsclib, dm, "stokes")Pointwise Callbacks
PETSc's FEM assembly calls user-supplied C function pointers at each quadrature point. Three macros generate the required @cfunction wrappers from plain Julia functions.
@petsc_simple_fn — boundary / exact-solution functions
Signature: f(t, x, u, ctx) where x is the coordinate vector and u is the output vector.
function bg_vel(t, x, u, ctx)
u[1] = x[1] * 0.1 # Vx = exx * x
u[2] = x[2] * (-0.1) # Vy = eyy * y
end
const bg_vel_ptr = PETSc.@petsc_simple_fn(bg_vel)@petsc_residual_fn — f0 / f1 residual and post-processing functions
Signature: full PETSc pointwise signature with dim_, Nf, NfAux, uOff, uOff_x, u, u_t, u_x, aOff, aOff_x, a, a_t, a_x, t, x, nC, cst, out. The second argument is the number of output components.
function f0_p(dim_, Nf, NfAux, uOff, uOff_x, u, u_t, u_x,
aOff, aOff_x, a, a_t, a_x, t, x, nC, cst, f0)
f0[1] = 0.0
for d in 0:dim_-1; f0[1] -= u_x[d*dim_+d+1]; end
end
const f0_p_ptr = PETSc.@petsc_residual_fn(f0_p, 1)@petsc_jacobian_fn — g0/g1/g2/g3 Jacobian functions
Same signature as @petsc_residual_fn but with an extra utShift argument before x.
function g1_pu(dim_, Nf, NfAux, uOff, uOff_x, u, u_t, u_x,
aOff, aOff_x, a, a_t, a_x, t, utShift, x, nC, cst, g1)
for d in 0:dim_-1; g1[d*dim_+d+1] = -1.0; end
end
const g1_pu_ptr = PETSc.@petsc_jacobian_fn(g1_pu, dim_*dim_)Jacobian callbacks can be computed automatically using ForwardDiff:
function g3_uu(dim_, Nf, NfAux, uOff, uOff_x, u, u_t, u_x, ...)
deviatoric_stress_tangent!(g3, dim_, u_x, ...) # uses ForwardDiff internally
endSee examples/ex62b.jl for a complete example.
PetscDS: Residuals, Jacobians, and Constants
ds = PETSc.getds(dm)
# Residual callbacks (f0 = volume source, f1 = flux)
PETSc.set_residual!(ds, 0, f0_u_ptr, f1_u_ptr) # momentum
PETSc.set_residual!(ds, 1, f0_p_ptr, C_NULL) # continuity
# Jacobian blocks (fieldI, fieldJ, g0, g1, g2, g3)
PETSc.set_jacobian!(ds, 0, 0, C_NULL, C_NULL, C_NULL, g3_uu_ptr)
PETSc.set_jacobian!(ds, 0, 1, C_NULL, C_NULL, g2_up_ptr, C_NULL)
PETSc.set_jacobian!(ds, 1, 0, C_NULL, g1_pu_ptr, C_NULL, C_NULL)
# Preconditioner Jacobian (can differ from the true Jacobian)
PETSc.set_jacobian_preconditioner!(ds, 1, 1, g0_pp_ptr, C_NULL, C_NULL, C_NULL)
# Exact solution (for manufactured solution tests / Dirichlet BCs)
PETSc.set_exact_solution!(ds, 0, exact_vel_ptr)
PETSc.set_exact_solution!(ds, 1, exact_pres_ptr)
# Constants (read by all callbacks as the `cst` argument)
PETSc.set_constants!(ds, [mu, rho, gravity, dt])Boundary Conditions
# Get the boundary label (created by Gmsh or DMPlexCreateBoxMesh)
label = PETSc.getlabel(dm, "Face Sets")
# Dirichlet (essential) BC on velocity (field 0), boundary tag 1
PETSc.add_boundary!(petsclib, dm,
LibPETSc.DM_BC_ESSENTIAL, "wall", label,
PetscInt[1], # boundary tag values
0, # field index
PetscInt[], # components (empty = all)
exact_vel_ptr)
# Split a Gmsh "boundary" label into per-face labels (for box meshes)
PETSc.create_split_boundary_labels!(petsclib, dm)Auxiliary Fields
Auxiliary fields carry per-cell data (phase, history stress, etc.) that callbacks read from the a / aOff arguments.
# Clone the primary DM and set up DG-P0 auxiliary fields
dm_aux = PETSc.dmclone(dm)
fe_phase = PETSc.fe_create_default(petsclib, MPI.COMM_SELF, dim, 1, simplex;
degree = 0, prefix = "phase_")
fe_tau = PETSc.fe_create_default(petsclib, MPI.COMM_SELF, dim, dim*dim, simplex;
degree = 0, prefix = "tau_")
PETSc.setfield!(dm_aux, 0, fe_phase)
PETSc.setfield!(dm_aux, 1, fe_tau)
PETSc.createds!(dm_aux)
aux_vec = PETSc.DMGlobalVec(dm_aux)
# Attach aux_vec so callbacks see it via `a`
LibPETSc.DMSetAuxiliaryVec(petsclib, dm,
LibPETSc.DMLabel(C_NULL), PetscInt(0), PetscInt(0), aux_vec)Projection and L² Norms
# Project exact functions onto a DM vector (initialisation / BCs)
PETSc.dm_project_function!(petsclib, dm, 0.0,
[vel_fn_ptr, pres_fn_ptr], nothing, LibPETSc.INSERT_ALL_VALUES, u)
# Project residual-style callbacks onto a DM vector (post-processing)
PETSc.dm_project_field!(petsclib, dm_out, t, u,
[copy_vel_ptr, copy_pres_ptr, compute_tau_3x3_ptr],
LibPETSc.INSERT_ALL_VALUES, out_vec)
# L² error vs. exact solution
err = PETSc.dm_compute_l2diff(petsclib, dm, t,
[exact_vel_ptr, exact_pres_ptr], nothing, u)SNES Integration and Null Spaces
# Wire FEM residual/Jacobian assembly into the SNES
PETSc.plex_set_snes_local_fem!(petsclib, dm)
# Constant-pressure null space (for incompressible flow)
null_vec = PETSc.DMGlobalVec(dm)
PETSc.dm_project_function!(petsclib, dm, 0.0,
[zero_vel_ptr, one_pres_ptr], nothing, LibPETSc.INSERT_ALL_VALUES, null_vec)
LibPETSc.VecNormalize(petsclib, null_vec)
nullspace = GC.@preserve null_vec PETSc.mat_null_space_create(petsclib, comm, (null_vec,))
PETSc.snes_set_jacobian_null_space!(snes, nullspace)
# Attach constant null space directly to the pressure FE
PETSc.fe_compose_constant_null_space!(petsclib, comm, fe_pres)
# Propagate discretisation to coarser MG levels
let cdm = dm
while convert(Ptr{Cvoid}, cdm) != C_NULL
PETSc.dm_copy_disc!(dm, cdm)
cdm = PETSc.dm_get_coarse(cdm)
end
end
# Cleanup
PETSc.mat_null_space_destroy!(petsclib, nullspace)VTK Output
# Write a named vector to a VTU file (merges parallel pieces automatically)
PETSc.vtk_save!(petsclib, comm, "solution.vtu", out_vec)
# Mark tensor fields so ParaView shows them as tensors
PETSc.vtk_merge_tensor!(fname, "strainrate", "tau")vtk_save! calls PetscViewerVTKOpen + DMView / VecView internally and works in parallel (each rank writes its own piece; PETSc merges the XML). vtk_merge_tensor! post-processes the XML header to annotate multiple tensor fields so ParaView's tensor glyph filter recognises them.
Writing a ParaView PVD animation file
pvd_entries = Tuple{Float64,String}[]
for step in 1:nsteps
# ... solve ...
fname = "out_$(lpad(step, 4, '0')).vtu"
PETSc.vtk_save!(petsclib, comm, fname, out_vec)
push!(pvd_entries, (t, abspath(fname)))
# rewrite PVD after every step so it is always playable
open("sim.pvd", "w") do io
println(io, """<?xml version="1.0"?>""")
println(io, """<VTKFile type="Collection" version="0.1" byte_order="LittleEndian">""")
println(io, " <Collection>")
for (ts, vtu) in pvd_entries
rel = relpath(vtu, dirname(abspath("sim.pvd")))
println(io, """ <DataSet timestep="$ts" group="" part="0" file="$rel"/>""")
end
println(io, " </Collection>")
println(io, "</VTKFile>")
end
endLagrangian Mesh Advection
For moving-mesh simulations, update the coordinate vector directly:
# Project P2 velocity onto P1 (vertex-based) to get nodal velocities
dm_p1 = PETSc.dmclone(dm)
PETSc.setfield!(dm_p1, 0, PETSc.fe_create_lagrange(petsclib, MPI.COMM_SELF, dim, dim, simplex, 1))
PETSc.createds!(dm_p1)
vel_p1 = PETSc.DMGlobalVec(dm_p1)
PETSc.dm_project_field!(petsclib, dm_p1, 0.0, u, [copy_vel_ptr],
LibPETSc.INSERT_ALL_VALUES, vel_p1)
# Move each mesh node by dt * v
PL = typeof(petsclib)
coords = LibPETSc.PetscVec{PL}(C_NULL)
LibPETSc.DMGetCoordinates(petsclib, dm, coords)
LibPETSc.VecAXPY(petsclib, coords, PetscScalar(dt), vel_p1)Pure Lagrangian advection distorts the mesh over time, degrading solver convergence. For background-deformation (bg) setups the mesh does not need to move at all. For large-displacement grav problems, periodic remeshing is needed for long runs.
Examples
ex17.jl — Linear Elasticity
examples/ex17.jl is a Julia port of PETSc's snes/tutorials/ex17.c. It solves linear isotropic elasticity −∇·σ(u) = f on (0,1)^dim with manufactured exact solutions, demonstrating:
- 2D/3D box meshes (simplex triangles/tetrahedra or quad/hex elements)
- Multiple solution types including uniform strain, axial displacement, and geological shear
- Direct, GAMG, and geometric multigrid solvers
- Near-null space (rigid-body modes) for AMG
# 2D Q1 quad mesh, quadratic MMS, direct solver
julia --project examples/ex17.jl \
-dm_plex_simplex 0 -dm_plex_box_faces 4,4 \
-sol_type elas_quad -displacement_petscspace_degree 1 -pc_type lu
# GAMG with rigid-body near-null space (recommended for large problems)
julia --project examples/ex17.jl \
-dm_plex_simplex 0 -dm_plex_box_faces 8,8 \
-sol_type elas_quad -pc_type gamg -ksp_type cg
# Geometric multigrid — 3 refinement levels, 3D
julia --project examples/ex17.jl \
-dm_plex_dim 3 -dm_plex_simplex 0 -dm_plex_box_faces 2,2,2 \
-dm_refine_hierarchy 3 -sol_type elas_quad \
-pc_type mg -pc_mg_type full \
-mg_levels_ksp_type chebyshev -mg_levels_pc_type jacobi -ksp_type cg
# MPI — 4 ranks
mpiexec -n 4 julia --project examples/ex17.jl \
-dm_plex_simplex 0 -dm_plex_box_faces 8,8 \
-sol_type elas_quad -pc_type gamg -ksp_type cgex62.jl — Isoviscous Stokes (MMS verification)
examples/ex62.jl is a Julia port of PETSc's snes/tutorials/ex62.c. It solves constant-viscosity Stokes on the unit square/cube with polynomial or trigonometric manufactured exact solutions, used for convergence verification of the P2/P1 and Q2/Q1 element pairs.
# 2D P2/P1 simplex, quadratic MMS — L² ≈ machine precision (exact for P2)
julia --project examples/ex62.jl -sol quadratic \
-vel_petscspace_degree 2 -pres_petscspace_degree 1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu -ksp_rtol 1e-9
# 2D Q2/Q1 quad, trig MMS, refinement convergence study
julia --project examples/ex62.jl -sol trig \
-dm_plex_simplex 0 -dm_refine 2 \
-vel_petscspace_degree 2 -pres_petscspace_degree 1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu -ksp_rtol 1e-9
# 3D Q2/Q1 hex, quadratic MMS
julia --project examples/ex62.jl -sol quadratic \
-dm_plex_dim 3 -dm_plex_simplex 0 -dm_plex_box_faces 3,3,3 \
-vel_petscspace_degree 2 -pres_petscspace_degree 1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu -ksp_rtol 1e-9ex62.jl uses a single boundary label (id=1) covering the entire box boundary — do not pass -dm_plex_separate_marker.
ex69.jl — Variable-Viscosity Stokes
examples/ex69.jl is a Julia port of PETSc's snes/tutorials/ex69.c. It solves variable-viscosity Stokes on (0,1)² with free-slip walls, using analytical benchmark solutions (solkx with exponential viscosity variation, solcx with a piecewise-constant jump), for convergence rate verification.
# Q2/Q1 quad, SolKx (exponential viscosity)
julia --project examples/ex69.jl \
-dm_plex_simplex 0 -dm_plex_separate_marker \
-vel_petscspace_degree 2 -pres_petscspace_degree 1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu -ksp_rtol 1e-9
# SolCx with viscosity jump etaB=1000
julia --project examples/ex69.jl \
-dm_plex_simplex 0 -dm_plex_separate_marker \
-sol_type solcx -etaB 1e3 \
-vel_petscspace_degree 2 -pres_petscspace_degree 1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu -ksp_rtol 1e-9
# Refinement study — 2 uniform refinements, Q2/Q1
julia --project examples/ex69.jl \
-dm_plex_simplex 0 -dm_plex_separate_marker -dm_refine 2 \
-vel_petscspace_degree 2 -pres_petscspace_degree 1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu -ksp_rtol 1e-9
# MPI — 4 ranks, SolCx, viscosity jump 1000
mpiexec -n 4 julia --project examples/ex69.jl \
-dm_plex_simplex 0 -dm_plex_separate_marker -dm_refine 1 \
-sol_type solcx -etaB 1e3 \
-vel_petscspace_degree 2 -pres_petscspace_degree 1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu -ksp_rtol 1e-9Expected convergence rates (L² velocity+pressure combined):
- Q2/Q1 quad: ~4× per refinement (O(h²), pressure-dominated)
- Q1/P0 quad: ~2× per refinement (O(h), velocity-dominated)
Examples: Stokes Flow (ex62b.jl)
examples/ex62b.jl solves the incompressible Stokes equations on a square/cube domain containing a circular/spherical inclusion meshed with Gmsh. It supports four solution types (-sol quadratic|trig|bg|grav), power-law and Maxwell viscoelastic rheology, time stepping, and VTK/PVD output.
Solver options
Three solver families are available, trading exactness for scalability:
| Label | Velocity block | Pressure block | Best for |
|---|---|---|---|
| (A) Direct | LU (lu) | LU | Small 2D problems, exact reference |
| (B) GAMG | CG + GAMG | Jacobi | Large 2D, 3D, moderate viscosity contrast |
| (C) GMG | FGMRES + geometric MG | Jacobi | 3D with coarse base mesh + refinement hierarchy |
All three use a Schur-complement fieldsplit preconditioner:
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur
-pc_fieldsplit_schur_factorization_type full
-pc_fieldsplit_schur_precondition a112D examples
Pure shear, direct solver (quick test, serial):
julia --project=examples examples/ex62b.jl \
-sol bg -exx_bg 1.0 -eyy_bg -1.0 -mu_inclusion 0.1 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu \
-ksp_rtol 1e-9 -snes_rtol 1e-9 -vtk_output out.vtuGravity-driven sinking cylinder, viscous, direct solver (20 time steps):
julia --project=examples examples/ex62b.jl \
-sol grav -mesh_h 0.05 -mesh_h_inclusion 0.02 \
-rho_bg 1.0 -rho_inclusion 2.0 -gravity 1.0 -mu_inclusion 0.1 \
-dt 0.02 -nsteps 20 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu \
-ksp_rtol 1e-9 -snes_rtol 1e-9 -vtk_output grav_viscous.vtuGravity-driven sinking cylinder, Maxwell viscoelastic (τ_rel = η/G = 0.2):
julia --project=examples examples/ex62b.jl \
-sol grav -mesh_h 0.05 -mesh_h_inclusion 0.02 \
-rho_bg 1.0 -rho_inclusion 2.0 -gravity 1.0 -mu_inclusion 0.1 \
-G_bg 5.0 -G_inclusion 0.5 -dt 0.01 -nsteps 50 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu \
-ksp_rtol 1e-9 -snes_rtol 1e-9 -vtk_output grav_viscoelastic.vtuPure shear, power-law inclusion (n=3), viscoelastic matrix (100 steps, GAMG):
julia --project=examples examples/ex62b.jl \
-sol bg -exx_bg 1.0 -eyy_bg -1.0 \
-mesh_h 0.04 -mesh_h_inclusion 0.015 \
-mu_inclusion 0.1 -n_inclusion 3.0 -eps0_inclusion 0.5 \
-G_bg 10.0 -G_inclusion 1.0 -dt 0.005 -nsteps 100 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_ksp_type cg -fieldsplit_velocity_pc_type gamg \
-fieldsplit_pressure_ksp_type preonly -fieldsplit_pressure_pc_type jacobi \
-ksp_type fgmres -ksp_rtol 1e-6 -snes_rtol 1e-6 -snes_max_it 20 \
-vtk_output shear_powerlaw.vtuAnalytical viscoelastic verification — Maxwell stress build-up, τ_rel = η/G = 1:
julia --project=examples examples/ex62b.jl \
-sol bg -exx_bg 0.1 -eyy_bg -0.1 \
-mesh_h 0.5 -mu_inclusion 1.0 -G_bg 1.0 -G_inclusion 1.0 \
-dt 0.1 -nsteps 30 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_pc_type lu \
-fieldsplit_pressure_ksp_rtol 1e-9 -fieldsplit_pressure_pc_type lu \
-ksp_rtol 1e-9 -snes_rtol 1e-9The code prints the numerical τ_II alongside the continuous and discrete-BE analytical solutions at each step so convergence can be verified.
3D examples
Add -dm_plex_dim 3 to switch to a sphere-in-cube geometry. The z strain rate (ezz) is set automatically to -(exx+eyy) to enforce incompressibility.
3D pure shear, algebraic multigrid (GAMG):
julia --project=examples examples/ex62b.jl \
-dm_plex_dim 3 -sol bg -exx_bg 1.0 -eyy_bg -0.5 -mu_inclusion 0.01 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_ksp_type cg -fieldsplit_velocity_pc_type gamg \
-fieldsplit_pressure_ksp_type preonly -fieldsplit_pressure_pc_type jacobi \
-ksp_type fgmres -ksp_rtol 1e-6 -snes_rtol 1e-6 -vtk_output out3d.vtu3D pure shear, geometric multigrid (coarse mesh h=0.4, 2 uniform refinements → 3 MG levels):
julia --project=examples examples/ex62b.jl \
-dm_plex_dim 3 -sol bg -exx_bg 1.0 -eyy_bg -0.5 -mu_inclusion 0.01 \
-mesh_h 0.4 -dm_refine 2 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_ksp_type fgmres -fieldsplit_velocity_pc_type mg \
-fieldsplit_velocity_mg_levels_ksp_type chebyshev \
-fieldsplit_velocity_mg_levels_pc_type sor \
-fieldsplit_velocity_mg_coarse_pc_type lu \
-fieldsplit_pressure_ksp_type preonly -fieldsplit_pressure_pc_type jacobi \
-ksp_type fgmres -ksp_rtol 1e-6 -snes_rtol 1e-6 -vtk_output out3d_mg.vtuWith -dm_refine N, PETSc builds a refinement hierarchy inside the preconditioner only. The SNES still assembles on the coarse Gmsh mesh (-mesh_h), so solution accuracy is at mesh_h resolution. Use a smaller mesh_h for finer physics; -dm_refine only controls the number of MG levels. GAMG (algebraic MG) requires no mesh hierarchy and is generally preferred for moderate viscosity contrasts.
3D gravity-driven sinking sphere, viscoelastic, GAMG (30 time steps):
julia --project=examples examples/ex62b.jl \
-dm_plex_dim 3 -sol grav \
-mesh_h 0.3 -mesh_h_inclusion 0.12 \
-rho_bg 1.0 -rho_inclusion 2.0 -gravity 1.0 -mu_inclusion 0.1 \
-G_bg 5.0 -G_inclusion 0.5 -dt 0.02 -nsteps 30 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_ksp_type cg -fieldsplit_velocity_pc_type gamg \
-fieldsplit_pressure_ksp_type preonly -fieldsplit_pressure_pc_type jacobi \
-ksp_type fgmres -ksp_rtol 1e-6 -snes_rtol 1e-6 -vtk_output sphere_sinking.vtuRunning in parallel with MPI
Every example above runs in parallel without code changes — just prepend mpiexec -n N:
mpiexec -n 4 julia --project=examples examples/ex62b.jl \
-dm_plex_dim 3 -sol grav \
-mesh_h 0.2 -mesh_h_inclusion 0.08 \
-rho_bg 1.0 -rho_inclusion 2.0 -gravity 1.0 -mu_inclusion 0.1 \
-G_bg 5.0 -G_inclusion 0.5 -dt 0.02 -nsteps 30 \
-pc_use_amat -pc_type fieldsplit -pc_fieldsplit_type schur \
-pc_fieldsplit_schur_factorization_type full -pc_fieldsplit_schur_precondition a11 \
-fieldsplit_velocity_ksp_type cg -fieldsplit_velocity_pc_type gamg \
-fieldsplit_pressure_ksp_type preonly -fieldsplit_pressure_pc_type jacobi \
-ksp_type fgmres -ksp_rtol 1e-6 -snes_rtol 1e-6 -vtk_output sphere_sinking.vtuThe mesh is partitioned automatically by PETSc's DMPlex. The direct solver (-fieldsplit_velocity_pc_type lu) works in serial only; use GAMG or GMG for parallel runs. VTK output is written per-rank and merged into a single XML collection automatically by vtk_save!.
For large HPC runs, see Running on HPC Systems for MPI launch syntax on different cluster schedulers.
API Reference
PETSc.DMPlex — Method
DMPlex(
petsclib::PetscLib,
comm::MPI.Comm,
dim::Integer,
simplex::Bool,
faces::AbstractVector{<:Integer};
lower = ntuple(_ -> 0.0, dim),
upper = ntuple(_ -> 1.0, dim),
periodicity = ntuple(_ -> DM_BOUNDARY_NONE, dim),
interpolate = true,
localize_height = 0,
sparse_localize = false,
setfromoptions = true,
dmsetup = false,
prefix = "",
options...,
)Create a structured-grid box DMPLEX mesh in dim dimensions with faces[d] cells along axis d. When simplex == true each box cell is split into simplices (triangles in 2D, tetrahedra in 3D); otherwise tensor-product cells are used.
External Links
- PETSc Manual:
DMPlex/DMPlexCreateBoxMesh
PETSc.DMPlex — Method
DMPlex(
petsclib::PetscLib,
comm::MPI.Comm;
setfromoptions = true,
dmsetup = false,
prefix = "",
options...,
)Create an empty DMPLEX mesh. Use the keyword options... (or PETSc command-line options) such as dm_plex_box_faces, dm_plex_dim, dm_plex_simplex, dm_plex_filename, etc. to populate the mesh — this is the most flexible entry point and matches the DMCreate + DMSetType(DMPLEX) + DMSetFromOptions flow used in PETSc's tutorials (e.g. snes/ex12.c).
For an explicit box mesh, use the DMPlex(petsclib, comm, dim, simplex, faces; ...) method below.
External Links
- PETSc Manual:
DMPlex/DMPlexCreate
- PETSc Manual:
DM/DMSetType
- PETSc Manual:
DM/DMSetFromOptions
PETSc.add_boundary! — Function
add_boundary!(petsclib, dm, bctype, name, label, values, field, comps,
bcfunc_ptr, bcfunc_t_ptr = C_NULL, ctx = C_NULL) -> bd::PetscIntAttach a boundary condition to dm. label is a Ptr{Cvoid} from getlabel. bcfunc_ptr / bcfunc_t_ptr are @cfunction(...) results matching PETSc's boundary callback signature.
External Links
- PETSc Manual:
DM/DMAddBoundary
PETSc.add_natural_boundary! — Function
add_natural_boundary!(petsclib, dm, ds, name, label, label_value, field, f0_ptr, f1_ptr = C_NULL) -> bd::PetscIntRegister a natural (Neumann) boundary condition on dm following the PETSc 3.22+ pattern used in the C tutorials. Equivalent to:
DMAddBoundary(dm, DM_BC_NATURAL, name, label, 1, &val, field, 0, NULL, NULL, NULL, NULL, &bd);
PetscDSGetBoundary(ds, bd, &wf, NULL, ...);
PetscWeakFormSetIndexBdResidual(wf, label, val, field, 0, 0, f0, 0, NULL);f0_ptr is a @petsc_bd_fn-generated C-callable function pointer implementing the boundary integrand (PetscBdPointFn signature, with the outward normal n[] between x[] and numConstants).
External Links
- PETSc Manual:
DM/DMAddBoundary
- PETSc Manual:
Dm/PetscWeakFormSetIndexBdResidual
PETSc.create_split_boundary_labels! — Function
create_split_boundary_labels!(dm)Split the single "marker" label on a 2D box mesh into four per-wall labels ("markerBottom", "markerRight", "markerTop", "markerLeft", marker IDs 1–4) so that corner DOFs can belong to multiple wall labels simultaneously.
PETSc.createds! — Method
createds!(dm::AbstractPetscDM)Build the PetscDS for dm from the currently-attached fields.
External Links
- PETSc Manual:
DM/DMCreateDS
PETSc.dm_coarsen_hook_add! — Function
dm_coarsen_hook_add!(dm, coarsenhook, restricthook = C_NULL)Register a C callback invoked each time dm is coarsened (e.g. during FAS hierarchy setup). The coarsenhook signature is:
hook(fine::Ptr{Cvoid}, coarse::Ptr{Cvoid}, ctx::Ptr{Cvoid}) → CintUse @cfunction(f, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid})) to create the pointer. restricthook (optional) is called on each nonlinear solve restriction step.
PETSc.dm_compute_l2diff — Function
dm_compute_l2diff(petsclib, dm, time, funcs, ctxs, X) -> PetscRealCompute the L² error between the global vector X and the pointwise exact functions funcs (one per field, matching PetscSimplePointFn signature). ctxs is a matching vector of context pointers, or nothing to use C_NULL for every field. Requires that set_exact_solution! has been called.
External Links
- PETSc Manual:
DM/DMComputeL2Diff
PETSc.dm_copy_disc! — Function
dm_copy_disc!(src::AbstractPetscDM, dst::AbstractPetscDM)Copy the discretisation (fields, PetscDS, BCs) from src to dst. Useful when propagating FEM setup to coarser levels of a multigrid hierarchy.
External Links
- PETSc Manual:
DM/DMCopyDisc
PETSc.dm_create_global_vec — Method
dm_create_global_vec(dm::AbstractPetscDM) -> AbstractPetscVecAllocate a global vector matching the layout of dm.
PETSc.dm_create_local_vec — Method
dm_create_local_vec(dm::AbstractPetscDM) -> AbstractPetscVecAllocate a local (ghosted) vector matching the layout of dm.
PETSc.dm_get_coarse — Function
dm_get_coarse(dm::AbstractPetscDM) -> AbstractPetscDMReturn the coarse DM from which dm was obtained by refinement (e.g. via -dm_refine_hierarchy). The returned DM is a borrowed reference owned by PETSc; do not call destroy on it. Check convert(Ptr{Cvoid}, cdm) == C_NULL to detect when there is no coarser level.
External Links
- PETSc Manual:
DM/DMGetCoarseDM
PETSc.dm_global_to_local! — Method
dm_global_to_local!(dm, global_vec, local_vec; mode = INSERT_VALUES)Scatter global_vec into local_vec (including ghost values).
PETSc.dm_project_field! — Function
dm_project_field!(petsclib, dm, time, U, funcs, mode, X)Project a function of the fields in the input vector U into the FE space of dm, writing the result into X. funcs is a vector of Ptr{Cvoid} function pointers (one per field in dm), each with the PetscPointFn / @petsc_residual_fn signature. U must be associated with a DM that shares the same mesh as dm (e.g. obtained via dmclone).
Use this to compute derived quantities (e.g. stress from displacement gradient) and project them onto a new FE field for visualisation.
External Links
- PETSc Manual:
DM/DMProjectField
PETSc.dm_project_function! — Function
dm_project_function!(petsclib, dm, time, funcs, ctxs, mode, X)Project a collection of pointwise functions into the global vector X. funcs is a vector of @cfunction pointers (one per field) matching PETSc's PetscSimplePointFn signature. ctxs is a matching vector of context pointers, or nothing to use C_NULL for every field.
mode is an InsertMode enum value, typically INSERT_ALL_VALUES (sets constrained DOFs too) or INSERT_VALUES (free DOFs only).
External Links
- PETSc Manual:
DM/DMProjectFunction
PETSc.dm_set_auxiliary_vec! — Method
dm_set_auxiliary_vec!(dm, aux_local)Attach a local auxiliary vector aux_local to dm (global label / value 0 / part 0). The auxiliary field values are forwarded to all pointwise functions as the a argument.
PETSc.dmclone — Method
dmclone(dm::AbstractPetscDM) -> AbstractPetscDMReturn a new DM that is a clone of dm (same topology, no fields or DS).
PETSc.fe_compose_constant_null_space! — Function
fe_compose_constant_null_space!(petsclib, comm, fe)Create a trivial (constant) MatNullSpace and attach it to the FE object fe under the key "nullspace" via PetscObjectCompose. This signals to the fieldsplit preconditioner that the field has a constant null space.
PETSc.fe_copy_quadrature! — Method
fe_copy_quadrature!(petsclib, src_fe, dst_fe)Copy the quadrature rule from src_fe to dst_fe so both fields share the same integration points.
PETSc.fe_create_default — Method
fe_create_default(petsclib, comm, dim, Nc, simplex; degree = 1, prefix = "", qorder = -1)Create a default PetscFE (Lagrange) for a single field with Nc components on either a simplex (simplex == true) or tensor cell.
degree is the polynomial degree (1 = P1/Q1, 2 = P2/Q2, etc.). It is injected into the options database under prefix as -${prefix}petscspace_degree before the call to PetscFECreateDefault, so any options explicitly set by the user will take precedence.
External Links
- PETSc Manual:
FE/PetscFECreateDefault
PETSc.fe_create_lagrange — Method
fe_create_lagrange(petsclib, comm, dim, Nc, simplex, degree; qorder = -1)Create a PetscFE Lagrange element of polynomial degree degree for a single field with Nc components on either a simplex (simplex == true) or tensor cell. Unlike fe_create_default, the degree is specified explicitly and does not depend on the options database.
External Links
- PETSc Manual:
FE/PetscFECreateLagrange
PETSc.getds — Function
getds(dm::AbstractPetscDM) -> PetscDSReturn the PetscDS (discrete system) attached to dm.
External Links
- PETSc Manual:
DM/DMGetDS
PETSc.getlabel — Function
getlabel(dm::AbstractPetscDM, name::AbstractString) -> Ptr{Cvoid}Look up a DMLabel on dm by name and return its raw pointer (Ptr{Cvoid}). Returns C_NULL if no label with that name exists.
External Links
- PETSc Manual:
DM/DMGetLabel
PETSc.isplexsimplex — Method
isplexsimplex(dm::AbstractPetscDM) -> BoolReturn true when the cells of dm (assumed to be a DMPLEX) are simplices.
External Links
- PETSc Manual:
DMPlex/DMPlexIsSimplex
PETSc.mat_null_space_create — Function
mat_null_space_create(petsclib, comm, vecs) -> MatNullSpaceCreate a null space spanned by the given vectors. Each element of vecs must have a .ptr field holding the underlying PETSc Vec handle (CVec). The vectors should be orthonormal; call VecNormalize beforehand if needed.
PETSc.mat_null_space_create — Method
mat_null_space_create(petsclib, comm; has_const = true) -> MatNullSpaceCreate a null space containing the constant vector (Neumann / saddle-point problems).
PETSc.mat_null_space_destroy! — Method
mat_null_space_destroy!(petsclib, nullsp)Destroy a MatNullSpace created by mat_null_space_create.
PETSc.mat_set_null_space! — Method
mat_set_null_space!(mat, nullsp)Attach nullsp to mat so the linear solver removes it each iteration.
PETSc.petsc_setname! — Method
petsc_setname!(petsclib, obj, name)Set the name of any PETSc object (DM, Vec, FE, …) to name. obj can be any pointer type that is convertible to Ptr{Cvoid}.
Thin wrapper around PetscObjectSetName that avoids needing an explicit convert(Ptr{Cvoid}, obj) at the call site.
External Links
- PETSc Manual:
Sys/PetscObjectSetName
PETSc.plex_set_snes_local_fem! — Function
plex_set_snes_local_fem!(petsclib, dm; use_obj = false, ctx = C_NULL)Tell SNES to use DMPlex's built-in FEM residual / Jacobian assembly on dm.
External Links
- PETSc Manual:
DMPlex/DMPlexSetSNESLocalFEM
PETSc.plexdistribute! — Method
plexdistribute!(dm::AbstractPetscDM; overlap = 0) -> Union{Nothing, AbstractPetscDM}Distribute the (serial) DMPLEX dm across the communicator with the given point-overlap. Returns the new distributed DM on the owning communicator, or nothing if no redistribution occurred (e.g. serial run).
The original dm is not destroyed — the caller is responsible for that.
External Links
- PETSc Manual:
DMPlex/DMPlexDistribute
PETSc.set_constants! — Method
set_constants!(ds::PetscDS, constants)Set the named constants (accessible as constants[i] in pointwise functions) on the PetscDS ds. constants must be an AbstractVector whose elements are convertible to PetscScalar.
External Links
- PETSc Manual:
Dm/PetscDSSetConstants
PETSc.set_exact_solution! — Method
set_exact_solution!(ds, field, sol_ptr, ctx = C_NULL)Register the pointwise exact-solution function sol_ptr for field field (0-based) of the PetscDS ds. The function pointer must match PETSc's PetscSimplePointFn / PetscErrorCode f(dim, time, x, Nc, u, ctx) signature.
This is required for DMComputeL2Diff and DMComputeExactSolution to work.
External Links
- PETSc Manual:
Dm/PetscDSSetExactSolution
PETSc.set_jacobian! — Method
set_jacobian!(ds, fieldI, fieldJ, g0_ptr, g1_ptr, g2_ptr, g3_ptr)Attach the pointwise Jacobian point-function quadruple for the block (fieldI, fieldJ) of the PetscDS ds. Pass C_NULL for any unused term.
External Links
- PETSc Manual:
Dm/PetscDSSetJacobian
PETSc.set_jacobian_preconditioner! — Method
set_jacobian_preconditioner!(ds, field_i, field_j, g0, g1, g2, g3)Set the Jacobian preconditioner terms for the weak form between fields field_i and field_j. Each gN argument is either a Ptr{Cvoid} function pointer or C_NULL.
This is the preconditioner equivalent of set_jacobian!.
External Links
- PETSc Manual:
Dm/PetscDSSetJacobianPreconditioner
PETSc.set_residual! — Method
set_residual!(ds, field, f0_ptr, f1_ptr)Attach the pointwise residual point-function pair to field field (0-based) of the PetscDS ds. f0_ptr and f1_ptr must be @cfunction(...)-style function pointers (Ptr{Cvoid}) matching PETSc's PetscPointFn signature.
External Links
- PETSc Manual:
Dm/PetscDSSetResidual
PETSc.setfield! — Function
setfield!(dm::AbstractPetscDM, field::Integer, fe; label = C_NULL)Attach a PetscFE (or other discretization object) as the field-th field of dm (0-based field index, matching PETSc).
External Links
- PETSc Manual:
DM/DMSetField
PETSc.snes_set_jacobian_null_space! — Function
snes_set_jacobian_null_space!(snes, nullsp)Retrieve the assembled Jacobian matrix from snes (after SNESSetUp) and attach nullsp to it. Must be called after SNESSetUp and before SNESSolve.
PETSc.vtk_merge_tensor! — Method
vtk_merge_tensor!(fname, names...)Post-process a VTK .vtu file written by PETSc's VTK viewer to merge 9 separate scalar DataArrays named name.0…name.8 (produced when a field has more than 3 components) into a single NumberOfComponents="9" DataArray, for each name given. Also sets Tensors="name1 name2 …" on the <PointData> tag so ParaView treats the arrays as 3×3 tensors. The file is rewritten in-place.
PETSc's VTK writer always splits fields with more than 3 components into separate scalar arrays; this function reassembles them for proper tensor visualisation.
PETSc.vtk_save! — Function
vtk_save!(petsclib, comm, filename, vec)Write the global vector vec to a VTK unstructured-grid file (.vtu), readable by ParaView and VisIt. Works for both serial and MPI runs; PETSc gathers all ranks into a single file. The filename must end in .vtu. The mesh geometry is taken from the DM attached to vec by PETSc.
PETSc.vtk_save_fields! — Function
vtk_save_fields!(petsclib, comm, filename, vecs)Write multiple global vectors to a single VTK file (.vtu). vecs is any iterable of AbstractPetscVec; each is written as a separate point-data array. The field name shown in ParaView/VisIt comes from the name set on the vector via PetscObjectSetName.
This is the multi-field equivalent of vtk_save!.
PETSc.@petsc_bd_fn — Macro
@petsc_bd_fn(f, outsz)Generate a C-callable wrapper for the pure-Julia boundary pointwise function f and return its C pointer, matching the PetscBdPointFn signature. Identical to @petsc_residual_fn but with an extra outward-normal vector n between x and numConstants.
User function signature:
f(dim_, Nf, NfAux, uOff, uOff_x, u, u_t, u_x,
aOff, aOff_x, a, a_t, a_x, t, x, n, numConstants, constants, out)PetscInt, PetscReal, PetscScalar must be in scope.
PETSc.@petsc_jacobian_fn — Macro
@petsc_jacobian_fn(f, outsz)Generate a C-callable wrapper for the pure-Julia Jacobian function f and return its C pointer, matching the PetscPointJacFn signature. Identical to @petsc_residual_fn but with an extra u_tShift scalar between t and x.
User function signature:
f(dim_, Nf, NfAux, uOff, uOff_x, u, u_t, u_x,
aOff, aOff_x, a, a_t, a_x, t, u_tShift, x, numConstants, constants, out)PetscInt, PetscReal, PetscScalar must be in scope.
PETSc.@petsc_residual_fn — Macro
@petsc_residual_fn(f, outsz)Generate a C-callable wrapper for the pure-Julia residual function f and return its C pointer, matching the PetscPointFn signature. All pointer arguments are unwrapped into Julia Vectors before calling f.
outsz is an expression giving the output array length in terms of dim_, Nf, NfAux, or numConstants (e.g. Nf for f0-type functions, dim_*Nf for f1-type functions).
User function signature:
f(dim_, Nf, NfAux, uOff, uOff_x, u, u_t, u_x,
aOff, aOff_x, a, a_t, a_x, t, x, numConstants, constants, out)PetscInt, PetscReal, PetscScalar must be in scope.
PETSc.@petsc_simple_fn — Macro
@petsc_simple_fn(f)Generate a C-callable wrapper for f(t, x, u, ctx) and return its C pointer, matching the PetscSimplePointFn signature. Arguments seen by f:
t— time (PetscReal)x— coordinates (Vector{PetscReal}, lengthdim)u— output field values (Vector{PetscScalar}, lengthNc)ctx— user context (Ptr{Cvoid})
Used by dm_project_function!, add_boundary!, set_exact_solution!. PetscInt, PetscReal, PetscScalar must be in scope.