version 3.11-dev
vtkgriddatahandle.hh
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3//
4// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
12#ifndef DUMUX_VTK_GRID_DATA_HANDLE_HH
13#define DUMUX_VTK_GRID_DATA_HANDLE_HH
14
15#include <memory>
16#include <algorithm>
17#include <map>
18#include <numeric>
19#include <array>
20#include <vector>
21#include <string>
22#include <unordered_map>
23#include <utility>
24#include <iostream>
25#include <ranges>
26
27#include <dune/common/parallel/communication.hh>
28#include <dune/common/parallel/future.hh>
29#include <dune/geometry/dimension.hh>
30#include <dune/grid/common/partitionset.hh>
31#include <dune/grid/common/datahandleif.hh>
32
34
35namespace Dumux {
36
41template<class Grid, class GridInput, class Data>
43: public Dune::CommDataHandleIF<VtkGridDataHandle<Grid, GridInput, Data>, typename Data::value_type>
44{
45 using GridView = typename Grid::LevelGridView;
46
47 VtkGridDataHandle(const Grid& grid, const GridInput& gridInput, VTKReader::Data& cellData, VTKReader::Data& pointData)
48 : gridView_(grid.levelGridView(0))
49 , idSet_(grid.localIdSet())
50 , userCellData_(cellData)
51 , userPointData_(pointData)
52 {
53 const auto rank = grid.comm().rank();
54
55 // For the following to work we assume a sorted map of keys to values in the user data.
56 // This is not guaranteed by the VTKReader, so we need to sort the data first.
57 for (const auto& [key, data] : userCellData_)
58 cellData_[key] = std::move(userCellData_[key]);
59 for (const auto& [key, data] : userPointData_)
60 pointData_[key] = std::move(userPointData_[key]);
61
62#if DUMUX_HAVE_GRIDFORMAT
63 // compute the number of components for each cell and point data array
64 // and the serialization size per entity
65 std::array<std::size_t, 2> numKeys{{ cellData_.size(), pointData_.size() }};
66 std::vector<std::size_t> keyComponents(numKeys[0] + numKeys[1], 0);
67 {
68 int n = 0;
69 for (const auto& [key, data] : cellData_)
70 keyComponents[n++] = rank == 0 ? data.size()/gridInput.numElements() : 0;
71 for (const auto& [key, data] : pointData_)
72 keyComponents[n++] = rank == 0 ? data.size()/gridInput.numVertices() : 0;
73 grid.comm().broadcast(keyComponents.data(), keyComponents.size(), 0);
74 }
75
76 const auto begin = keyComponents.begin();
77 cellDataComponents_.assign(begin, begin + numKeys[0]);
78 pointDataComponents_.assign(begin + numKeys[0], keyComponents.end());
79 numCellDataPerElement_ = std::accumulate(cellDataComponents_.begin(), cellDataComponents_.end(), 0UL);
80 numPointDataPerVertex_ = std::accumulate(pointDataComponents_.begin(), pointDataComponents_.end(), 0UL);
81#else
82 // assume all data is on rank 0 (see grid manager)
83 // First broadcast how many keys we have
84 std::array<std::size_t, 2> numKeys{{ cellData_.size(), pointData_.size() }};
85 grid.comm().broadcast(numKeys.data(), 2, 0);
86
87 // then broadcast the length of the individual key strings
88 // and the number of component associated with each key (e.g. vector/tensor fields)
89 std::vector<std::size_t> keyLengthAndComponents(2*(numKeys[0] + numKeys[1]), 0);
90 {
91 int n = 0;
92
93 // key length
94 for (const auto& [key, data] : cellData_)
95 keyLengthAndComponents[n++] = key.size();
96 for (const auto& [key, data] : pointData_)
97 keyLengthAndComponents[n++] = key.size();
98
99 // number of components
100 for (const auto& [key, data] : cellData_)
101 keyLengthAndComponents[n++] = rank == 0 ? data.size()/gridInput.numElements() : 0;
102 for (const auto& [key, data] : pointData_)
103 keyLengthAndComponents[n++] = rank == 0 ? data.size()/gridInput.numVertices() : 0;
104
105 grid.comm().broadcast(keyLengthAndComponents.data(), keyLengthAndComponents.size(), 0);
106 }
107
108 // save the number of components for each cell and point data array
109 const auto begin = keyLengthAndComponents.begin() + numKeys[0] + numKeys[1];
110 cellDataComponents_.assign(begin, begin + numKeys[0]);
111 pointDataComponents_.assign(begin + numKeys[0], keyLengthAndComponents.end());
112 numCellDataPerElement_ = std::accumulate(cellDataComponents_.begin(), cellDataComponents_.end(), 0UL);
113 numPointDataPerVertex_ = std::accumulate(pointDataComponents_.begin(), pointDataComponents_.end(), 0UL);
114
115 // then broadcast the actual keys
116 std::string keys; keys.resize(std::accumulate(keyLengthAndComponents.begin(), begin, 0UL));
117 {
118 int n = 0;
119 for (const auto& [key, data] : cellData_)
120 for (const auto& c : key)
121 keys[n++] = c;
122 for (const auto& [key, data] : pointData_)
123 for (const auto& c : key)
124 keys[n++] = c;
125
126 grid.comm().broadcast(keys.data(), keys.size(), 0);
127 }
128
129 // create the entries in the cellData and pointData maps on all processes
130 std::size_t offset = 0;
131 for (int keyIdx = 0; keyIdx < numKeys[0]; ++keyIdx)
132 {
133 if (std::string key{ keys, offset, keyLengthAndComponents[keyIdx] }; cellData_.count(key) == 0)
134 cellData_[key] = Data{};
135
136 offset += keyLengthAndComponents[keyIdx];
137 }
138 for (int keyIdx = numKeys[0]; keyIdx < numKeys[0] + numKeys[1]; ++keyIdx)
139 {
140 if (std::string key{ keys, offset, keyLengthAndComponents[keyIdx] }; pointData_.count(key) == 0)
141 pointData_[key] = Data{};
142
143 offset += keyLengthAndComponents[keyIdx];
144 }
145#endif
146
147 // write data into an id map
148 for (const auto& element : elements(gridView_, Dune::Partitions::interior))
149 {
150 data_[idSet_.id(element)].resize(numCellDataPerElement_);
151
152 int n = 0, l = 0;
153 for (const auto& [key, data] : cellData_)
154 {
155 const auto nComp = cellDataComponents_[l++];
156 for (int k = 0; k < nComp; ++k)
157 std::swap(cellData_[key][k + nComp*gridInput.insertionIndex(element)], data_[idSet_.id(element)][n++]);
158 }
159
160 assert(n == numCellDataPerElement_);
161 }
162
163 for (const auto& vertex : vertices(gridView_))
164 {
165 data_[idSet_.id(vertex)].resize(numPointDataPerVertex_);
166
167 int n = 0, l = 0;
168 for (const auto& [key, data] : pointData_)
169 {
170 const auto nComp = pointDataComponents_[l++];
171 for (int k = 0; k < nComp; ++k)
172 std::swap(pointData_[key][k + nComp*gridInput.insertionIndex(vertex)], data_[idSet_.id(vertex)][n++]);
173 }
174
175 assert(n == numPointDataPerVertex_);
176 }
177 }
178
180 {
181 // resize arrays and unpack communicated data
182 const auto& indexSet = gridView_.indexSet();
183
184 {
185 int n = 0;
186 for (const auto& [key, data] : cellData_)
187 cellData_[key].resize(indexSet.size(0)*cellDataComponents_[n++]);
188 }
189 {
190 int n = 0;
191 for (const auto& [key, data] : pointData_)
192 pointData_[key].resize(indexSet.size(GridView::dimension)*pointDataComponents_[n++]);
193 }
194
195 for (const auto& element : elements(gridView_))
196 {
197 int n = 0, l = 0;
198 for (const auto& [key, data] : cellData_)
199 {
200 const auto nComp = cellDataComponents_[l++];
201 for (int k = 0; k < nComp; ++k)
202 std::swap(cellData_[key][k + nComp*indexSet.index(element)], data_[idSet_.id(element)][n++]);
203 }
204 }
205
206 for (const auto& vertex : vertices(gridView_))
207 {
208 int n = 0, l = 0;
209 for (const auto& [key, data] : pointData_)
210 {
211 const auto nComp = pointDataComponents_[l++];
212 for (int k = 0; k < nComp; ++k)
213 std::swap(pointData_[key][k + nComp*indexSet.index(vertex)], data_[idSet_.id(vertex)][n++]);
214 }
215 }
216
217 // move data back from sorted internal storage to user data
218 for (const auto& [key, data] : cellData_)
219 userCellData_[key] = std::move(cellData_[key]);
220 for (const auto& [key, data] : pointData_)
221 userPointData_[key] = std::move(pointData_[key]);
222 }
223
224 Dune::CommDataHandleIF<VtkGridDataHandle<Grid, GridInput, Data>, typename Data::value_type>& interface()
225 { return *this; }
226
227 bool contains (int dim, int codim) const
228 { return codim == 0 || codim == dim; }
229
231 bool fixedSize(int dim, int codim) const
232 { return true; }
233
234 template<class Entity>
235 std::size_t size (const Entity&) const
236 {
237 if constexpr (Entity::codimension == 0)
238 return numCellDataPerElement_;
239 else if constexpr (Entity::codimension == GridView::dimension)
240 return numPointDataPerVertex_;
241 else
242 return 0;
243 }
244
245 template<class MessageBufferImp, class Entity>
246 void gather (MessageBufferImp& buff, const Entity& e) const
247 {
248 if constexpr (Entity::codimension == 0)
249 {
250 const auto& data = data_[idSet_.id(e)];
251 for (int n = 0; n < numCellDataPerElement_; ++n)
252 buff.write(data[n]);
253 }
254
255 if constexpr (Entity::codimension == GridView::dimension)
256 {
257 const auto& data = data_[idSet_.id(e)];
258 for (int n = 0; n < numPointDataPerVertex_; ++n)
259 buff.write(data[n]);
260 }
261 }
262
263 template<class MessageBufferImp, class Entity>
264 void scatter (MessageBufferImp& buff, const Entity& e, std::size_t n)
265 {
266 auto& data = data_[idSet_.id(e)];
267 data.resize(n);
268
269 if constexpr (Entity::codimension == 0)
270 for (int k = 0; k < numCellDataPerElement_; ++k)
271 buff.read(data[k]);
272
273 if constexpr (Entity::codimension == GridView::dimension)
274 for (int k = 0; k < numPointDataPerVertex_; ++k)
275 buff.read(data[k]);
276 }
277
278private:
279 using IdSet = typename Grid::LocalIdSet;
280
281 const GridView gridView_;
282 const IdSet &idSet_;
283
284 VTKReader::Data& userCellData_;
285 VTKReader::Data& userPointData_;
286
287 std::map<std::string, VTKReader::Data::mapped_type> cellData_;
288 std::map<std::string, VTKReader::Data::mapped_type> pointData_;
289
290 std::vector<std::size_t> cellDataComponents_;
291 std::vector<std::size_t> pointDataComponents_;
292
293 std::size_t numCellDataPerElement_;
294 std::size_t numPointDataPerVertex_;
295
296 mutable std::map< typename IdSet::IdType, std::vector<typename Data::value_type> > data_;
297};
298
299} // namespace Dumux
300
302
303// for structured vtk data, we manually distribute the data to the other ranks
304template<class Grid, class GridInput>
305void communicateStructuredVtkData(const Grid& grid, const GridInput& gridInput, ::Dumux::VTKReader::Data& cellData, ::Dumux::VTKReader::Data& pointData)
306{
307#if HAVE_MPI // needed due to oversight in Dune::Communication interface
308 const auto commSize = grid.comm().size();
309 if (commSize <= 1)
310 return;
311
312 const auto rank = grid.comm().rank();
313
314#ifndef NDEBUG
315 if (rank == 0)
316 {
317 std::cout << "Communicating structured VTK data...\n\n" << std::endl;
318 std::cout << "Grid has " << gridInput.numElements() << " elements and " << gridInput.numVertices() << " vertices." << std::endl;
319 }
320#endif
321
322 // first some preliminary steps
323 // we need to sort the data
324 std::map<std::string, ::Dumux::VTKReader::Data::mapped_type> sortedCellData, sortedPointData;
325 for (const auto& [key, data] : cellData)
326 sortedCellData[key] = std::move(cellData[key]);
327 for (const auto& [key, data] : pointData)
328 sortedPointData[key] = std::move(pointData[key]);
329
330 // and we need to compute the number of components for each cell and point data array
331 // to know the message sizes
332 std::array<std::size_t, 2> numKeys{{ sortedCellData.size(), sortedPointData.size() }};
333 std::vector<std::size_t> keyComponents(numKeys[0] + numKeys[1], 0);
334 {
335 int n = 0;
336 for (const auto& [key, data] : sortedCellData)
337 keyComponents[n++] = rank == 0 ? data.size()/gridInput.numElements() : 0;
338 for (const auto& [key, data] : sortedPointData)
339 keyComponents[n++] = rank == 0 ? data.size()/gridInput.numVertices() : 0;
340 grid.comm().broadcast(keyComponents.data(), keyComponents.size(), 0);
341 }
342
343 const auto begin = keyComponents.begin();
344 const std::size_t numCellDataPerElement = std::accumulate(begin, begin + numKeys[0], 0UL);
345 const std::size_t numPointDataPerVertex = std::accumulate(begin + numKeys[0], keyComponents.end(), 0UL);
346
347#ifndef NDEBUG
348 std::cout << "Rank " << rank << ": numbers of components for each cell and point data array: "
349 << numCellDataPerElement << " " << numPointDataPerVertex << std::endl;
350#endif
351
352 // each process knows:
353 // - total number of cells and vertices
354 // - its data indices for each cell and vertex (global numbering)
355 // process 0 has all the data
356
357 // each rank decides which data they need
358 const auto& gridView = grid.levelGridView(0);
359 std::vector<std::size_t> requestedData(gridView.size(0) + gridView.size(Grid::dimension));
360 const std::size_t numElements = gridView.size(0);
361 const std::size_t numVertices = gridView.size(Grid::dimension);
362 for (const auto& element : elements(gridView))
363 {
364 requestedData[gridView.indexSet().index(element)] = gridInput.insertionIndex(element);
365 for (const auto& vertex : subEntities(element, Dune::Codim<Grid::dimension>{}))
366 requestedData[numElements + gridView.indexSet().index(vertex)] = gridInput.insertionIndex(vertex);
367 }
368
369 // gather the sizes of the data on rank 0
370 std::vector<std::size_t> numData_;
371 if (rank == 0)
372 numData_.resize(2*commSize);
373 std::array<std::size_t, 2> localNumData{{ numElements, numVertices }};
374 grid.comm().gather(localNumData.data(), numData_.data(), 2, 0);
375
376#ifndef NDEBUG
377 if (rank == 0)
378 {
379 std::cout << "Number of elements and vertices on each rank: ";
380 for (std::size_t i = 0; i < commSize; ++i)
381 std::cout << numData_[2*i] << " " << numData_[2*i + 1] << " ";
382 std::cout << std::endl;
383 }
384#endif
385
386 // send the data request to rank 0
387 using RequestData = std::vector<std::size_t>;
388 using FutureIndices = Dune::Future<RequestData>;
389 std::unique_ptr<FutureIndices> sendRequest;
390 if (rank != 0)
391 sendRequest = std::make_unique<FutureIndices>(
392 std::move(grid.comm().isend(std::move(requestedData), 0, /*tag*/0))
393 );
394
395 // receive the data on rank 0
396 std::vector<RequestData> requestedDataAll(commSize);
397 if (rank == 0)
398 {
399 std::vector<std::unique_ptr<FutureIndices>> receiveRequests(commSize-1);
400 for (std::size_t i = 0; i < commSize; ++i)
401 {
402 requestedDataAll[i].resize(numData_[2*i] + numData_[2*i + 1]);
403
404 if (i == 0)
405 requestedDataAll[i] = std::move(requestedData);
406 else
407 {
408 receiveRequests[i-1] = std::make_unique<FutureIndices>(
409 std::move(grid.comm().irecv(requestedDataAll[i], i, /*tag*/0))
410 );
411 }
412 }
413
415 std::ranges::for_each(receiveRequests, [](auto& request) { request->wait(); });
416
417#ifndef NDEBUG
418 std::cout << "Received data from all ranks." << std::endl;
419 for (std::size_t i = 0; i < commSize; ++i)
420 std::cout << "From rank " << i << ": " << requestedDataAll[i].size() << " index size." << std::endl;
421#endif
422 }
423
424 // Now we need to pack the data and send it to the other ranks
425 using PackedData = std::vector<double>;
426 PackedData receivedFlatData(localNumData[0]*numCellDataPerElement + localNumData[1]*numPointDataPerVertex);
427 if (rank == 0)
428 {
429 using FutureData = Dune::Future<PackedData>;
430 std::vector<std::unique_ptr<FutureData>> sendRequests(commSize-1);
431 for (std::size_t i = 0; i < commSize; ++i)
432 {
433 const auto& requestedIndices = requestedDataAll[i];
434 PackedData packedData(numData_[2*i]*numCellDataPerElement + numData_[2*i + 1]*numPointDataPerVertex);
435
436 // pack the data
437 std::size_t n = 0, l = 0;
438 for (const auto& [key, data] : sortedCellData)
439 {
440 const auto nComp = keyComponents[l++];
441 for (std::size_t k = 0; k < numData_[2*i]; ++k)
442 {
443 const auto idx = requestedIndices[k];
444 for (std::size_t j = 0; j < nComp; ++j)
445 packedData[n++] = data[j + nComp*idx];
446 }
447 }
448
449 const auto pointDataOffsett = numData_[2*i];
450 for (const auto& [key, data] : sortedPointData)
451 {
452 const auto nComp = keyComponents[l++];
453 for (std::size_t k = 0; k < numData_[2*i + 1]; ++k)
454 {
455 const auto idx = requestedIndices[k + pointDataOffsett];
456 for (std::size_t j = 0; j < nComp; ++j)
457 packedData[n++] = data[j + nComp*idx];
458 }
459 }
460
461 if (n != packedData.size())
462 DUNE_THROW(Dune::InvalidStateException, "Packed data size does not match expected size.");
463
464 if (i == 0)
465 receivedFlatData = std::move(packedData);
466 else
467 {
468 // send the data to rank i
469 sendRequests[i-1] = std::make_unique<FutureData>(
470 std::move(grid.comm().isend(std::move(packedData), i, /*tag*/1))
471 );
472 }
473 }
474
476 std::ranges::for_each(sendRequests, [](auto& request) { request->wait(); });
477 }
478 else
479 {
480 // receive the data from rank 0
481 auto receiveRequest = grid.comm().irecv(receivedFlatData, 0, /*tag*/1);
482
483 // finalize all communication
484 sendRequest->wait();
485 receiveRequest.wait();
486 }
487
488#ifndef NDEBUG
489 std::cout << "On rank " << rank << ", the received data size is " << receivedFlatData.size() << std::endl;
490#endif
491
492 // unpack the data on each process into cellData and pointData
493 std::size_t n = 0, l = 0;
494 for (const auto& [key, data] : sortedCellData)
495 {
496 auto& out = cellData[key];
497 const auto nComp = keyComponents[l++];
498 out.resize(localNumData[0]*nComp);
499 for (std::size_t k = 0; k < localNumData[0]; ++k)
500 for (std::size_t j = 0; j < nComp; ++j)
501 out[j + nComp*k] = receivedFlatData[n++];
502 }
503
504 for (const auto& [key, data] : sortedPointData)
505 {
506 auto& out = pointData[key];
507 const auto nComp = keyComponents[l++];
508 out.resize(localNumData[1]*nComp);
509 for (std::size_t k = 0; k < localNumData[1]; ++k)
510 for (std::size_t j = 0; j < nComp; ++j)
511 out[j + nComp*k] = receivedFlatData[n++];
512 }
513
514 if (n != receivedFlatData.size())
515 DUNE_THROW(Dune::InvalidStateException, "Unpacked data size does not match expected size.");
516
517#ifndef NDEBUG
518 if (rank == 0)
519 std::cout << "\n\n+++++++++++++++++++++++++++++++++++++++++++++" << std::endl;
520#endif
521#endif
522}
523
524} // end namespace Dumux::Detail::VtkData
525
526#endif
std::unordered_map< std::string, std::vector< double > > Data
the cell / point data type for point data read from a grid file
Definition: vtkreader.hh:350
Definition: vtkgriddatahandle.hh:301
void communicateStructuredVtkData(const Grid &grid, const GridInput &gridInput, ::Dumux::VTKReader::Data &cellData, ::Dumux::VTKReader::Data &pointData)
Definition: vtkgriddatahandle.hh:305
Definition: adapt.hh:17
A data handle for communicating grid data for VTK grids.
Definition: vtkgriddatahandle.hh:44
void gather(MessageBufferImp &buff, const Entity &e) const
Definition: vtkgriddatahandle.hh:246
typename Grid::LevelGridView GridView
Definition: vtkgriddatahandle.hh:45
VtkGridDataHandle(const Grid &grid, const GridInput &gridInput, VTKReader::Data &cellData, VTKReader::Data &pointData)
Definition: vtkgriddatahandle.hh:47
bool fixedSize(int dim, int codim) const
returns true if size per entity of given dim and codim is a constant
Definition: vtkgriddatahandle.hh:231
~VtkGridDataHandle()
Definition: vtkgriddatahandle.hh:179
bool contains(int dim, int codim) const
Definition: vtkgriddatahandle.hh:227
std::size_t size(const Entity &) const
Definition: vtkgriddatahandle.hh:235
void scatter(MessageBufferImp &buff, const Entity &e, std::size_t n)
Definition: vtkgriddatahandle.hh:264
Dune::CommDataHandleIF< VtkGridDataHandle< Grid, GridInput, Data >, typename Data::value_type > & interface()
Definition: vtkgriddatahandle.hh:224
A vtk file reader using tinyxml2 as xml backend.