Introduction
Installation instructions and first usage examples are shown on the main page. This page collects some additional examples and translation guidelines from the C library p4est
to its Julia wrapper P4est.jl.
High-level overview of the setup
The Julia bindings of p4est
are generated using Clang.jl. This process is described in the dev
folder of P4est.jl. Since all MPI datatypes are mapped to types from MPI.jl, the generated bindings are agnostic of the local MPI installation. Hence, there should be no need to generate new bindings as a user. If there are problems, please let us know, e.g., by creating on issue on GitHub. New bindings only need to be generated for a new version of p4est
that comes with a different API. We will probably provide some new bindings when we learn about this.
Translation guidelines
Many functions of p4est
work with pointers, e.g.,
int p4est_connectivity_is_valid (p4est_connectivity_t * connectivity);
or return pointers to newly created struct
s, e.g.,
p4est_connectivity_t *p4est_connectivity_new_periodic (void);
P4est.jl wraps these C functions accordingly and works with pointers. For a convenient alternative to using raw pointers directly, see the next section on PointerWrapper
s. We also follow the naming scheme of p4est
. For example, we use connectivity::Ptr{p4est_connectivity}
. However, it is sometimes useful/required to also load the wrappers of the C struct
s from their pointers. In this case, we use the naming convention to append _obj
, e.g., connectivity_obj = unsafe_load(connectivity)
. A full example is given here:
julia> using P4est, MPI; MPI.Init()
MPI.ThreadLevel(2)
julia> connectivity = p4est_connectivity_new_periodic()
Ptr{p4est_connectivity} @0x00000000038a4060
julia> @doc(p4est_connectivity)
p4est_connectivity This structure holds the 2D inter-tree connectivity information. Identification of arbitrary faces and corners is possible. The arrays tree_to_* are stored in z ordering. For corners the order wrt. yx is 00 01 10 11. For faces the order is -x +x -y +y. They are allocated [0][0]..[0][3]..[num_trees-1][0]..[num_trees-1][3]. The values for tree_to_face are 0..7 where ttf % 4 gives the face number and ttf / 4 the face orientation code. The orientation is 0 for edges that are aligned in z-order, and 1 for edges that are running opposite in z-order. It is valid to specify num_vertices as 0. In this case vertices and tree_to_vertex are set to NULL. Otherwise the vertex coordinates are stored in the array vertices as [0][0]..[0][2]..[num_vertices-1][0]..[num_vertices-1][2]. The corners are only stored when they connect trees. In this case tree_to_corner indexes into ctt_offset. Otherwise the tree_to_corner entry must be -1 and this corner is ignored. If num_corners == 0, tree_to_corner and corner_to_* arrays are set to NULL. The arrays corner_to_* store a variable number of entries per corner. For corner c these are at position [ctt_offset[c]]..[ctt_offset[c+1]-1]. Their number for corner c is ctt_offset[c+1] - ctt_offset[c]. The entries encode all trees adjacent to corner c. The size of the corner_to_* arrays is num_ctt = ctt_offset[num_corners]. The *_to_attr arrays may have arbitrary contents defined by the user. Field Note –––––––––––––––– ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– num_vertices the number of vertices that define the embedding of the forest (not the topology) num_trees the number of trees num_corners the number of corners that help define topology vertices an array of size (3 * num_vertices) tree_to_vertex embed each tree into c++ R^3 for e.g. visualization (see p4est_vtk.h) tree_attr_bytes bytes per tree in tree_to_attr tree_to_attr not touched by p4est tree_to_tree (4 * num_trees) neighbors across faces tree_to_face (4 * num_trees) face to face+orientation (see description) tree_to_corner (4 * num_trees) or NULL (see description) ctt_offset corner to offset in cornertotree and cornertocorner corner_to_tree list of trees that meet at a corner corner_to_corner list of tree-corners that meet at a corner
julia> connectivity_obj = unsafe_load(connectivity)
p4est_connectivity(4, 1, 1, Ptr{Float64} @0x000000000392c5e0, Ptr{Int32} @0x0000000002ad9750, 0x0000000000000000, Cstring(0x0000000000000000), Ptr{Int32} @0x00000000026d1f50, Ptr{Int8} @0x0000000002a2f600, Ptr{Int32} @0x0000000001d09d50, Ptr{Int32} @0x0000000002a7c3d0, Ptr{Int32} @0x00000000025f3030, Ptr{Int8} @0x0000000002dd6ff0)
julia> connectivity_obj.num_vertices
4
julia> connectivity_obj.num_trees
1
julia> connectivity_obj.num_corners
1
julia> p4est_connectivity_destroy(connectivity)
PointerWrapper
s
As we have seen in the previous section on translation guidelines, many functions provided by p4est
return pointers to struct
s, which requires one to unsafe_load
the pointer in order to access the underlying data. P4est.jl offers a convenient alternative to directly using unsafe_load
by providing the PointerWrapper
data type. If you, e.g., have a pointer to a p4est_connectivity
(i.e., an object of type Ptr{p4est_connectivity}
) called connectivity
, you can use connectivity_pw = PointerWrapper(connectivity)
to obtain a wrapped version of the pointer, where the underlying data can be accessed simply by connectivity_pw.num_trees[]
without the need to call unsafe_load
manually. This works even for nested structures, e.g, where the named field of a struct
is a pointer to a struct
. A full example on how to use the PointerWrapper
is given here (note the nested access at p4est_pw.connectivity.num_trees[]
compared to unsafe_load(unsafe_load(p4est).connectivity).num_trees
without a PointerWrapper
):
julia> using P4est, MPI; MPI.Init()
MPI.ThreadLevel(2)
julia> connectivity = p4est_connectivity_new_periodic()
Ptr{p4est_connectivity} @0x0000000002cb68e0
julia> connectivity_pw = PointerWrapper(connectivity)
PointerWrapper{p4est_connectivity}(Ptr{p4est_connectivity} @0x0000000002cb68e0)
julia> connectivity_pw.num_trees[]
1
julia> p4est = p4est_new_ext(MPI.COMM_WORLD, connectivity, 0, 0, true, 0, C_NULL, C_NULL)
Into p4est_new with min quadrants 0 level 0 uniform 1 New p4est with 1 trees on 1 processors Initial level 0 potential global quadrants 1 per tree 1 Done p4est_new with 1 total quadrants Ptr{P4est.LibP4est.p4est} @0x000000000281bb70
julia> p4est_pw = PointerWrapper(p4est)
PointerWrapper{P4est.LibP4est.p4est}(Ptr{P4est.LibP4est.p4est} @0x000000000281bb70)
julia> p4est_pw.connectivity.num_trees[]
1
julia> p4est_destroy(p4est)
julia> p4est_connectivity_destroy(connectivity)
You may also use the PointerWrapper
to set variables in struct
s. Here we set the user data pointer in the p4est_t
struct
to point to some data:
julia> using P4est, MPI; MPI.Init()
MPI.ThreadLevel(2)
julia> connectivity = p4est_connectivity_new_periodic()
Ptr{p4est_connectivity} @0x0000000003599470
julia> p4est = p4est_new_ext(MPI.COMM_WORLD, connectivity, 0, 0, true, 0, C_NULL, C_NULL)
Into p4est_new with min quadrants 0 level 0 uniform 1 New p4est with 1 trees on 1 processors Initial level 0 potential global quadrants 1 per tree 1 Done p4est_new with 1 total quadrants Ptr{P4est.LibP4est.p4est} @0x0000000002bf28b0
julia> p4est_pw = PointerWrapper(p4est)
PointerWrapper{P4est.LibP4est.p4est}(Ptr{P4est.LibP4est.p4est} @0x0000000002bf28b0)
julia> data = Ref((rand(4), rand(5)))
Base.RefValue{Tuple{Vector{Float64}, Vector{Float64}}}(([0.9563143700262997, 0.16619213386036424, 0.7944128972383298, 0.9264181925787428], [0.750243785467092, 0.7525164883579533, 0.5929672400982664, 0.9314917147561163, 0.6349109946176067]))
julia> GC.@preserve data begin p4est_pw.user_pointer = pointer_from_objref(data) # Call p4est function with callback that uses p4est_pw.user_pointer here. # You may retrieve the data `Ref` in the callback with # data = unsafe_pointer_to_objref(pointer(p4est_pw.user_pointer)) end
Ptr{Nothing} @0x00007f8c8c044390
julia> p4est_destroy(p4est)
julia> p4est_connectivity_destroy(connectivity)
In addition, you can use a PointerWrapper
as an array if the underlying datastructure is an array, i.e. you can access the i
-th element of the underlying array by pw[i]
for a PointerWrapper
pw
. See the following code for a full example:
julia> using P4est, MPI; MPI.Init()
MPI.ThreadLevel(2)
julia> connectivity = p4est_connectivity_new_periodic()
Ptr{p4est_connectivity} @0x000000000226bfe0
julia> p4est = p4est_new_ext(MPI.COMM_WORLD, connectivity, 0, 0, true, 0, C_NULL, C_NULL)
Into p4est_new with min quadrants 0 level 0 uniform 1 New p4est with 1 trees on 1 processors Initial level 0 potential global quadrants 1 per tree 1 Done p4est_new with 1 total quadrants Ptr{P4est.LibP4est.p4est} @0x00000000026cf1d0
julia> p4est_pw = PointerWrapper(p4est)
PointerWrapper{P4est.LibP4est.p4est}(Ptr{P4est.LibP4est.p4est} @0x00000000026cf1d0)
julia> p4est_pw.global_first_quadrant[2]
1
julia> p4est_destroy(p4est)
julia> p4est_connectivity_destroy(connectivity)
Note on MPI datatypes
MPI types used by p4est
are mapped to the types provided by MPI.jl.
p4est | P4est.jl |
---|---|
sc_MPI_Comm | MPI.MPI_Comm |
MPI_Datatype | MPI.MPI_Datatype |
MPI_File | MPI.MPI_File |
In particular, it is currently not possible to use p4est
without MPI support.