Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
22f7471
Sketch out strategy and copy maintain_coarse_partitioner (for non gho…
schnellerhase Mar 11, 2025
6160a96
A first version
schnellerhase Mar 11, 2025
c7f6e32
Compiling...
schnellerhase Mar 11, 2025
c4afbcb
Fix: python layer default value of partitioner does not align with cp…
schnellerhase Mar 11, 2025
d865056
Debug
schnellerhase Mar 11, 2025
9b8314b
Move compute_destination_ranks out of anonymous namespace
schnellerhase Mar 11, 2025
c4766dc
Add docstring
schnellerhase Mar 12, 2025
bb79ada
Improve sequential code path
schnellerhase Mar 12, 2025
d2ac333
Add explicit instantiations
schnellerhase Mar 12, 2025
b59a167
Doxygen fix explicit instantiation
schnellerhase Mar 12, 2025
5419dee
Move docstring to header
schnellerhase Mar 12, 2025
6f1fcbc
Remove cpp docstring
schnellerhase Mar 12, 2025
701e399
Change defaults and add special case of config
schnellerhase Mar 12, 2025
1d19a21
Switch to optional to handle cases correctly
schnellerhase Mar 12, 2025
5629a0e
Update docstring
schnellerhase Mar 12, 2025
20cdceb
Merge branch 'main' into fix_3443
schnellerhase Mar 15, 2025
21539cc
Merge branch 'main' into fix_3443
schnellerhase Mar 18, 2025
c386c6a
Fix Python export for optional
schnellerhase Mar 18, 2025
c4cc987
Merge branch 'main' into fix_3443
schnellerhase Mar 18, 2025
6a79e10
Update python default value for refinement optio
schnellerhase Mar 18, 2025
dba96cc
Fix interval refinement test
schnellerhase Mar 18, 2025
c4eaa0b
Merge branch 'main' into fix_3443
garth-wells Mar 19, 2025
c2917dd
Partial fix
schnellerhase Mar 24, 2025
a8a2269
Merge branch 'main' into fix_3443
schnellerhase Mar 24, 2025
8973edc
Track down further
schnellerhase Mar 25, 2025
b769eeb
Showcase None export problem
schnellerhase Mar 25, 2025
b2b9902
Merge branch 'main' into fix_3443
schnellerhase Mar 31, 2025
fe6de20
Remove empty_partitioner and introduce helper variable as sentinel
schnellerhase Mar 31, 2025
91d02f0
Ruff
schnellerhase Mar 31, 2025
6004efb
Tidy docstring
schnellerhase Mar 31, 2025
fd71eeb
Return to previous test state
schnellerhase Mar 31, 2025
a3d79bf
Merge branch 'main' into fix_3443
schnellerhase Apr 2, 2025
fca1671
Add unit test for identity_partitioner
schnellerhase Apr 2, 2025
5b5a611
Merge branch 'main' into fix_3443
garth-wells Apr 12, 2025
31af8d1
Merge branch 'main' into fix_3443
schnellerhase May 29, 2025
cd8c56f
Fix sign compare
schnellerhase May 29, 2025
c754ba8
Tidy logic and consistent naming
schnellerhase May 29, 2025
f1872e9
Merge branch 'main' into fix_3443
schnellerhase Jun 17, 2025
0b976db
Merge branch 'main' into fix_3443
schnellerhase Jun 20, 2025
6aa0e1c
Adapt to docstrng length
schnellerhase Jun 20, 2025
a4666a1
Merge branch 'main' into fix_3443
schnellerhase Jul 5, 2025
bdc6e4e
Merge branch 'main' into fix_3443
schnellerhase Jul 7, 2025
5bd2649
Change to placehodler + variant
schnellerhase Jul 7, 2025
0bc2f9b
Adapt interface
schnellerhase Jul 7, 2025
1f3fd0a
Update docstring
schnellerhase Jul 7, 2025
a3346db
Only once
schnellerhase Jul 7, 2025
d5f3a25
Remvoe not necessary optional and fix tests
schnellerhase Jul 8, 2025
79f25c3
Reactivate interval refinement test
schnellerhase Jul 8, 2025
7255d53
Merge branch 'main' into fix_3443
schnellerhase Jul 10, 2025
2cdbf5b
Merge branch 'main' into fix_3443
schnellerhase Jul 16, 2025
001524b
Remove ghostmode shared_vertex
schnellerhase Jul 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 15 additions & 17 deletions cpp/dolfinx/graph/partitioners.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,8 @@ extern "C"

using namespace dolfinx;

namespace
{
/// @todo Is it un-documented that the owning rank must come first in
/// reach list of edges?
///
/// @param[in] comm The communicator
/// @param[in] graph Graph, using global indices for graph edges
/// @param[in] node_disp The distribution of graph nodes across MPI
/// ranks. The global index `gidx` of local index `lidx` is `lidx +
/// node_disp[my_rank]`.
/// @param[in] part The destination rank for owned nodes, i.e. `dest[i]`
/// is the destination of the node with local index `i`.
/// @return Destination ranks for each local node.
template <typename T>
graph::AdjacencyList<int> compute_destination_ranks(
graph::AdjacencyList<int> dolfinx::graph::compute_destination_ranks(
MPI_Comm comm, const graph::AdjacencyList<std::int64_t>& graph,
const std::vector<T>& node_disp, const std::vector<T>& part)
{
Expand Down Expand Up @@ -202,6 +189,18 @@ graph::AdjacencyList<int> compute_destination_ranks(

return g;
}

/// @cond
template graph::AdjacencyList<int> dolfinx::graph::compute_destination_ranks(
MPI_Comm comm, const graph::AdjacencyList<std::int64_t>& graph,
const std::vector<int>& node_disp, const std::vector<int>& part);

template graph::AdjacencyList<int> dolfinx::graph::compute_destination_ranks(
MPI_Comm comm, const graph::AdjacencyList<std::int64_t>& graph,
const std::vector<unsigned long long>& node_disp,
const std::vector<unsigned long long>& part);
/// @endcond

//-----------------------------------------------------------------------------
#ifdef HAS_PARMETIS
template <typename T>
Expand Down Expand Up @@ -304,7 +303,6 @@ std::vector<int> refine(MPI_Comm comm, const graph::AdjacencyList<T>& adj_graph)
//-----------------------------------------------------------------------------
}
#endif
} // namespace

//-----------------------------------------------------------------------------
#ifdef HAS_PTSCOTCH
Expand Down Expand Up @@ -587,7 +585,7 @@ graph::partition_fn graph::parmetis::partitioner(double imbalance,
{
// FIXME: Is it implicit that the first entry is the owner?
graph::AdjacencyList<int> dest
= compute_destination_ranks(pcomm, graph, node_disp, part);
= graph::compute_destination_ranks(pcomm, graph, node_disp, part);
if (split_comm)
MPI_Comm_free(&pcomm);
return dest;
Expand Down Expand Up @@ -653,7 +651,7 @@ graph::partition_fn graph::kahip::partitioner(int mode, int seed,
timer2.stop();

if (ghosting)
return compute_destination_ranks(comm, graph, node_disp, part);
return graph::compute_destination_ranks(comm, graph, node_disp, part);
else
{
return regular_adjacency_list(std::vector<int>(part.begin(), part.end()),
Expand Down
17 changes: 17 additions & 0 deletions cpp/dolfinx/graph/partitioners.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@

namespace dolfinx::graph
{

/// @todo Is it un-documented that the owning rank must come first in
/// reach list of edges?
///
/// @param[in] comm The communicator
/// @param[in] graph Graph, using global indices for graph edges
/// @param[in] node_disp The distribution of graph nodes across MPI
/// ranks. The global index `gidx` of local index `lidx` is `lidx +
/// node_disp[my_rank]`.
/// @param[in] part The destination rank for owned nodes, i.e. `dest[i]`
/// is the destination of the node with local index `i`.
/// @return Destination ranks for each local node.
template <typename T>
graph::AdjacencyList<int> compute_destination_ranks(
MPI_Comm comm, const graph::AdjacencyList<std::int64_t>& graph,
const std::vector<T>& node_disp, const std::vector<T>& part);

namespace scotch
{
#ifdef HAS_PTSCOTCH
Expand Down
104 changes: 93 additions & 11 deletions cpp/dolfinx/refinement/refine.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,90 @@

#pragma once

#include "dolfinx/common/MPI.h"
#include "dolfinx/graph/AdjacencyList.h"
#include "dolfinx/graph/partitioners.h"
#include "dolfinx/mesh/Mesh.h"
#include "dolfinx/mesh/Topology.h"
#include "dolfinx/mesh/cell_types.h"
#include "dolfinx/mesh/graphbuild.h"
#include "dolfinx/mesh/utils.h"
#include "interval.h"
#include "plaza.h"
#include <algorithm>
#include <concepts>
#include <mpi.h>
#include <optional>
#include <spdlog/spdlog.h>
#include <stdexcept>
#include <utility>
#include <variant>

namespace dolfinx::refinement
{

/**
* @brief Create a cell partitioner which maintains the partition of a coarse
* mesh.
*
* @tparam T floating point type of mesh geometry.
* @param parent_mesh mesh indicating the partition scheme to use, i.e. the
* coarse mesh.
* @param parent_cell list of cell indices mapping cells of the new refined mesh
* into the coarse mesh, usually output of `refinement::refine`.
* @return The created cell partition function.
*/
template <std::floating_point T>
mesh::CellPartitionFunction
create_identity_partitioner(const mesh::Mesh<T>& parent_mesh,
std::span<std::int32_t> parent_cell)
{
// TODO: optimize for non ghosted mesh?

return [&parent_mesh,
parent_cell](MPI_Comm comm, int /*nparts*/,
std::vector<mesh::CellType> cell_types,
std::vector<std::span<const std::int64_t>> cells)
-> graph::AdjacencyList<std::int32_t>
{
const auto parent_cell_im
= parent_mesh.topology()->index_map(parent_mesh.topology()->dim());
std::int32_t parent_num_cells = parent_cell_im->size_local();
std::span parent_cell_owners = parent_cell_im->owners();

std::int32_t num_cells
= cells.front().size() / mesh::num_cell_vertices(cell_types.front());
std::vector<std::int32_t> destinations(num_cells);

int rank = dolfinx::MPI::rank(comm);
for (std::size_t i = 0; i < destinations.size(); i++)
{
bool parent_is_ghost_cell = parent_cell[i] > parent_num_cells;
if (parent_is_ghost_cell)
destinations[i] = parent_cell_owners[parent_cell[i] - parent_num_cells];
else
destinations[i] = rank;
}

if (comm == MPI_COMM_NULL)
return graph::regular_adjacency_list(std::move(destinations), 1);

auto dual_graph = mesh::build_dual_graph(comm, cell_types, cells);
std::vector<std::int32_t> node_disp(MPI::size(comm) + 1, 0);
std::int32_t local_size = dual_graph.num_nodes();
MPI_Allgather(&local_size, 1, dolfinx::MPI::mpi_t<std::int32_t>,
node_disp.data() + 1, 1, dolfinx::MPI::mpi_t<std::int32_t>,
comm);
std::partial_sum(node_disp.begin(), node_disp.end(), node_disp.begin());
return compute_destination_ranks(comm, dual_graph, node_disp, destinations);
};
}

/// @brief Placeholder for the creation of an identity partitioner in refine.
struct IdentityPartitionerPlaceholder
{
};

/// @brief Refine a mesh with markers.
///
/// The refined mesh can be optionally re-partitioned across processes.
Expand All @@ -35,30 +104,28 @@ namespace dolfinx::refinement
/// refined mesh will **not** include ghosts cells (cells connected by
/// facet to an owned cell) even if the parent mesh is ghosted.
///
/// @warning Passing `nullptr` for `partitioner`, the refined mesh will
/// **not** have ghosts cells even if the parent mesh is ghosted. The
/// possibility to not re-partition the refined mesh and include ghost
/// cells in the refined mesh will be added in a future release.
///
/// @param[in] mesh Input mesh to be refined.
/// @param[in] edges Indices of the edges that should be split in the
/// refinement. If not provided (`std::nullopt`), uniform refinement is
/// performed.
/// @param[in] partitioner Partitioner to be used to distribute the
/// refined mesh. If not callable, refined cells will be on the same
/// @param[in] partitioner (Optional) partitioner to be used to distribute the
/// refined mesh. If not provided (`std::nullopt`) the partition of the coarse
/// mesh will be maintained. If not callable, refined cells will be on the same
/// process as the parent cell.
/// @param[in] option Control the computation of parent facets, parent
/// cells. If an option is not selected, an empty list is returned.
/// cells.
/// @return New mesh, and optional parent cell indices and parent facet
/// indices.
template <std::floating_point T>
std::tuple<mesh::Mesh<T>, std::optional<std::vector<std::int32_t>>,
std::optional<std::vector<std::int8_t>>>
refine(const mesh::Mesh<T>& mesh,
std::optional<std::span<const std::int32_t>> edges,
const mesh::CellPartitionFunction& partitioner
= mesh::create_cell_partitioner(mesh::GhostMode::none),
Option option = Option::none)
std::variant<IdentityPartitionerPlaceholder, mesh::CellPartitionFunction>
partitioner
= IdentityPartitionerPlaceholder(),
Option option = Option::parent_cell)
{
auto topology = mesh.topology();
assert(topology);
Expand All @@ -70,9 +137,23 @@ refine(const mesh::Mesh<T>& mesh,
? interval::compute_refinement_data(mesh, edges, option)
: plaza::compute_refinement_data(mesh, edges, option);

if (std::holds_alternative<IdentityPartitionerPlaceholder>(partitioner))
{
if (!parent_cell)
{
throw std::runtime_error(
"Identity partitioner relies on parent cell computation");
}
assert(parent_cell);
partitioner = create_identity_partitioner(mesh, parent_cell.value());
}

assert(std::holds_alternative<mesh::CellPartitionFunction>(partitioner));

mesh::Mesh<T> mesh1 = mesh::create_mesh(
mesh.comm(), mesh.comm(), cell_adj.array(), mesh.geometry().cmap(),
mesh.comm(), new_vertex_coords, xshape, partitioner);
mesh.comm(), new_vertex_coords, xshape,
std::get<mesh::CellPartitionFunction>(partitioner));

// Report the number of refined cells
const int D = topology->dim();
Expand All @@ -84,4 +165,5 @@ refine(const mesh::Mesh<T>& mesh,

return {std::move(mesh1), std::move(parent_cell), std::move(parent_facet)};
}

} // namespace dolfinx::refinement
25 changes: 12 additions & 13 deletions python/dolfinx/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
to_string,
to_type,
)
from dolfinx.cpp.refinement import RefinementOption
from dolfinx.cpp.refinement import IdentityPartitionerPlaceholder, RefinementOption
from dolfinx.fem import CoordinateElement as _CoordinateElement
from dolfinx.fem import coordinate_element as _coordinate_element
from dolfinx.graph import AdjacencyList
Expand Down Expand Up @@ -550,8 +550,10 @@ def transfer_meshtag(
def refine(
msh: Mesh,
edges: typing.Optional[np.ndarray] = None,
partitioner: typing.Optional[typing.Callable] = create_cell_partitioner(GhostMode.none),
option: RefinementOption = RefinementOption.none,
partitioner: typing.Union[
typing.Callable, IdentityPartitionerPlaceholder
] = IdentityPartitionerPlaceholder(),
option: RefinementOption = RefinementOption.parent_cell,
) -> tuple[Mesh, npt.NDArray[np.int32], npt.NDArray[np.int8]]:
"""Refine a mesh.

Expand All @@ -563,20 +565,17 @@ def refine(
mesh will **not** include ghosts cells (cells connected by facet
to an owned cells) even if the parent mesh is ghosted.

Warning:
Passing ``None`` for ``partitioner``, the refined mesh will
**not** have ghosts cells even if the parent mesh has ghost
cells. The possibility to not re-partition the refined mesh and
include ghost cells in the refined mesh will be added in a
future release.

Args:
msh: Mesh from which to create the refined mesh.
edges: Indices of edges to split during refinement. If ``None``,
mesh refinement is uniform.
partitioner: Partitioner to distribute the refined mesh. If
``None`` no redistribution is performed, i.e. refined cells
remain on the same process as the parent cell.
partitioner: Partitioner to distribute the refined mesh. If a
``IdentityPartitionerPlaceholder`` is passed (default) no
redistribution is performed, i.e. refined cells remain on the
same process as the parent cell, but the ghost layer is
updated. If a custom partitioner is passed, it will be used for
distributing the refined mesh. If ``None`` is passed no
redistribution will happen.
option: Controls whether parent cells and/or parent facets are
computed.

Expand Down
43 changes: 35 additions & 8 deletions python/dolfinx/wrappers/refinement.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (C) 2018-2024 Chris N. Richardson and Garth N. Wells
// Copyright (C) 2018-2024 Chris N. Richardson, Garth N. Wells and Paul T.
// Kühner
//
// This file is part of DOLFINx (https://www.fenicsproject.org)
//
Expand All @@ -23,9 +24,12 @@
#include <nanobind/stl/optional.h>
#include <nanobind/stl/shared_ptr.h>
#include <nanobind/stl/tuple.h>
#include <nanobind/stl/variant.h>
#include <nanobind/stl/vector.h>
#include <optional>
#include <span>
#include <stdexcept>
#include <variant>

namespace nb = nanobind;

Expand All @@ -45,8 +49,9 @@ void export_refinement(nb::module_& m)
std::optional<
nb::ndarray<const std::int32_t, nb::ndim<1>, nb::c_contig>>
edges,
std::optional<
dolfinx_wrappers::part::impl::PythonCellPartitionFunction>
std::variant<dolfinx::refinement::IdentityPartitionerPlaceholder,
std::optional<dolfinx_wrappers::part::impl::
PythonCellPartitionFunction>>
partitioner,
dolfinx::refinement::Option option)
{
Expand All @@ -57,11 +62,29 @@ void export_refinement(nb::module_& m)
std::span(edges.value().data(), edges.value().size()));
}

dolfinx_wrappers::part::impl::CppCellPartitionFunction cpp_partitioner
= partitioner.has_value()
? dolfinx_wrappers::part::impl::create_cell_partitioner_cpp(
partitioner.value())
: nullptr;
std::variant<dolfinx::refinement::IdentityPartitionerPlaceholder,
dolfinx_wrappers::part::impl::CppCellPartitionFunction>
cpp_partitioner
= dolfinx::refinement::IdentityPartitionerPlaceholder();
if (std::holds_alternative<std::optional<
dolfinx_wrappers::part::impl::PythonCellPartitionFunction>>(
partitioner))
{
auto optional = std::get<std::optional<
dolfinx_wrappers::part::impl::PythonCellPartitionFunction>>(
partitioner);
if (!optional.has_value())
cpp_partitioner
= dolfinx_wrappers::part::impl::CppCellPartitionFunction(
nullptr);
else
{
cpp_partitioner
= dolfinx_wrappers::part::impl::create_cell_partitioner_cpp(
optional.value());
}
}

auto [mesh1, cell, facet] = dolfinx::refinement::refine(
mesh, cpp_edges, cpp_partitioner, option);

Expand Down Expand Up @@ -97,6 +120,10 @@ void refinement(nb::module_& m)
export_refinement<float>(m);
export_refinement<double>(m);

nb::class_<dolfinx::refinement::IdentityPartitionerPlaceholder>(
m, "IdentityPartitionerPlaceholder")
.def(nb::init<>());

nb::enum_<dolfinx::refinement::Option>(m, "RefinementOption")
.value("none", dolfinx::refinement::Option::none)
.value("parent_facet", dolfinx::refinement::Option::parent_facet)
Expand Down
3 changes: 1 addition & 2 deletions python/test/unit/refinement/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
def test_refine_interval(n, ghost_mode, ghost_mode_refined, option):
msh = mesh.create_interval(MPI.COMM_WORLD, n, [0, 1], ghost_mode=ghost_mode)
msh_refined, edges, vertices = mesh.refine(
msh,
option=option,
msh, option=option, partitioner=mesh.create_cell_partitioner(ghost_mode_refined)
)
# TODO: add create_cell_partitioner(ghost_mode) when works

Expand Down
Loading
Loading