Skip to content

Add threading to mesh::h, mesh::cell_normals and mesh::compute_midpoints - #4380

Draft
garth-wells wants to merge 8 commits into
mainfrom
garth/thread-h-normals-midpoints
Draft

Add threading to mesh::h, mesh::cell_normals and mesh::compute_midpoints#4380
garth-wells wants to merge 8 commits into
mainfrom
garth/thread-h-normals-midpoints

Conversation

@garth-wells

Copy link
Copy Markdown
Member

Summary

  • Add a num_threads parameter (default 1) to mesh::h, mesh::cell_normals, and mesh::compute_midpoints in cpp/dolfinx/mesh/utils.h.
  • These are per-entity loops that write to disjoint output slots with no cross-iteration dependency — unlike sibling functions in this file, they previously had no way to use more than one thread. The threading follows the existing common::local_range + std::jthread pattern used elsewhere in the mesh module.
  • Expose num_threads through the nanobind wrapper (mesh.h) and the mesh.py convenience wrappers (Mesh.h, compute_midpoints), matching the existing create_entities/create_entity_permutations precedent (nb::arg("num_threads") = 1 at the binding layer, Python-level default num_threads: int = 1).

Test plan

  • ninja clean build of libdolfinx
  • clang-format --dry-run --Werror / ruff check / ruff format --check / mypy all pass on touched files
  • C++ unit tests pass serially and under mpirun -n 3
  • Python mesh/geometry unit tests pass
  • Bit-identical results verified (all cell_normals cell-type branches: interval, triangle, quadrilateral) across num_threads in {1, 2, 3, 4, 8} and 1-3 MPI ranks

🤖 Generated with Claude Code

garth-wells and others added 4 commits August 8, 2026 21:46
These per-entity loops write to disjoint output slots with no
cross-iteration dependency, unlike sibling functions in this file which
already accept num_threads. Add a num_threads parameter (default 1) to
each, following the existing common::local_range + std::jthread pattern,
and expose it through the Python bindings and mesh.py convenience
wrappers, matching the create_entities/create_entity_permutations
precedent.

Verified bit-identical results (including all cell_normals cell-type
branches: interval, triangle, quadrilateral) across num_threads in
{1, 2, 3, 4, 8} and 1-3 MPI ranks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Doxygen only enforces full @PARAM coverage on a function once at least
one parameter is documented. Adding num_threads without documenting
the pre-existing parameters tripped WARN_AS_ERROR in the docs build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
},
nb::arg("mesh"), nb::arg("dim"), nb::arg("entities"));
nb::arg("mesh"), nb::arg("dim"), nb::arg("entities"),
nb::arg("num_threads") = 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We avoid defaults in the nanobind layer, and handle any defaults in the Python later.

#4303 (comment)

@garth-wells
garth-wells marked this pull request as draft August 9, 2026 12:01
Comment thread cpp/dolfinx/mesh/utils.h
{
std::span<const T, 3> p1(x.data() + 3 * e_vertices[j], 3);
h[e] = std::max(h[e], delta_norm(p0, p1));
std::span<const T, 3> p0(x.data() + 3 * e_vertices[i], 3);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use subspan?

Comment thread cpp/dolfinx/mesh/utils.h
std::span<const T, 3> p0(x.data() + 3 * e_vertices[i], 3);
for (std::size_t j = i + 1; j < e_vertices.size(); ++j)
{
std::span<const T, 3> p1(x.data() + 3 * e_vertices[j], 3);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here, subspan?

Comment thread cpp/dolfinx/mesh/utils.h
std::array vertices{geometry_entities[i * eshape1],
geometry_entities[i * eshape1 + 1]};
std::array p
= {std::span<const T, 3>(x.data() + 3 * vertices[0], 3),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subspans?

Comment thread cpp/dolfinx/mesh/utils.h
[](auto x, auto y) { return x - y; });

T norm = std::sqrt(t[0] * t[0] + t[1] * t[1]);
std::span<T, 3> ni(n.data() + 3 * i, 3);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subspan?

Comment thread cpp/dolfinx/mesh/utils.h

return n;
// TODO: check the quadrilateral case
return run_threaded(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment regarding subspans in this section.

Comment thread cpp/dolfinx/mesh/utils.h
// into x_mid_chunk. This is thread-safe.
auto compute_midpoints_chunk = [](std::span<const std::int32_t> e_to_g,
std::size_t num_xdofs_per_entity,
std::span<const T> x, std::span<T> x_mid)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subspans!:D

Comment thread python/test/unit/mesh/test_mesh.py Outdated
num_cells = mesh.topology.index_map(tdim).size_local
cells = np.arange(num_cells, dtype=np.int32)
h = _cpp.mesh.h(mesh._cpp_object, tdim, cells)
h = _cpp.mesh.h(mesh._cpp_object, tdim, cells, 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we parametrize one of this tests over num threads to check the new code logic?

Comment thread python/test/unit/mesh/test_face.py Outdated
fdim = square.topology.dim - 1
facets = locate_entities_boundary(square, fdim, left_side)
normals = cell_normals(square._cpp_object, fdim, facets)
normals = cell_normals(square._cpp_object, fdim, facets, 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parametrize of thread size (1, 2)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants