Zoltan2
Loading...
Searching...
No Matches
ddirectoryTest.cpp
Go to the documentation of this file.
1// @HEADER
2// *****************************************************************************
3// Zoltan2: A package of combinatorial algorithms for scientific computing
4//
5// Copyright 2012 NTESS and the Zoltan2 contributors.
6// SPDX-License-Identifier: BSD-3-Clause
7// *****************************************************************************
8// @HEADER
9
10// Program that demonstrates how to emulate distributed directories that
11// can sum-up entries using Tpetra classes (Map, Vector).
12// Similar functionality (but, of course, without the sum) using a Zoltan_DD
13// is also written.
14// It would be fair to compare the runtimes of the Tpetra version with the
15// Zoltan version, even without the sum operation. And if/when we implement
16// the sum operation, we could add a test of it to this program.
17
18#include <Teuchos_RCP.hpp>
19#include <Teuchos_ArrayView.hpp>
20
21#include <unordered_map>
22
23#include <Tpetra_Map.hpp>
24#include <Tpetra_Vector.hpp>
25
26#include <Zoltan2_TPLTraits.hpp>
27
28#include <zoltan_dd_cpp.h>
29#include <unordered_set>
30
31
32static const size_t TOOMANY = 100;
33
35// Functions for debugging
36template <typename T>
37void printTpetraThing(T &thing, const std::string &msg,
38 std::ostream &ostr = std::cout)
39{
40 ostr << msg << std::endl;
41 Teuchos::FancyOStream fancy(Teuchos::rcpFromRef(ostr));
42 thing.describe(fancy, Teuchos::VERB_EXTREME);
43}
44
45template <typename vector_t>
46void printVector(vector_t &vec, const std::string &msg,
47 std::ostream &ostr = std::cout)
48{
49 if (vec.getGlobalLength() > TOOMANY) return;
50 printTpetraThing<vector_t>(vec, msg, ostr);
51}
52
53template <typename map_t>
54void printMap(map_t &map, const std::string &msg,
55 std::ostream &ostr = std::cout)
56{
57 if (map.getGlobalNumElements() > TOOMANY) return;
58 printTpetraThing<map_t>(map, msg, ostr);
59}
60
62// Class to generate input IDs based on input values
63template <typename id_t>
64class IDs {
65public:
66
67 IDs(size_t nIds_, float fracShared_, id_t idBase_, int idStride_,
68 Teuchos::RCP<const Teuchos::Comm<int> > &comm_) :
69 nIds(nIds_),
70 nShared(std::ceil(nIds * fracShared_)),
71 nUnique(nIds - nShared),
72 idBase(idBase_),
73 idStride(idStride_),
74 contiguous(idStride_ == 1),
75 ids(nIds, 0), comm(comm_)
76 {
77 int me = comm->getRank();
78 int np = comm->getSize();
79
80 // Generate the uniquely owned IDs;
81 size_t cnt = 0;
82 for (size_t i = 0; i < nUnique; i++)
83 ids[cnt++] = idBase + ((me * nUnique + i) * idStride);
84
85 // Generate the shared IDs; they are the highest-numbered IDs
86 size_t gnUnique = nUnique * np;
87 std::srand(me);
88
89 for (size_t i = 0; i < nShared; i++) {
90 size_t r = rand() % nShared;
91 ids[cnt++] = idBase + ((gnUnique + r) * idStride);
92 }
93
94 print();
95 }
96
97 void print()
98 {
99 if (nIds > TOOMANY) return;
100
101 int me = comm->getRank();
102
103 std::cout << me << " nIds = " << nIds << "; nUnique = " << nUnique
104 << "; nShared = " << nShared << std::endl;
105
106 std::cout << me << " Unique: ";
107 for (size_t i = 0; i < nUnique; i++) std::cout << ids[i] << " ";
108 std::cout << std::endl;
109
110 std::cout << me << " Shared: ";
111 for (size_t i = 0; i < nShared; i++) std::cout << ids[nUnique+i] << " ";
112 std::cout << std::endl;
113 }
114
115 bool TpetraDDTest();
116
117 bool ZoltanDDTest();
118
119private:
120
121 typedef int scalar_t; // use int since we are counting occurrences
122
123
124 size_t nIds; // Number of IDs per processor
125 size_t nShared; // Number of shared IDs per processor
126 size_t nUnique; // Number of unique IDs per processor
127
128 id_t idBase; // Smallest possible ID
129 int idStride; // Offset between IDs; 1 provides contiguous
130 bool contiguous; // Flag indicating whether IDs are contiguous
131
132 std::vector<id_t> ids; // Ids generated on this proc
133
134 Teuchos::RCP<const Teuchos::Comm<int> > comm;
135};
136
137
139// Test of DDirectory-like functionality using Tpetra
140// Basic steps:
141// T1: Create an overlapped map M1 and a vector V1 that uses it;
142// V1[i] = number of local occurrencts of ID i
143// T2: Call createOneToOne to create a one-to-one map M2;
144// create a vector M2 that uses it.
145// T3: Create an Import object between the overlapped map and
146// the one-to-one map.
147// T4: Import with Tpetra::ADD from V1 to V2 to count the occurrences
148// T5: Import with Tpetra::REPLACE from V2 to V1 to return the
149// result to the original processors
150
151
152template <typename id_t>
154{
155 typedef typename Tpetra::Map<int, id_t> map_t;
156 typedef typename Teuchos::RCP<const map_t> rcpmap_t;
157
158 typedef typename Tpetra::Vector<scalar_t, int, id_t> vector_t;
159 typedef typename Teuchos::ArrayRCP<scalar_t> vectordata_t;
160
161 // Step T1
162
163 // Find unique IDs numbers and indexBase on this processor for
164 // constructing overlapping Tpetra::Map for IDs.
165
166 id_t minId = std::numeric_limits<id_t>::max();
167
168 std::unordered_map<id_t, size_t> uniqueIds;
169 uniqueIds.reserve(nIds); // Worst case
170
171 for (size_t i = 0; i < nIds; i++) {
172 id_t id = ids[i];
173 if (id < minId) minId = id;
174 if (uniqueIds.find(id) == uniqueIds.end())
175 uniqueIds[id] = 1;
176 else
177 uniqueIds[id]++;
178 }
179
180 // Compute global indexBase
181
182 id_t indexBase;
183 Teuchos::reduceAll(*comm, Teuchos::REDUCE_MIN, 1, &minId, &indexBase);
184
185 // Create list of locally unique IDs to use in Tpetra::Map creation
186
187 size_t nUniqueIds = uniqueIds.size();
188 Teuchos::Array<id_t> uniqueIdsList(nUniqueIds);
189
190 size_t cnt = 0;
191 for (auto it = uniqueIds.begin(); it != uniqueIds.end(); it++)
192 uniqueIdsList[cnt++] = it->first;
193
194 // Build Tpetra::Map for the given local ids
195
196 size_t dummy = Teuchos::OrdinalTraits<Tpetra::global_size_t>::invalid();
197
198 rcpmap_t idMap = Teuchos::rcp(new map_t(dummy, uniqueIdsList(), indexBase,
199 comm), true);
200
201 // Create Vector using this map.
202 // This vector will store number of occurrences of each id across procs
203
204 vector_t idVec(idMap, 0.);
205
206 // Use the multivector for counting number of occurrences of each id
207
208 {
209 vectordata_t idData = idVec.getDataNonConst();
210 for (auto it = uniqueIds.begin(); it != uniqueIds.end(); it++) {
211 id_t idx = idMap->getLocalElement(it->first);
212 idData[idx] = it->second;
213 }
214 }
215
216 printMap(*idMap, "idMap ");
217
218 printVector(idVec, "idVec before ");
219
220 // Step T2
221
222 // Create a one-to-one map and a vector that uses it
223
224 rcpmap_t oto_idMap;
225 if (contiguous) {
226 // For contigous ids, can use Tpetra default Map
227 id_t gnUnique = idMap->getMaxAllGlobalIndex() - indexBase + 1;
228 oto_idMap = Teuchos::rcp(new map_t(gnUnique, indexBase, comm));
229 }
230 else {
231 // Since ids may not be contiguous, cannot use default Tpetra Map
232 oto_idMap = Tpetra::createOneToOne(idMap);
233 }
234
235 vector_t oto_idVec(oto_idMap);
236
237 // Step T3
238
239 // Create an exporter between the two maps
240
241 typedef Tpetra::Export<int, id_t> export_t;
242
243 export_t idExporter(idMap, oto_idMap);
244
245 printTpetraThing(idExporter, "exporter ");
246
247 // Step T4
248
249 // Compute the number of occurrences of shared ids
250 printMap(*oto_idMap, "oto_idMap ");
251
252 printVector(oto_idVec, "oto_idVec BEFORE EXPORT");
253
254 oto_idVec.doExport(idVec, idExporter, Tpetra::ADD);
255
256 printVector(oto_idVec, "oto_idVec AFTER EXPORT");
257
258 // Step T5
259
260 // Send the result back to orig processors
261 // so that we can identify shared IDs in orig procs
262
263 idVec.doImport(oto_idVec, idExporter, Tpetra::REPLACE);
264
265 printVector(idVec, "idVec after ");
266
267 // Check the result
268 size_t cntShared = 0;
269 {
270 auto idData = idVec.getDataNonConst();
271 for (size_t i = 0; i < idVec.getLocalLength(); i++)
272 if (idData[i] > 1) cntShared++;
273 }
274
275 std::cout << comm->getRank() << " cntShared = " << cntShared
276 << "; nShared = " << nShared << std::endl;
277
278 return (cntShared == nShared);
279}
280
282// Perform nearly same operations using Zoltan DD.
283// Note that Zoltan cannot current do Tpetra::ADD; it will do Tpetra::REPLACE.
284// (That is why we are doing this study!)
285// Thus, the computed results will not be correct, but except for the addition,
286// the mechanics are all the same.
287
288template <typename id_t>
290{
291
292 if (nIds > size_t(std::numeric_limits<int>::max()))
293 throw std::runtime_error("Problem too large for Zoltan_DD");
294
295 int inIds = int(nIds);
296
297 // Build a Zoltan directory for the IDs; don't care about local IDs
298
299#ifdef HAVE_MPI
300 MPI_Comm mpicomm = Teuchos::getRawMpiComm(*comm);
301#else
302 { // Use siMPI from Zoltan here; make sure it is initialized
303 int flag;
304 MPI_Initialized(&flag);
305 if (!flag) {
306 int narg = 0;
307 char **argv = NULL;
308 MPI_Init(&narg, &argv);
309 }
310 }
311 MPI_Comm mpicomm = MPI_COMM_WORLD;
312#endif
313
315
316 Zoltan_DD zz(mpicomm, nIdEnt, 0, sizeof(scalar_t), inIds, 0);
317
318 // TODO: Can think about appropriate hash functions here.
319 // To match Tpetra's default map, we could use something like DD_Hash_Fn1.
320
321 // Allocate space for user data.
322 // In this case, the user data is occurrence counts that we would like to
323 // add up in the directory.
324 std::vector<scalar_t> user(nIds, 1.);
325
326 // Depending on size of id_t, may need to copy IDs to array of ZOLTAN_ID_TYPE
327 // TODO: Add TPL_Traits that don't require ArrayView. Doh.
328
329 ZOLTAN_ID_PTR zgids = NULL;
330 if (nIds) {
331 Teuchos::ArrayView<id_t> av(&(ids[0]), nIds);
333 }
334
335 // To do the summation, we'd need this function to be UpdateAdd or
336 // Update with an operator argument like Tpetra::ADD.
337 // For now, we'll just do the update (which does a replace-like operation).
338 zz.Update(zgids, NULL, (char*)(nIds ? &(user[0]) : NULL), NULL, inIds);
339
340 // Retrieve the result for all local IDs.
341 zz.Find(zgids, NULL, (char*)(nIds ? &(user[0]) : NULL), NULL, inIds, NULL);
342
343 // The following step is needed only to test the results;
344 // for general use, if user[i] > 1 in the summation, id[i] is shared.
345 size_t cntShared = 0;
346 std::unordered_set<id_t> alreadyCounted;
347 for (size_t i = 0; i < nIds; i++) {
348 if (user[i] > 1) {
349 // Id is shared; have we already counted it locally?
350 if (alreadyCounted.find(ids[i]) == alreadyCounted.end()) {
351 alreadyCounted.insert(ids[i]);
352 cntShared++;
353 }
354 }
355 }
356
357 if ((nIds * comm->getSize()) <= TOOMANY) zz.Print();
358
359 return (cntShared == nShared);
360}
361
363int main(int narg, char **arg)
364{
365 Tpetra::ScopeGuard tscope(&narg, &arg);
366 Teuchos::RCP<const Teuchos::Comm<int> > comm = Tpetra::getDefaultComm();
367
368 // Test with contiguous IDs of default Tpetra GO type
369 {
370 size_t nIds = 5; // Number of IDs per processor
371 float fracShared = 0.2; // Fraction of IDs that should be shared
372 size_t idBase = 0; // Smallest possible ID
373 int idStride = 1; // Offset between IDs; 1 gives contiguous numbering
374
375 typedef Tpetra::Map<>::global_ordinal_type gno_t;
376 IDs<gno_t> myIds(nIds, fracShared, idBase, idStride, comm);
377
378 myIds.TpetraDDTest();
379
380 myIds.ZoltanDDTest();
381 }
382
383 // Test with non-contiguous IDs of default Tpetra GO starting at 20
384 {
385 size_t nIds = 5; // Number of IDs per processor
386 float fracShared = 0.2; // Fraction of IDs that should be shared
387 size_t idBase = 20; // Smallest possible ID
388 int idStride = 3; // Offset between IDs; 1 gives contiguous numbering
389
390 typedef Tpetra::Map<>::global_ordinal_type gno_t;
391 IDs<gno_t> myIds(nIds, fracShared, idBase, idStride, comm);
392
393 myIds.TpetraDDTest();
394
395 myIds.ZoltanDDTest();
396 }
397
398#ifdef HAVE_TPETRA_INT_INT
399 // Test with contiguous integer IDs
400 {
401 size_t nIds = 5; // Number of IDs per processor
402 float fracShared = 0.2; // Fraction of IDs that should be shared
403 size_t idBase = 0; // Smallest possible ID
404 int idStride = 1; // Offset between IDs; 1 gives contiguous numbering
405
406 IDs<int> myIds(nIds, fracShared, idBase, idStride, comm);
407
408 myIds.TpetraDDTest();
409
410 myIds.ZoltanDDTest();
411 }
412
413 // Test with non-contiguous integer IDs starting at 20
414 {
415 size_t nIds = 5; // Number of IDs per processor
416 float fracShared = 0.2; // Fraction of IDs that should be shared
417 size_t idBase = 20; // Smallest possible ID
418 int idStride = 3; // Offset between IDs; 1 gives contiguous numbering
419
420 IDs<int> myIds(nIds, fracShared, idBase, idStride, comm);
421
422 myIds.TpetraDDTest();
423
424 myIds.ZoltanDDTest();
425 }
426#endif
427
428#ifdef HAVE_TPETRA_INT_LONG_LONG
429 // Test with non-contiguous long long IDs starting at 200
430 {
431 size_t nIds = 5; // Number of IDs per processor
432 float fracShared = 0.4; // Fraction of IDs that should be shared
433 size_t idBase = 200; // Smallest possible ID
434 int idStride = 4; // Offset between IDs; 1 gives contiguous numbering
435
436 IDs<long long> myIds(nIds, fracShared, idBase, idStride, comm);
437
438 myIds.TpetraDDTest();
439
440 myIds.ZoltanDDTest();
441 }
442#endif
443}
Traits class to handle conversions between gno_t/lno_t and TPL data types (e.g., ParMETIS's idx_t,...
int main()
IDs(size_t nIds_, float fracShared_, id_t idBase_, int idStride_, Teuchos::RCP< const Teuchos::Comm< int > > &comm_)
bool TpetraDDTest()
void print()
bool ZoltanDDTest()
void printTpetraThing(T &thing, const std::string &msg, std::ostream &ostr=std::cout)
void printMap(map_t &map, const std::string &msg, std::ostream &ostr=std::cout)
void printVector(vector_t &vec, const std::string &msg, std::ostream &ostr=std::cout)
static const size_t TOOMANY
map_t::global_ordinal_type gno_t
Tpetra::Map map_t
static void ASSIGN_ARRAY(first_t **a, ArrayView< second_t > &b)