September 13, 2025

Data Structure in FaceLabeling

I struggled with ChatGPT to resolve bugs in my code, but finally gave up. It just tells us codes without thinking so much. What I understood is that seeing original code is fastest way. Before seeing the code, I was so reluctant to do that, seeing codes, which is implemented by others, is kind of headache. But What I found is that data structure and functions are so simple. Probably because this is a feature of Programing Language, Julia.

Lets see key data structure to label boundary, this is actual implementation that original repository uses.

struct FaceLabeling <: GridapType
    d_to_dface_to_entity :: Vector{Vector{Int32}}
    tag_to_entities      :: Vector{Vector{Int32}}
    tag_to_name          :: Vector{String}

I do not write detail here, but you can easily imagine what data is stored here.

Fundamental Usage to access mesh data

Get Entities

labels = get_face_labeling(model)
tag_A   = Gridap.Geometry.get_tag_from_name(labels, "surface_A")
ids_A   = Gridap.Geometry.get_tag_entities(labels, tag_A)

Some useful functions

d = num_cell_dims(model) - 1
topo  = Gridap.Geometry.get_grid_topology(model)
is_bdry = Gridap.Geometry.get_isboundary_face(topo, d)
verticies = Gridap.ReferenceFEs.get_face_vertices(topo, d)
cell_faces = get_cell_faces(topo)
face_entity = get_face_entity(labels, d)
nc = num_cells(model)

When I see original repo, the implementation of function get_face_entity is like this.

function get_face_entity(lab::FaceLabeling,d::Integer)
  lab.d_to_dface_to_entity[d+1]
end

What I found is, Julia is a language that combines small functions and structures to create larger processes.

Functions I coded

Export Boundary with Tag Information

To verify what tags are assigned on the boundary is important.


function write_boundary_tag_ids(
  model;
  tag_names::Vector{String},
  outname::String = "boundary_tags",
  background::Int = 0,
)
  D     = num_cell_dims(model)
  d     = D - 1 # facet
  topo  = Gridap.Geometry.get_grid_topology(model)
  labels = Gridap.Geometry.get_face_labeling(model)

  is_bdry = Gridap.Geometry.get_isboundary_face(topo, d)
  bdry_gface_ids = findall(is_bdry)

  tag_ids  = [Gridap.Geometry.get_tag_from_name(labels, nm) for nm in tag_names]
  masks    = [Gridap.Geometry.get_face_mask(labels, tid, d) for tid in tag_ids]  # ::Vector{Vector{Bool}}

  out = fill(Int(background), length(bdry_gface_ids))
  for (i, m) in enumerate(masks)
    m_bdry = m[bdry_gface_ids]
    for (k, flag) in pairs(m_bdry)
      if flag && out[k] == background
        out[k] = i
      end
    end
  end

  Γall = Gridap.Geometry.BoundaryTriangulation(model)
  Gridap.writevtk(
    Γall, outname;
    cellfields = ["tag_id" => Gridap.CellField(out, Γall)],
  )

  return nothing
end

Export Info with Independent Tag

function write_boundary_tag_ids2(
  model;
  tag_names::Vector{String},
  outname::String = "boundary_tags",
  background::Int = 0,
)
  D      = Gridap.Geometry.num_cell_dims(model)
  d      = D - 1
  topo   = Gridap.Geometry.get_grid_topology(model)
  labels = Gridap.Geometry.get_face_labeling(model)

  is_bdry         = Gridap.Geometry.get_isboundary_face(topo, d)
  bdry_gface_ids  = findall(is_bdry)  # ::Vector{Int}

  tag_ids = [Gridap.Geometry.get_tag_from_name(labels, nm) for nm in tag_names]
  masks   = [Gridap.Geometry.get_face_mask(labels, tid, d) for tid in tag_ids]  # Vector{Vector{Bool}}

  Γall = Gridap.Geometry.BoundaryTriangulation(model)

  cellfields_int = [
    begin
      m_bdry = m[bdry_gface_ids]
      vals   = ifelse.(m_bdry, Int(tid), background)  # Vector{Int}
      tag => Gridap.CellField(vals, Γall)
    end
    for (m, tid, tag) in zip(masks, tag_ids, tag_names)
  ]

  Gridap.writevtk(Γall, outname; cellfields = cellfields_int)
  return nothing
end