Skip to content

Commit ef1270d

Browse files
committed
Merge branch 'main' into release
2 parents 6f31e9b + f88ca65 commit ef1270d

File tree

7 files changed

+95
-32
lines changed

7 files changed

+95
-32
lines changed

.github/workflows/oneapi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,4 @@ jobs:
109109
run: |
110110
. /opt/intel/oneapi/setvars.sh
111111
cd python/test/unit
112-
mpiexec -n 2 pytest .
112+
mpiexec -n 2 pytest -vs .

cpp/dolfinx/common/IndexMap.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,3 +947,35 @@ const std::vector<int>& IndexMap::dest() const noexcept { return _dest; }
947947
//-----------------------------------------------------------------------------
948948
bool IndexMap::overlapped() const noexcept { return _overlapping; }
949949
//-----------------------------------------------------------------------------
950+
std::array<double, 2> IndexMap::imbalance() const
951+
{
952+
std::array<double, 2> imbalance{-1., -1.};
953+
std::array<std::int32_t, 2> max_count;
954+
std::array<std::int32_t, 2> local_sizes
955+
= {static_cast<std::int32_t>(_local_range[1] - _local_range[0]),
956+
static_cast<std::int32_t>(_ghosts.size())};
957+
958+
// Find the maximum number of owned indices and the maximum number of ghost
959+
// indices across all processes.
960+
MPI_Allreduce(local_sizes.data(), max_count.data(), 2,
961+
dolfinx::MPI::mpi_type<std::int32_t>(), MPI_MAX, _comm.comm());
962+
963+
std::int32_t total_num_ghosts = 0;
964+
MPI_Allreduce(&local_sizes[1], &total_num_ghosts, 1,
965+
dolfinx::MPI::mpi_type<std::int32_t>(), MPI_SUM, _comm.comm());
966+
967+
// Compute the average number of owned and ghost indices per process.
968+
int comm_size = dolfinx::MPI::size(_comm.comm());
969+
double avg_owned = static_cast<double>(_size_global) / comm_size;
970+
double avg_ghosts = static_cast<double>(total_num_ghosts) / comm_size;
971+
972+
// Compute the imbalance by dividing the maximum number of indices by the
973+
// corresponding average.
974+
if (avg_owned > 0)
975+
imbalance[0] = max_count[0] / avg_owned;
976+
if (avg_ghosts > 0)
977+
imbalance[1] = max_count[1] / avg_ghosts;
978+
979+
return imbalance;
980+
}
981+
//-----------------------------------------------------------------------------

cpp/dolfinx/common/IndexMap.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,22 @@ class IndexMap
228228
/// false.
229229
bool overlapped() const noexcept;
230230

231+
/// @brief Returns the imbalance of the current IndexMap.
232+
///
233+
/// The imbalance is a measure of load balancing across all processes, defined
234+
/// as the maximum number of indices on any process divided by the average
235+
/// number of indices per process. This function calculates the imbalance
236+
/// separately for owned indices and ghost indices and returns them as a
237+
/// std::array<double, 2>. If the total number of owned or ghost indices is
238+
/// zero, the respective entry in the array is set to -1.
239+
///
240+
/// @note This is a collective operation and must be called by all processes
241+
/// in the communicator associated with the IndexMap.
242+
///
243+
/// @return An array containing the imbalance in owned indices
244+
/// (first element) and the imbalance in ghost indices (second element).
245+
std::array<double, 2> imbalance() const;
246+
231247
private:
232248
// Range of indices (global) owned by this process
233249
std::array<std::int64_t, 2> _local_range;

cpp/dolfinx/geometry/BoundingBoxTree.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,11 @@ class BoundingBoxTree
328328
const int mpi_size = dolfinx::MPI::size(comm);
329329

330330
// Send root node coordinates to all processes
331-
std::array<T, 6> send_bbox = {0, 0, 0, 0, 0, 0};
331+
// This is to counteract the fact that a process might have 0 bounding box
332+
// causing false positives on process collisions around (0,0,0)
333+
constexpr T max_val = std::numeric_limits<T>::max();
334+
std::array<T, 6> send_bbox
335+
= {max_val, max_val, max_val, max_val, max_val, max_val};
332336
if (num_bboxes() > 0)
333337
std::copy_n(std::prev(_bbox_coordinates.end(), 6), 6, send_bbox.begin());
334338
std::vector<T> recv_bbox(mpi_size * 6);

cpp/dolfinx/geometry/utils.h

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ std::vector<T> shortest_vector(const mesh::Mesh<T>& mesh, int dim,
4848
{
4949
for (std::size_t e = 0; e < entities.size(); e++)
5050
{
51+
// Check that we have sent in valid entities, i.e. that they exist in the
52+
// local dofmap. One gets a cryptical memory segfault if entities is -1
53+
assert(entities[e] >= 0);
5154
auto dofs
5255
= MDSPAN_IMPL_STANDARD_NAMESPACE::MDSPAN_IMPL_PROPOSED_NAMESPACE::
5356
submdspan(x_dofmap, entities[e],
@@ -674,6 +677,7 @@ determine_point_ownership(const mesh::Mesh<T>& mesh, std::span<const T> points)
674677
std::vector<std::int32_t> cells(num_cells, 0);
675678
std::iota(cells.begin(), cells.end(), 0);
676679
BoundingBoxTree bb(mesh, tdim, cells, padding);
680+
BoundingBoxTree midpoint_tree = create_midpoint_tree(mesh, tdim, cells);
677681
BoundingBoxTree global_bbtree = bb.create_global_tree(comm);
678682

679683
// Compute collisions:
@@ -751,20 +755,15 @@ determine_point_ownership(const mesh::Mesh<T>& mesh, std::span<const T> points)
751755
dolfinx::MPI::mpi_type<T>(), received_points.data(), recv_sizes.data(),
752756
recv_offsets.data(), dolfinx::MPI::mpi_type<T>(), forward_comm);
753757

754-
// Each process checks which points collides with a cell on the process
758+
// Each process checks which local cell is closest and computes the squared
759+
// distance to the cell
755760
const int rank = dolfinx::MPI::rank(comm);
756-
std::vector<std::int32_t> cell_indicator(received_points.size() / 3);
757-
std::vector<std::int32_t> colliding_cells(received_points.size() / 3);
758-
for (std::size_t p = 0; p < received_points.size(); p += 3)
759-
{
760-
std::array<T, 3> point;
761-
std::copy(std::next(received_points.begin(), p),
762-
std::next(received_points.begin(), p + 3), point.begin());
763-
const int colliding_cell
764-
= geometry::compute_first_colliding_cell(mesh, bb, point);
765-
cell_indicator[p / 3] = (colliding_cell >= 0) ? rank : -1;
766-
colliding_cells[p / 3] = colliding_cell;
767-
}
761+
const std::vector<std::int32_t> closest_cells = compute_closest_entity(
762+
bb, midpoint_tree, mesh,
763+
std::span<const T>(received_points.data(), received_points.size()));
764+
const std::vector<T> squared_distances = squared_distance(
765+
mesh, tdim, closest_cells,
766+
std::span<const T>(received_points.data(), received_points.size()));
768767

769768
// Create neighborhood communicator in the reverse direction: send
770769
// back col to requesting processes
@@ -791,20 +790,28 @@ determine_point_ownership(const mesh::Mesh<T>& mesh, std::span<const T> points)
791790
std::swap(recv_offsets, send_offsets);
792791
}
793792

794-
std::vector<std::int32_t> recv_ranks(recv_offsets.back());
793+
// Get distances from closest entity of points that were on the other process
794+
std::vector<T> recv_distances(recv_offsets.back());
795795
MPI_Neighbor_alltoallv(
796-
cell_indicator.data(), send_sizes.data(), send_offsets.data(),
797-
dolfinx::MPI::mpi_type<std::int32_t>(), recv_ranks.data(),
798-
recv_sizes.data(), recv_offsets.data(),
799-
dolfinx::MPI::mpi_type<std::int32_t>(), reverse_comm);
796+
squared_distances.data(), send_sizes.data(), send_offsets.data(),
797+
dolfinx::MPI::mpi_type<T>(), recv_distances.data(), recv_sizes.data(),
798+
recv_offsets.data(), dolfinx::MPI::mpi_type<T>(), reverse_comm);
800799

801800
std::vector<std::int32_t> point_owners(points.size() / 3, -1);
802-
for (std::size_t i = 0; i < unpack_map.size(); i++)
801+
std::vector<T> closest_distance(points.size() / 3, -1);
802+
for (std::size_t i = 0; i < out_ranks.size(); i++)
803803
{
804-
const std::int32_t pos = unpack_map[i];
805-
// Only insert new owner if no owner has previously been found
806-
if ((recv_ranks[i] >= 0) && (point_owners[pos] == -1))
807-
point_owners[pos] = recv_ranks[i];
804+
for (std::int32_t j = recv_offsets[i]; j < recv_offsets[i + 1]; j++)
805+
{
806+
const std::int32_t pos = unpack_map[j];
807+
// If point has not been found yet distance is negative
808+
// If new received distance smaller than current distance choose owner
809+
if (auto d = closest_distance[pos]; d < 0 or d > recv_distances[j])
810+
{
811+
point_owners[pos] = out_ranks[i];
812+
closest_distance[pos] = recv_distances[j];
813+
}
814+
}
808815
}
809816

810817
// Communication is reversed again to send dest ranks to all processes
@@ -847,7 +854,7 @@ determine_point_ownership(const mesh::Mesh<T>& mesh, std::span<const T> points)
847854
owned_recv_points.insert(
848855
owned_recv_points.end(), std::next(received_points.cbegin(), 3 * j),
849856
std::next(received_points.cbegin(), 3 * (j + 1)));
850-
owned_recv_cells.push_back(colliding_cells[j]);
857+
owned_recv_cells.push_back(closest_cells[j]);
851858
}
852859
}
853860
}

python/dolfinx/wrappers/common.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ void common(py::module& m)
9999
.def_property_readonly("local_range",
100100
&dolfinx::common::IndexMap::local_range,
101101
"Range of indices owned by this map")
102+
.def_property_readonly("imbalance", &dolfinx::common::IndexMap::imbalance,
103+
"Imbalance of the current IndexMap.")
102104
.def_property_readonly("index_to_dest_ranks",
103105
&dolfinx::common::IndexMap::index_to_dest_ranks)
104106
.def_property_readonly(

python/test/unit/fem/test_interpolation.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,29 +282,31 @@ def f(x):
282282
u, v = Function(V), Function(V)
283283
u.interpolate(U.sub(i))
284284
v.interpolate(f)
285-
assert np.allclose(u.vector.array, v.vector.array)
285+
assert np.allclose(u.x.array, v.x.array)
286286

287287
# Same map, different elements
288288
gdim = mesh.geometry.dim
289289
V = FunctionSpace(mesh, ("Lagrange", 1, (gdim,)))
290290
u, v = Function(V), Function(V)
291291
u.interpolate(U.sub(i))
292292
v.interpolate(f)
293-
assert np.allclose(u.vector.array, v.vector.array)
293+
assert np.allclose(u.x.array, v.x.array)
294294

295295
# Different maps (0)
296296
V = FunctionSpace(mesh, ("N1curl", 1))
297297
u, v = Function(V), Function(V)
298298
u.interpolate(U.sub(i))
299299
v.interpolate(f)
300-
assert np.allclose(u.vector.array, v.vector.array, atol=1.0e-6)
300+
atol = 5 * np.finfo(u.x.array.dtype).resolution
301+
assert np.allclose(u.x.array, v.x.array, atol=atol)
301302

302303
# Different maps (1)
303304
V = FunctionSpace(mesh, ("RT", 2))
304305
u, v = Function(V), Function(V)
305306
u.interpolate(U.sub(i))
306307
v.interpolate(f)
307-
assert np.allclose(u.vector.array, v.vector.array, atol=1.0e-6)
308+
atol = 5 * np.finfo(u.x.array.dtype).resolution
309+
assert np.allclose(u.x.array, v.x.array, atol=atol)
308310

309311
# Test with wrong shape
310312
V0 = FunctionSpace(mesh, P.sub_elements()[0])
@@ -778,8 +780,8 @@ def test_nonmatching_mesh_single_cell_overlap_interpolation(xtype):
778780
mesh2 = create_rectangle(MPI.COMM_WORLD, [[0.0, 0.0], [p0_mesh2, p0_mesh2]], [n_mesh2, n_mesh2],
779781
cell_type=CellType.triangle, dtype=xtype)
780782

781-
u1 = Function(FunctionSpace(mesh1, ("CG", 1)), name="u1", dtype=xtype)
782-
u2 = Function(FunctionSpace(mesh2, ("CG", 1)), name="u2", dtype=xtype)
783+
u1 = Function(FunctionSpace(mesh1, ("Lagrange", 1)), name="u1", dtype=xtype)
784+
u2 = Function(FunctionSpace(mesh2, ("Lagrange", 1)), name="u2", dtype=xtype)
783785

784786
def f_test1(x):
785787
return 1.0 - x[0] * x[1]

0 commit comments

Comments
 (0)