Writing a mesh checkpoint#
In this example, we will demonstrate how to write a mesh checkpoint to disk.
We start by creating a simple unit-square mesh.
import logging
from pathlib import Path
from mpi4py import MPI
import dolfinx
import ipyparallel as ipp
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)
Note that when a mesh is created in DOLFINx, we send in an MPI communicator. The communicator is used to partition (distribute) the mesh across the available processes. This means that each process only have access to a sub-set of cells and nodes of the mesh. We can inspect these with the following commands:
def print_mesh_info(mesh: dolfinx.mesh.Mesh):
cell_map = mesh.topology.index_map(mesh.topology.dim)
node_map = mesh.geometry.index_map()
print(
f"Rank {mesh.comm.rank}: number of owned cells {cell_map.size_local}",
f", number of ghosted cells {cell_map.num_ghosts}\n",
f"Number of owned nodes {node_map.size_local}",
f", number of ghosted nodes {node_map.num_ghosts}",
)
print_mesh_info(mesh)
Rank 0: number of owned cells 200 , number of ghosted cells 0
Number of owned nodes 121 , number of ghosted nodes 0
Create a distributed mesh#
Next, we can use IPython parallel to inspect a partitioned mesh.
We create a convenience function for creating a mesh that shares cells on the boundary
between two processes if ghosted=True
.
def create_distributed_mesh(ghosted: bool, N: int = 10):
"""
Create a distributed mesh with N x N cells. Share cells on process boundaries
if ghosted is set to True
"""
from mpi4py import MPI
import dolfinx
ghost_mode = dolfinx.mesh.GhostMode.shared_facet if ghosted else dolfinx.mesh.GhostMode.none
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, N, N, ghost_mode=ghost_mode)
print(f"{ghost_mode=}")
print_mesh_info(mesh)
Next we start up a new cluster with three engines.
As we defined print_mesh_info
locally on this process, we need to push it to all engines.
with ipp.Cluster(engines="mpi", n=3, log_level=logging.ERROR) as cluster:
# Push print_mesh_info to all engines
cluster[:].push({"print_mesh_info": print_mesh_info})
# Create mesh with ghosted cells
query_true = cluster[:].apply_async(create_distributed_mesh, True)
query_true.wait()
assert query_true.successful(), query_true.error
print("".join(query_true.stdout))
# Create mesh without ghosted cells
query_false = cluster[:].apply_async(create_distributed_mesh, False)
query_false.wait()
assert query_false.successful(), query_false.error
print("".join(query_false.stdout))
ghost_mode=dolfinx.cpp.mesh.GhostMode.shared_facet
Rank 0: number of owned cells 66 , number of ghosted cells 9
Number of owned nodes 40 , number of ghosted nodes 13
ghost_mode=dolfinx.cpp.mesh.GhostMode.shared_facet
Rank 1: number of owned cells 67 , number of ghosted cells 14
Number of owned nodes 37 , number of ghosted nodes 20
ghost_mode=dolfinx.cpp.mesh.GhostMode.shared_facet
Rank 2: number of owned cells 67 , number of ghosted cells 11
Number of owned nodes 44 , number of ghosted nodes 13
ghost_mode=dolfinx.cpp.mesh.GhostMode.none
Rank 0: number of owned cells 66 , number of ghosted cells 0
Number of owned nodes 40 , number of ghosted nodes 6
ghost_mode=dolfinx.cpp.mesh.GhostMode.none
Rank 1: number of owned cells 67 , number of ghosted cells 0
Number of owned nodes 37 , number of ghosted nodes 9
ghost_mode=dolfinx.cpp.mesh.GhostMode.none
Rank 2: number of owned cells 67 , number of ghosted cells 0
Number of owned nodes 44 , number of ghosted nodes 4
Writing a mesh checkpoint#
The input data to a mesh is:
A geometry: the set of points in R^D that are part of each cell
A two-dimensional connectivity array: A list that indicates which nodes of the geometry is part of each cell
A reference element: Used for push data back and forth from the reference element and computing Jacobians We now use adios4dolfinx to write a mesh to file.
def write_mesh(filename: Path):
import subprocess
from mpi4py import MPI
import dolfinx
import adios4dolfinx
# Create a simple unit square mesh
mesh = dolfinx.mesh.create_unit_square(
MPI.COMM_WORLD, 10, 10, cell_type=dolfinx.mesh.CellType.quadrilateral
)
# Write mesh checkpoint
adios4dolfinx.write_mesh(filename, mesh, engine="BP4")
# Inspect checkpoint on rank 0 with `bpls`
if mesh.comm.rank == 0:
output = subprocess.run(["bpls", "-a", "-l", str(filename.absolute())], capture_output=True)
print(output.stdout.decode("utf-8"))
mesh_file = Path("mesh.bp")
with ipp.Cluster(engines="mpi", n=2, log_level=logging.ERROR) as cluster:
# Write mesh to file
query = cluster[:].apply_async(write_mesh, mesh_file)
query.wait()
assert query.successful(), query.error
print("".join(query.stdout))
string CellType attr = "quadrilateral"
int32_t Degree attr = 1
int32_t LagrangeVariant attr = 2
double MeshTime {1} = 0 / 0
double Points {121, 2} = 0 / 1
int64_t Topology {100, 4} = 0 / 120
We observe that we have stored all the data needed to re-create the mesh in the file mesh.bp
.
We can therefore read it (to any number of processes) with adios4dolfinx.read_mesh
def read_mesh(filename: Path):
from mpi4py import MPI
import dolfinx
import adios4dolfinx
mesh = adios4dolfinx.read_mesh(
filename, comm=MPI.COMM_WORLD, engine="BP4", ghost_mode=dolfinx.mesh.GhostMode.none
)
print_mesh_info(mesh)
Reading mesh checkpoints (N-to-M)#
We can now read the checkpoint on a different number of processes than we wrote it on.
with ipp.Cluster(engines="mpi", n=4, log_level=logging.ERROR) as cluster:
# Write mesh to file
cluster[:].push({"print_mesh_info": print_mesh_info})
query = cluster[:].apply_async(read_mesh, mesh_file)
query.wait()
assert query.successful(), query.error
print("".join(query.stdout))
Rank 0: number of owned cells 25 , number of ghosted cells 0
Number of owned nodes 28 , number of ghosted nodes 8
Rank 1: number of owned cells 25 , number of ghosted cells 0
Number of owned nodes 33 , number of ghosted nodes 3
Rank 2: number of owned cells 25 , number of ghosted cells 0
Number of owned nodes 29 , number of ghosted nodes 7
Rank 3: number of owned cells 25 , number of ghosted cells 0
Number of owned nodes 31 , number of ghosted nodes 5