DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
utils.h
1// Copyright (C) 2012-2024 Chris N. Richardson, Garth N. Wells, Jørgen S. Dokken
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include <array>
10#include <basix/mdspan.hpp>
11#include <boost/unordered/unordered_flat_map.hpp>
12#include <dolfinx/common/log.h>
13#include <dolfinx/common/types.h>
14#include <dolfinx/fem/ElementDofLayout.h>
15#include <dolfinx/mesh/Topology.h>
16#include <dolfinx/mesh/cell_types.h>
17#include <mpi.h>
18#include <span>
19#include <utility>
20#include <vector>
21
22namespace dolfinx
23{
24
25namespace io
26{
27
62template <typename T>
63std::pair<std::vector<std::int32_t>, std::vector<T>> distribute_entity_data(
64 const mesh::Topology& topology, std::span<const std::int64_t> nodes_g,
65 std::int64_t num_nodes_g, const fem::ElementDofLayout& cmap_dof_layout,
66 md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>> xdofmap,
67 int entity_dim,
68 md::mdspan<const std::int64_t, md::dextents<std::size_t, 2>> entities,
69 std::span<const T> data)
70{
71 assert(entities.extent(0) == data.size());
72
73 logger().info("XDMF distribute entity data");
74 mesh::CellType cell_type = topology.cell_type();
75
76 // Get layout of dofs on 0th cell entity of dimension entity_dim
77 std::vector<int> cell_vertex_dofs;
78 for (int i = 0; i < mesh::cell_num_entities(cell_type, 0); ++i)
79 {
80 const std::vector<int>& local_index = cmap_dof_layout.entity_dofs(0, i);
81 assert(local_index.size() == 1);
82 cell_vertex_dofs.push_back(local_index[0]);
83 }
84
85 // -- A. Convert from list of entities by 'nodes' to list of entities
86 // by 'vertex nodes'
87 auto to_vertex_entities
88 = [](const fem::ElementDofLayout& layout, int dim,
89 std::span<const int> vertex_dofs, mesh::CellType type, auto ents)
90 {
91 // Use ElementDofLayout of the cell to get vertex dof indices (local
92 // to a cell), i.e. build a map from local vertex index to associated
93 // local dof index
94 const std::vector<int>& entity_layout = layout.entity_closure_dofs(dim, 0);
95 std::vector<int> entity_vertex_dofs;
96 for (std::size_t i = 0; i < vertex_dofs.size(); ++i)
97 {
98 auto it = std::find(entity_layout.begin(), entity_layout.end(),
99 vertex_dofs[i]);
100 if (it != entity_layout.end())
101 {
102 entity_vertex_dofs.push_back(
103 std::ranges::distance(entity_layout.begin(), it));
104 }
105 }
106
107 const std::size_t num_vert_per_e
109
110 assert(ents.extent(1) == entity_layout.size());
111 std::vector<std::int64_t> entities_v(ents.extent(0) * num_vert_per_e);
112 for (std::size_t e = 0; e < ents.extent(0); ++e)
113 {
114 std::span entity(entities_v.data() + e * num_vert_per_e, num_vert_per_e);
115 for (std::size_t i = 0; i < num_vert_per_e; ++i)
116 entity[i] = ents(e, entity_vertex_dofs[i]);
117 std::ranges::sort(entity);
118 }
119
120 std::array shape{ents.extent(0), num_vert_per_e};
121 return std::pair(std::move(entities_v), shape);
122 };
123 const auto [entities_v_b, shapev] = to_vertex_entities(
124 cmap_dof_layout, entity_dim, cell_vertex_dofs, cell_type, entities);
125
126 md::mdspan<const std::int64_t, md::dextents<std::size_t, 2>> entities_v(
127 entities_v_b.data(), shapev);
128
129 MPI_Comm comm = topology.comm();
130 MPI_Datatype compound_type;
131 MPI_Type_contiguous(entities_v.extent(1), MPI_INT64_T, &compound_type);
132 MPI_Type_commit(&compound_type);
133
134 // -- B. Send entities and entity data to postmaster
135 auto send_entities_to_postmaster
136 = [](MPI_Comm pm_comm, MPI_Datatype pm_compound_type,
137 std::int64_t pm_num_nodes_g, auto pm_entities,
138 std::span<const T> pm_data)
139 {
140 const int size = dolfinx::MPI::size(pm_comm);
141
142 // Determine destination by index of first vertex
143 std::vector<int> dest0;
144 dest0.reserve(pm_entities.extent(0));
145 for (std::size_t e = 0; e < pm_entities.extent(0); ++e)
146 {
147 dest0.push_back(
148 dolfinx::MPI::index_owner(size, pm_entities(e, 0), pm_num_nodes_g));
149 }
150 std::vector<int> perm(dest0.size());
151 std::iota(perm.begin(), perm.end(), 0);
152 std::ranges::sort(perm, [&dest0](auto x0, auto x1)
153 { return dest0[x0] < dest0[x1]; });
154
155 // Note: dest[perm[i]] is ordered with increasing i
156 // Build list of neighbour dest ranks and count number of entities to
157 // send to each post office
158 std::vector<int> dest;
159 std::vector<std::int32_t> num_items_send;
160 {
161 auto it = perm.begin();
162 while (it != perm.end())
163 {
164 dest.push_back(dest0[*it]);
165 auto it1
166 = std::find_if(it, perm.end(), [&dest0, r = dest.back()](auto idx)
167 { return dest0[idx] != r; });
168 num_items_send.push_back(std::ranges::distance(it, it1));
169 it = it1;
170 }
171 }
172
173 // Compute send displacements
174 std::vector<int> send_disp(num_items_send.size() + 1, 0);
175 std::partial_sum(num_items_send.begin(), num_items_send.end(),
176 std::next(send_disp.begin()));
177
178 // Determine src ranks. Sort ranks so that ownership determination is
179 // deterministic for a given number of ranks.
180 std::vector<int> src = dolfinx::MPI::compute_graph_edges_nbx(pm_comm, dest);
181 std::ranges::sort(src);
182
183 // Create neighbourhood communicator for sending data to post
184 // offices
185 MPI_Comm comm0;
186 int err = MPI_Dist_graph_create_adjacent(
187 pm_comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(),
188 dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm0);
189 dolfinx::MPI::check_error(pm_comm, err);
190
191 // Send number of items to post offices (destinations)
192 std::vector<int> num_items_recv(src.size());
193 num_items_send.reserve(1);
194 num_items_recv.reserve(1);
195 MPI_Neighbor_alltoall(num_items_send.data(), 1, MPI_INT,
196 num_items_recv.data(), 1, MPI_INT, comm0);
197 dolfinx::MPI::check_error(pm_comm, err);
198
199 // Compute receive displacements
200 std::vector<int> recv_disp(num_items_recv.size() + 1, 0);
201 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
202 std::next(recv_disp.begin()));
203
204 // Prepare send buffer
205 std::vector<std::int64_t> send_buffer;
206 std::vector<T> send_values_buffer;
207 send_buffer.reserve(pm_entities.size());
208 send_values_buffer.reserve(pm_data.size());
209 for (std::size_t e = 0; e < pm_entities.extent(0); ++e)
210 {
211 auto idx = perm[e];
212 auto it
213 = std::next(pm_entities.data_handle(), idx * pm_entities.extent(1));
214 send_buffer.insert(send_buffer.end(), it, it + pm_entities.extent(1));
215 send_values_buffer.push_back(pm_data[idx]);
216 }
217
218 std::vector<std::int64_t> recv_buffer(recv_disp.back()
219 * pm_entities.extent(1));
220 err = MPI_Neighbor_alltoallv(send_buffer.data(), num_items_send.data(),
221 send_disp.data(), pm_compound_type,
222 recv_buffer.data(), num_items_recv.data(),
223 recv_disp.data(), pm_compound_type, comm0);
224 dolfinx::MPI::check_error(pm_comm, err);
225 std::vector<T> recv_values_buffer(recv_disp.back());
226 err = MPI_Neighbor_alltoallv(
227 send_values_buffer.data(), num_items_send.data(), send_disp.data(),
228 dolfinx::MPI::mpi_t<T>, recv_values_buffer.data(),
229 num_items_recv.data(), recv_disp.data(), dolfinx::MPI::mpi_t<T>, comm0);
230 dolfinx::MPI::check_error(pm_comm, err);
231 err = MPI_Comm_free(&comm0);
232 dolfinx::MPI::check_error(pm_comm, err);
233
234 std::array shape{recv_buffer.size() / (pm_entities.extent(1)),
235 (pm_entities.extent(1))};
236 return std::tuple<std::vector<std::int64_t>, std::vector<T>,
237 std::array<std::size_t, 2>>(
238 std::move(recv_buffer), std::move(recv_values_buffer), shape);
239 };
240 const auto [entitiesp_b, entitiesp_v, shapep] = send_entities_to_postmaster(
241 comm, compound_type, num_nodes_g, entities_v, data);
242 md::mdspan<const std::int64_t, md::dextents<std::size_t, 2>> entitiesp(
243 entitiesp_b.data(), shapep);
244
245 // -- C. Send mesh global indices to postmaster
246 auto indices_to_postoffice = [](MPI_Comm po_comm, std::int64_t num_nodes,
247 std::span<const std::int64_t> indices)
248 {
249 int size = dolfinx::MPI::size(po_comm);
250 std::vector<std::pair<int, std::int64_t>> dest_to_index;
251 std::ranges::transform(
252 indices, std::back_inserter(dest_to_index),
253 [size, num_nodes](auto n)
254 {
255 return std::pair(dolfinx::MPI::index_owner(size, n, num_nodes), n);
256 });
257 std::ranges::sort(dest_to_index);
258
259 // Build list of neighbour dest ranks and count number of indices to
260 // send to each post office
261 std::vector<int> dest;
262 std::vector<std::int32_t> num_items_send;
263 {
264 auto it = dest_to_index.begin();
265 while (it != dest_to_index.end())
266 {
267 dest.push_back(it->first);
268 auto it1
269 = std::find_if(it, dest_to_index.end(), [r = dest.back()](auto idx)
270 { return idx.first != r; });
271 num_items_send.push_back(std::ranges::distance(it, it1));
272 it = it1;
273 }
274 }
275
276 // Compute send displacements
277 std::vector<int> send_disp(num_items_send.size() + 1, 0);
278 std::partial_sum(num_items_send.begin(), num_items_send.end(),
279 std::next(send_disp.begin()));
280
281 // Determine src ranks. Sort ranks so that ownership determination is
282 // deterministic for a given number of ranks.
283 std::vector<int> src = dolfinx::MPI::compute_graph_edges_nbx(po_comm, dest);
284 std::ranges::sort(src);
285
286 // Create neighbourhood communicator for sending data to post offices
287 MPI_Comm comm0;
288 int err = MPI_Dist_graph_create_adjacent(
289 po_comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(),
290 dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm0);
291 dolfinx::MPI::check_error(po_comm, err);
292
293 // Send number of items to post offices (destination) that I will be
294 // sending
295 std::vector<int> num_items_recv(src.size());
296 num_items_send.reserve(1);
297 num_items_recv.reserve(1);
298 MPI_Neighbor_alltoall(num_items_send.data(), 1, MPI_INT,
299 num_items_recv.data(), 1, MPI_INT, comm0);
300 dolfinx::MPI::check_error(po_comm, err);
301
302 // Compute receive displacements
303 std::vector<int> recv_disp(num_items_recv.size() + 1, 0);
304 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
305 std::next(recv_disp.begin()));
306
307 // Prepare send buffer
308 std::vector<std::int64_t> send_buffer;
309 send_buffer.reserve(indices.size());
310 std::ranges::transform(dest_to_index, std::back_inserter(send_buffer),
311 [](auto x) { return x.second; });
312
313 std::vector<std::int64_t> recv_buffer(recv_disp.back());
314 err = MPI_Neighbor_alltoallv(send_buffer.data(), num_items_send.data(),
315 send_disp.data(), MPI_INT64_T,
316 recv_buffer.data(), num_items_recv.data(),
317 recv_disp.data(), MPI_INT64_T, comm0);
318 dolfinx::MPI::check_error(po_comm, err);
319 err = MPI_Comm_free(&comm0);
320 dolfinx::MPI::check_error(po_comm, err);
321 return std::tuple(std::move(recv_buffer), std::move(recv_disp),
322 std::move(src), std::move(dest));
323 };
324 const auto [nodes_g_p, nodes_g_p_disp, post_src, post_dest]
325 = indices_to_postoffice(comm, num_nodes_g, nodes_g);
326
327 // D. Send entities to possible owners, based on first entity index
328 auto candidate_ranks
329 = [](MPI_Comm cr_comm, MPI_Datatype cr_compound_type,
330 std::span<const std::int64_t> indices_recv,
331 std::span<const int> indices_recv_disp, std::span<const int> src,
332 std::span<const int> dest, auto entities, std::span<const T> cr_data)
333 {
334 // Build map from received global node indices to neighbourhood
335 // ranks that have the node
336 std::multimap<std::int64_t, int> node_to_rank;
337 for (std::size_t i = 0; i < indices_recv_disp.size() - 1; ++i)
338 for (int j = indices_recv_disp[i]; j < indices_recv_disp[i + 1]; ++j)
339 node_to_rank.insert({indices_recv[j], i});
340
341 std::vector<std::vector<std::int64_t>> send_data(dest.size());
342 std::vector<std::vector<T>> send_values(dest.size());
343 for (std::size_t e = 0; e < entities.extent(0); ++e)
344 {
345 std::span e_recv(entities.data_handle() + e * entities.extent(1),
346 entities.extent(1));
347 auto [it0, it1] = node_to_rank.equal_range(entities(e, 0));
348 for (auto it = it0; it != it1; ++it)
349 {
350 int p = it->second;
351 send_data[p].insert(send_data[p].end(), e_recv.begin(), e_recv.end());
352 send_values[p].push_back(cr_data[e]);
353 }
354 }
355
356 MPI_Comm comm0;
357 int err = MPI_Dist_graph_create_adjacent(
358 cr_comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(),
359 dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm0);
360 dolfinx::MPI::check_error(cr_comm, err);
361
362 std::vector<int> num_items_send;
363 num_items_send.reserve(send_data.size());
364 for (auto& x : send_data)
365 num_items_send.push_back(x.size() / entities.extent(1));
366
367 std::vector<int> num_items_recv(src.size());
368 num_items_send.reserve(1);
369 num_items_recv.reserve(1);
370 err = MPI_Neighbor_alltoall(num_items_send.data(), 1, MPI_INT,
371 num_items_recv.data(), 1, MPI_INT, comm0);
372 dolfinx::MPI::check_error(cr_comm, err);
373
374 // Compute send displacements
375 std::vector<std::int32_t> send_disp(num_items_send.size() + 1, 0);
376 std::partial_sum(num_items_send.begin(), num_items_send.end(),
377 std::next(send_disp.begin()));
378
379 // Compute receive displacements
380 std::vector<std::int32_t> recv_disp(num_items_recv.size() + 1, 0);
381 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
382 std::next(recv_disp.begin()));
383
384 // Prepare send buffers
385 std::vector<std::int64_t> send_buffer;
386 std::vector<T> send_values_buffer;
387 for (auto& x : send_data)
388 send_buffer.insert(send_buffer.end(), x.begin(), x.end());
389 for (auto& v : send_values)
390 send_values_buffer.insert(send_values_buffer.end(), v.begin(), v.end());
391 std::vector<std::int64_t> recv_buffer(entities.extent(1)
392 * recv_disp.back());
393 err = MPI_Neighbor_alltoallv(send_buffer.data(), num_items_send.data(),
394 send_disp.data(), cr_compound_type,
395 recv_buffer.data(), num_items_recv.data(),
396 recv_disp.data(), cr_compound_type, comm0);
397
398 dolfinx::MPI::check_error(cr_comm, err);
399
400 std::vector<T> recv_values_buffer(recv_disp.back());
401 err = MPI_Neighbor_alltoallv(
402 send_values_buffer.data(), num_items_send.data(), send_disp.data(),
403 dolfinx::MPI::mpi_t<T>, recv_values_buffer.data(),
404 num_items_recv.data(), recv_disp.data(), dolfinx::MPI::mpi_t<T>, comm0);
405
406 dolfinx::MPI::check_error(cr_comm, err);
407
408 err = MPI_Comm_free(&comm0);
409 dolfinx::MPI::check_error(cr_comm, err);
410
411 std::array shape{recv_buffer.size() / entities.extent(1),
412 entities.extent(1)};
413 return std::tuple<std::vector<std::int64_t>, std::vector<T>,
414 std::array<std::size_t, 2>>(
415 std::move(recv_buffer), std::move(recv_values_buffer), shape);
416 };
417 // NOTE: src and dest are transposed here because we're reversing the
418 // direction of communication
419 const auto [entities_data_b, entities_values, shape_eb]
420 = candidate_ranks(comm, compound_type, nodes_g_p, nodes_g_p_disp,
421 post_dest, post_src, entitiesp, std::span(entitiesp_v));
422 md::mdspan<const std::int64_t, md::dextents<std::size_t, 2>> entities_data(
423 entities_data_b.data(), shape_eb);
424
425 // -- E. From the received (key, value) data, determine which keys
426 // (entities) are on this process.
427 //
428 // TODO: We have already received possibly tagged entities from other
429 // ranks, so we could use the received data to avoid creating
430 // the std::map for *all* entities and just for candidate
431 // entities.
432 auto select_entities = [](const mesh::Topology& topo, auto xdofmap,
433 std::span<const std::int64_t> nodes,
434 std::span<const int> cell_vertex_dofs,
435 auto entities_data, std::span<const T> values)
436 {
437 logger().info("XDMF build map");
438 auto c_to_v = topo.connectivity(topo.dim(), 0);
439 if (!c_to_v)
440 throw std::runtime_error("Missing cell-vertex connectivity.");
441
442 // Map input node indices to local vertices.
443 boost::unordered_flat_map<std::int64_t, std::int32_t> input_idx_to_vertex;
444 input_idx_to_vertex.reserve(c_to_v->num_nodes() * cell_vertex_dofs.size());
445 for (int c = 0; c < c_to_v->num_nodes(); ++c)
446 {
447 auto vertices = c_to_v->links(c);
448 std::span xdofs(xdofmap.data_handle() + c * xdofmap.extent(1),
449 xdofmap.extent(1));
450 for (std::size_t v = 0; v < vertices.size(); ++v)
451 input_idx_to_vertex[nodes[xdofs[cell_vertex_dofs[v]]]] = vertices[v];
452 }
453
454 std::vector<std::int32_t> local_entities;
455 std::vector<T> local_data;
456 local_entities.reserve(entities_data.extent(0) * entities_data.extent(1));
457 local_data.reserve(entities_data.extent(0));
458 std::vector<std::int32_t> entity(entities_data.extent(1));
459 for (std::size_t e = 0; e < entities_data.extent(0); ++e)
460 {
461 bool entity_found = true;
462 for (std::size_t i = 0; i < entities_data.extent(1); ++i)
463 {
464 if (auto it = input_idx_to_vertex.find(entities_data(e, i));
465 it == input_idx_to_vertex.end())
466 {
467 // As soon as this received index is not in locally owned
468 // input global indices skip the entire entity
469 entity_found = false;
470 break;
471 }
472 else
473 entity[i] = it->second;
474 }
475
476 if (entity_found)
477 {
478 local_entities.insert(local_entities.end(), entity.begin(),
479 entity.end());
480 local_data.push_back(values[e]);
481 }
482 }
483
484 return std::pair(std::move(local_entities), std::move(local_data));
485 };
486
487 MPI_Type_free(&compound_type);
488
489 return select_entities(topology, xdofmap, nodes_g, cell_vertex_dofs,
490 entities_data, std::span(entities_values));
491}
492//-----------------------------------------------------------------------------}
493
494} // namespace io
495} // namespace dolfinx
Definition ElementDofLayout.h:31
const std::vector< int > & entity_closure_dofs(int dim, int entity_index) const
Definition ElementDofLayout.cpp:65
const std::vector< int > & entity_dofs(int dim, int entity_index) const
Definition ElementDofLayout.cpp:58
Topology stores the topology of a mesh, consisting of mesh entities and connectivity (incidence relat...
Definition Topology.h:49
CellType cell_type() const
Cell type.
Definition Topology.cpp:907
std::shared_ptr< const graph::AdjacencyList< std::int32_t > > connectivity(std::array< int, 2 > d0, std::array< int, 2 > d1) const
Get the connectivity from entities of topological dimension d0 to dimension d1.
Definition Topology.cpp:958
int dim() const noexcept
Topological dimension of the mesh.
Definition Topology.cpp:897
MPI_Comm comm() const
Mesh MPI communicator.
Definition Topology.cpp:1246
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:326
void check_error(MPI_Comm comm, int code) noexcept
Check MPI error code. If the error code is not equal to MPI_SUCCESS, then std::abort is called.
Definition MPI.cpp:89
std::vector< int > compute_graph_edges_nbx(MPI_Comm comm, std::span< const int > edges, int tag=static_cast< int >(tag::consensus_nbx))
Determine incoming graph edges using the NBX consensus algorithm.
Definition MPI.cpp:294
constexpr int index_owner(int size, std::size_t index, std::size_t N)
Return which rank owns index in global range [0, N - 1] (inverse of MPI::local_range).
Definition MPI.h:98
int size(MPI_Comm comm)
Definition MPI.cpp:81
Support for file IO.
Definition ADIOS2Writers.h:44
std::pair< std::vector< std::int32_t >, std::vector< T > > distribute_entity_data(const mesh::Topology &topology, std::span< const std::int64_t > nodes_g, std::int64_t num_nodes_g, const fem::ElementDofLayout &cmap_dof_layout, md::mdspan< const std::int32_t, md::dextents< std::size_t, 2 > > xdofmap, int entity_dim, md::mdspan< const std::int64_t, md::dextents< std::size_t, 2 > > entities, std::span< const T > data)
Get owned entities and associated data from input entities defined by global 'node' indices.
Definition utils.h:63
CellType
Cell type identifier.
Definition cell_types.h:24
int cell_num_entities(CellType type, int dim)
Number of entities of dimension.
Definition cell_types.cpp:97
CellType cell_entity_type(CellType type, int d, int index)
Return type of cell for entity of dimension d at given entity index.
Definition cell_types.h:113
Top-level namespace.
Definition defines.h:12
auto logger() -> spdlog::logger &
Dolfinx's logger.
Definition log.cpp:24