Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #pragma once
11 :
12 : #include "MooseRandom.h"
13 : #include "libmesh/communicator.h"
14 : #include "libmesh/parallel.h"
15 : #include "libmesh/parallel_sync.h"
16 : #include "libmesh/libmesh_common.h"
17 : #include <list>
18 : #include <memory>
19 : #include <iterator>
20 : #include <algorithm>
21 :
22 : namespace MooseUtils
23 : {
24 : ///@{
25 : /**
26 : * Swap function for serial or distributed vector of data.
27 : * @param data The vector on which the values are to be swapped
28 : * @param idx0, idx1 The global indices to be swapped
29 : * @param comm_ptr Optional Communicator, if provided and running with multiple processors the
30 : * vector is assumed to be distributed
31 : */
32 : template <typename T>
33 : void swap(std::vector<T> & data,
34 : const std::size_t idx0,
35 : const std::size_t idx1,
36 : const libMesh::Parallel::Communicator & comm);
37 : template <typename T>
38 : void swap(std::vector<T> & data,
39 : const std::size_t idx0,
40 : const std::size_t idx1,
41 : const libMesh::Parallel::Communicator * comm_ptr = nullptr);
42 : ///@}
43 :
44 : ///@{
45 : /**
46 : * Shuffle function for serial or distributed vector of data that shuffles in place.
47 : * @param data The vector on which the values are to be swapped
48 : * @param generator Random number generator to use for shuffle
49 : * @param seed_index (default: 0) The seed index to use for calls to randl
50 : * @param comm_ptr Optional Communicator, if provided and running with multiple processors the
51 : * vector is assumed to be distributed
52 : *
53 : * Both the serial and distributed version implement a Fisher-Yates shuffle
54 : * https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
55 : *
56 : * NOTE: This distributed shuffle I have here does a parallel communication with each swap pair
57 : * generated. I am certain that there are more efficient ways to shuffle a distributed vector,
58 : * but there doesn't seem to be an algorithm in the literature (my search was not extensive).
59 : *
60 : * The reason I came to this conclusion was because of a 2019 paper, which states the
61 : * following (https://iopscience.iop.org/article/10.1088/1742-6596/1196/1/012035):
62 : *
63 : * The study also says that the Fisher-Yates Shuffle can be developed in two ways,
64 : * namely the algorithm's initial assumptions that allow for discrete uniform variables,
65 : * and also with the avent of large core clusters and GPUs, there is an interest in making
66 : * parallel versions of this algorithm.
67 : *
68 : * This paper discusses the MergeShuffle (https://arxiv.org/abs/1508.03167), but that is a
69 : * shared memory parallel algorithm.
70 : *
71 : * Hence, if you want to become famous create a parallel Fisher-Yates algorithm for MPI.
72 : */
73 : template <typename T>
74 : void shuffle(std::vector<T> & data, MooseRandom & generator, const std::size_t seed_index = 0);
75 : template <typename T>
76 : void shuffle(std::vector<T> & data,
77 : MooseRandom & generator,
78 : const libMesh::Parallel::Communicator & comm);
79 : template <typename T>
80 : void shuffle(std::vector<T> & data,
81 : MooseRandom & generator,
82 : const std::size_t seed_index,
83 : const libMesh::Parallel::Communicator & comm);
84 : template <typename T>
85 : void shuffle(std::vector<T> & data,
86 : MooseRandom & generator,
87 : const std::size_t seed_index,
88 : const libMesh::Parallel::Communicator * comm_ptr);
89 : ///@}
90 :
91 : ///@{
92 : /**
93 : * Randomly resample a vector of data, allowing a value to be repeated.
94 : * @param data The vector on which the values are to be swapped
95 : * @param generator Random number generator to use for shuffle
96 : * @param seed_index (default: 0) The seed index to use for calls to randl
97 : * @param comm_ptr Optional Communicator, if provided and running with multiple processors the
98 : * vector is assumed to be distributed
99 : */
100 : template <typename T>
101 : std::vector<T>
102 : resample(const std::vector<T> & data, MooseRandom & generator, const std::size_t seed_index = 0);
103 : template <typename T>
104 : std::vector<T> resample(const std::vector<T> & data,
105 : MooseRandom & generator,
106 : const libMesh::Parallel::Communicator & comm);
107 : template <typename T>
108 : std::vector<T> resample(const std::vector<T> & data,
109 : MooseRandom & generator,
110 : const std::size_t seed_index,
111 : const libMesh::Parallel::Communicator & comm);
112 : template <typename T>
113 : std::vector<T> resample(const std::vector<T> & data,
114 : MooseRandom & generator,
115 : const std::size_t seed_index,
116 : const libMesh::Parallel::Communicator * comm_ptr);
117 : //@}
118 :
119 : ///@{
120 : /**
121 : * Randomly resample a vector of data and apply a functor, allowing a value to be repeated.
122 : * @param data The vector on which the values are to be swapped
123 : * @param functor Functor to apply to each entry of the resampled vector
124 : * @param generator Random number generator to use for shuffle
125 : * @param seed_index (default: 0) The seed index to use for calls to randl
126 : * @param comm_ptr Optional Communicator, if provided and running with multiple processors the
127 : * vector is assumed to be distributed
128 : */
129 : template <typename T, typename ActionFunctor>
130 : void resampleWithFunctor(const std::vector<T> & data,
131 : const ActionFunctor & functor,
132 : MooseRandom & generator,
133 : const std::size_t seed_index = 0);
134 : template <typename T, typename ActionFunctor>
135 : void resampleWithFunctor(const std::vector<T> & data,
136 : const ActionFunctor & functor,
137 : MooseRandom & generator,
138 : const libMesh::Parallel::Communicator & comm);
139 : template <typename T, typename ActionFunctor>
140 : void resampleWithFunctor(const std::vector<T> & data,
141 : const ActionFunctor & functor,
142 : MooseRandom & generator,
143 : const std::size_t seed_index,
144 : const libMesh::Parallel::Communicator & comm);
145 : template <typename T, typename ActionFunctor>
146 : void resampleWithFunctor(const std::vector<T> & data,
147 : const ActionFunctor & functor,
148 : MooseRandom & generator,
149 : const std::size_t seed_index,
150 : const libMesh::Parallel::Communicator * comm_ptr);
151 : //@}
152 : }
153 :
154 : template <typename T>
155 : void
156 137 : MooseUtils::swap(std::vector<T> & data,
157 : const std::size_t idx0,
158 : const std::size_t idx1,
159 : const libMesh::Parallel::Communicator * comm_ptr)
160 : {
161 137 : if (!comm_ptr || comm_ptr->size() == 1)
162 : {
163 : mooseAssert(idx0 < data.size(),
164 : "idx0 (" << idx0 << ") out of range, data.size() is " << data.size());
165 : mooseAssert(idx1 < data.size(),
166 : "idx1 (" << idx1 << ") out of range, data.size() is " << data.size());
167 113 : std::swap(data[idx0], data[idx1]);
168 : }
169 :
170 : else
171 : {
172 : // Size of the local input data
173 24 : const auto n_local = data.size();
174 24 : const auto rank = comm_ptr->rank();
175 :
176 : // Compute the global size of the vector
177 24 : std::size_t n_global = n_local;
178 24 : comm_ptr->sum(n_global);
179 : mooseAssert(idx0 < n_global,
180 : "idx0 (" << idx0 << ") out of range, the global data size is " << n_global);
181 : mooseAssert(idx1 < n_global,
182 : "idx1 (" << idx1 << ") out of range, the global data size is " << n_global);
183 :
184 : // Compute the vector data offsets, the scope cleans up the "n_local" vector
185 24 : std::vector<std::size_t> offsets(comm_ptr->size());
186 : {
187 24 : std::vector<std::size_t> local_sizes;
188 24 : comm_ptr->allgather(n_local, local_sizes);
189 48 : for (std::size_t i = 0; i < local_sizes.size() - 1; ++i)
190 24 : offsets[i + 1] = offsets[i] + local_sizes[i];
191 24 : }
192 :
193 : // Locate the rank and local index of the data to swap
194 24 : auto idx0_offset_iter = std::prev(std::upper_bound(offsets.begin(), offsets.end(), idx0));
195 24 : auto idx0_rank = std::distance(offsets.begin(), idx0_offset_iter);
196 24 : auto idx0_local_idx = idx0 - *idx0_offset_iter;
197 :
198 24 : auto idx1_offset_iter = std::prev(std::upper_bound(offsets.begin(), offsets.end(), idx1));
199 24 : auto idx1_rank = std::distance(offsets.begin(), idx1_offset_iter);
200 24 : auto idx1_local_idx = idx1 - *idx1_offset_iter;
201 :
202 : // The values, if any, needed from other rank
203 24 : std::unordered_map<processor_id_type, std::vector<std::size_t>> needs;
204 24 : if (idx0_rank != rank && idx1_rank == rank)
205 6 : needs[idx0_rank].push_back(idx0_local_idx);
206 24 : if (idx0_rank == rank && idx1_rank != rank)
207 6 : needs[idx1_rank].push_back(idx1_local_idx);
208 :
209 : // Collect the values needed by this processor
210 24 : std::unordered_map<processor_id_type, std::vector<T>> returns;
211 24 : auto return_functor =
212 12 : [&data, &returns](processor_id_type pid, const std::vector<std::size_t> & indices)
213 : {
214 12 : auto & returns_pid = returns[pid];
215 24 : for (auto idx : indices)
216 12 : returns_pid.push_back(data[idx]);
217 : };
218 24 : Parallel::push_parallel_vector_data(*comm_ptr, needs, return_functor);
219 :
220 : // Receive needed values from the others processors
221 24 : std::vector<T> incoming;
222 48 : auto recv_functor = [&incoming](processor_id_type /*pid*/, const std::vector<T> & values)
223 12 : { incoming = values; };
224 24 : Parallel::push_parallel_vector_data(*comm_ptr, returns, recv_functor);
225 :
226 24 : if (idx0_rank == rank && idx1_rank == rank)
227 6 : MooseUtils::swap(data, idx0_local_idx, idx1_local_idx);
228 :
229 18 : else if (idx0_rank == rank)
230 : {
231 : mooseAssert(incoming.size() == 1, "Only one value should be received");
232 6 : data[idx0_local_idx] = incoming[0];
233 : }
234 12 : else if (idx1_rank == rank)
235 : {
236 : mooseAssert(incoming.size() == 1, "Only one value should be received");
237 6 : data[idx1_local_idx] = incoming[0];
238 : }
239 24 : }
240 137 : }
241 :
242 : template <typename T>
243 : void
244 14 : MooseUtils::shuffle(std::vector<T> & data,
245 : MooseRandom & generator,
246 : const std::size_t seed_index,
247 : const libMesh::Parallel::Communicator * comm_ptr)
248 : {
249 : // REPLICATED data
250 14 : if (!comm_ptr || comm_ptr->size() == 1)
251 : {
252 8 : std::size_t n_global = data.size();
253 80 : for (std::size_t i = n_global - 1; i > 0; --i)
254 : {
255 72 : auto j = generator.randl(seed_index, 0, i);
256 72 : MooseUtils::swap(data, i, j, nullptr);
257 : }
258 : }
259 :
260 : // DISTRIBUTED data
261 : else
262 : {
263 : // Local/global size
264 6 : std::size_t n_local = data.size();
265 6 : std::size_t n_global = n_local;
266 6 : comm_ptr->sum(n_global);
267 :
268 : // Compute the vector data offsets, the scope cleans up the "n_local" vector
269 6 : std::vector<std::size_t> offsets(comm_ptr->size());
270 : {
271 6 : std::vector<std::size_t> local_sizes;
272 6 : comm_ptr->allgather(n_local, local_sizes);
273 12 : for (std::size_t i = 0; i < local_sizes.size() - 1; ++i)
274 6 : offsets[i + 1] = offsets[i] + local_sizes[i];
275 6 : }
276 :
277 : // Perform swaps
278 6 : auto rank = comm_ptr->rank();
279 60 : for (std::size_t idx0 = n_global - 1; idx0 > 0; --idx0)
280 : {
281 54 : auto idx1 = generator.randl(seed_index, 0, idx0);
282 :
283 : // Locate the rank and local index of the data to swap
284 54 : auto idx0_offset_iter = std::prev(std::upper_bound(offsets.begin(), offsets.end(), idx0));
285 54 : auto idx0_rank = std::distance(offsets.begin(), idx0_offset_iter);
286 54 : auto idx0_local_idx = idx0 - *idx0_offset_iter;
287 :
288 54 : auto idx1_offset_iter = std::prev(std::upper_bound(offsets.begin(), offsets.end(), idx1));
289 54 : auto idx1_rank = std::distance(offsets.begin(), idx1_offset_iter);
290 54 : auto idx1_local_idx = idx1 - *idx1_offset_iter;
291 :
292 : // The values, if any, needed from other rank
293 54 : std::unordered_map<processor_id_type, std::vector<std::size_t>> needs;
294 54 : if (idx0_rank != rank && idx1_rank == rank)
295 12 : needs[idx0_rank].push_back(idx0_local_idx);
296 54 : if (idx0_rank == rank && idx1_rank != rank)
297 12 : needs[idx1_rank].push_back(idx1_local_idx);
298 :
299 : // Collect the values needed by this processor
300 54 : std::unordered_map<processor_id_type, std::vector<T>> returns;
301 54 : auto return_functor =
302 24 : [&data, &returns](processor_id_type pid, const std::vector<std::size_t> & indices)
303 : {
304 24 : auto & returns_pid = returns[pid];
305 48 : for (auto idx : indices)
306 24 : returns_pid.push_back(data[idx]);
307 : };
308 54 : Parallel::push_parallel_vector_data(*comm_ptr, needs, return_functor);
309 :
310 : // Receive needed values from the others processors
311 54 : std::vector<T> incoming;
312 102 : auto recv_functor = [&incoming](processor_id_type /*pid*/, const std::vector<T> & values)
313 24 : { incoming = values; };
314 54 : Parallel::push_parallel_vector_data(*comm_ptr, returns, recv_functor);
315 :
316 54 : if (idx0_rank == rank && idx1_rank == rank)
317 15 : MooseUtils::swap(data, idx0_local_idx, idx1_local_idx);
318 :
319 39 : else if (idx0_rank == rank)
320 : {
321 : mooseAssert(incoming.size() == 1, "Only one value should be received");
322 12 : data[idx0_local_idx] = incoming[0];
323 : }
324 27 : else if (idx1_rank == rank)
325 : {
326 : mooseAssert(incoming.size() == 1, "Only one value should be received");
327 12 : data[idx1_local_idx] = incoming[0];
328 : }
329 : }
330 6 : }
331 14 : }
332 :
333 : template <typename T>
334 : std::vector<T>
335 14 : MooseUtils::resample(const std::vector<T> & data,
336 : MooseRandom & generator,
337 : const std::size_t seed_index,
338 : const libMesh::Parallel::Communicator * comm_ptr)
339 : {
340 : // Size of the local input data
341 14 : const std::size_t n_local = data.size();
342 :
343 : // Re-sampled data vector to be returned
344 14 : std::vector<T> replicate(n_local);
345 :
346 : // REPLICATED data
347 14 : if (!comm_ptr || comm_ptr->size() == 1)
348 : {
349 8 : replicate.resize(n_local);
350 88 : for (std::size_t j = 0; j < n_local; ++j)
351 : {
352 80 : auto index = generator.randl(seed_index, 0, n_local);
353 80 : replicate[j] = data[index];
354 : }
355 : }
356 :
357 : // DISTRIBUTED data
358 : else
359 : {
360 : // Compute the global size of the vector
361 6 : std::size_t n_global = n_local;
362 6 : comm_ptr->sum(n_global);
363 :
364 : // Compute the vector data offsets, the scope cleans up the "n_local" vector
365 6 : std::vector<std::size_t> offsets(comm_ptr->size());
366 : {
367 6 : std::vector<std::size_t> local_sizes;
368 6 : comm_ptr->allgather(n_local, local_sizes);
369 12 : for (std::size_t i = 0; i < local_sizes.size() - 1; ++i)
370 6 : offsets[i + 1] = offsets[i] + local_sizes[i];
371 6 : }
372 :
373 : // Advance the random number generator to the current offset
374 6 : const auto rank = comm_ptr->rank();
375 18 : for (std::size_t i = 0; i < offsets[rank]; ++i)
376 12 : generator.randl(seed_index, 0, n_global);
377 :
378 : // Compute the needs for this processor
379 6 : std::unordered_map<processor_id_type, std::vector<std::pair<std::size_t, std::size_t>>> needs;
380 36 : for (std::size_t i = 0; i < n_local; ++i)
381 : {
382 30 : const auto idx = generator.randl(seed_index, 0, n_global); // random global index
383 :
384 : // Locate the rank and local index of the data desired
385 30 : const auto idx_offset_iter = std::prev(std::upper_bound(offsets.begin(), offsets.end(), idx));
386 30 : const auto idx_rank = std::distance(offsets.begin(), idx_offset_iter);
387 30 : const auto idx_local_idx = idx - *idx_offset_iter;
388 :
389 : // Local available data can be inserted into the re-sample, non-local data is add the the
390 : // needs from other ranks
391 30 : if (idx_rank == rank)
392 24 : replicate[i] = data[idx_local_idx];
393 : else
394 6 : needs[idx_rank].emplace_back(idx_local_idx, i);
395 : }
396 :
397 : // Advance the random number generator to the end of the global vector
398 24 : for (std::size_t i = offsets[rank] + n_local; i < n_global; ++i)
399 18 : generator.randl(seed_index, 0, n_global);
400 :
401 : // Collect the values to be returned to the various processors
402 6 : std::unordered_map<processor_id_type, std::vector<std::pair<T, std::size_t>>> returns;
403 6 : auto return_functor =
404 6 : [&data, &returns](processor_id_type pid,
405 : const std::vector<std::pair<std::size_t, std::size_t>> & indices)
406 : {
407 6 : auto & returns_pid = returns[pid];
408 12 : for (const auto & idx : indices)
409 6 : returns_pid.emplace_back(data[idx.first], idx.second);
410 : };
411 6 : Parallel::push_parallel_vector_data(*comm_ptr, needs, return_functor);
412 :
413 : // Receive resampled values from the various processors
414 6 : auto recv_functor =
415 12 : [&replicate](processor_id_type, const std::vector<std::pair<T, std::size_t>> & values)
416 : {
417 12 : for (const auto & value : values)
418 6 : replicate[value.second] = value.first;
419 : };
420 6 : Parallel::push_parallel_vector_data(*comm_ptr, returns, recv_functor);
421 6 : }
422 28 : return replicate;
423 0 : }
424 :
425 : template <typename T, typename ActionFunctor>
426 : void
427 4 : MooseUtils::resampleWithFunctor(const std::vector<T> & data,
428 : const ActionFunctor & functor,
429 : MooseRandom & generator,
430 : const std::size_t seed_index,
431 : const libMesh::Parallel::Communicator * comm_ptr)
432 : {
433 4 : const std::size_t n_local = data.size();
434 :
435 4 : if (!comm_ptr || comm_ptr->size() == 1)
436 : {
437 44 : for (std::size_t j = 0; j < n_local; ++j)
438 : {
439 40 : auto index = generator.randl(seed_index, 0, n_local);
440 40 : functor(data[index]);
441 : }
442 : }
443 : else
444 : {
445 : // Compute the global size of the vector
446 0 : std::size_t n_global = n_local;
447 0 : comm_ptr->sum(n_global);
448 :
449 : // Compute the vector data offsets, the scope cleans up the "n_local" vector
450 0 : std::vector<std::size_t> offsets(comm_ptr->size());
451 : {
452 0 : std::vector<std::size_t> local_sizes;
453 0 : comm_ptr->allgather(n_local, local_sizes);
454 0 : for (std::size_t i = 0; i < local_sizes.size() - 1; ++i)
455 0 : offsets[i + 1] = offsets[i] + local_sizes[i];
456 0 : }
457 :
458 : // Advance the random number generator to the current offset
459 0 : const auto rank = comm_ptr->rank();
460 0 : for (std::size_t i = 0; i < offsets[rank]; ++i)
461 0 : generator.randl(seed_index, 0, n_global);
462 :
463 : // Compute the needs for this processor
464 0 : std::unordered_map<processor_id_type, std::vector<std::size_t>> indices;
465 0 : for (std::size_t i = 0; i < n_local; ++i)
466 : {
467 0 : const auto idx = generator.randl(seed_index, 0, n_global); // random global index
468 :
469 : // Locate the rank and local index of the data desired
470 0 : const auto idx_offset_iter = std::prev(std::upper_bound(offsets.begin(), offsets.end(), idx));
471 0 : const auto idx_rank = std::distance(offsets.begin(), idx_offset_iter);
472 0 : const auto idx_local_idx = idx - *idx_offset_iter;
473 :
474 : // Push back the index to appropriate rank
475 0 : indices[idx_rank].push_back(idx_local_idx);
476 : }
477 :
478 : // Advance the random number generator to the end of the global vector
479 0 : for (std::size_t i = offsets[rank] + n_local; i < n_global; ++i)
480 0 : generator.randl(seed_index, 0, n_global);
481 :
482 : // Send the indices to the appropriate rank and have the calculator do its work
483 0 : auto act_functor =
484 0 : [&functor, &data](processor_id_type /*pid*/, const std::vector<std::size_t> & indices)
485 : {
486 0 : for (const auto & idx : indices)
487 0 : functor(data[idx]);
488 : };
489 0 : Parallel::push_parallel_vector_data(*comm_ptr, indices, act_functor);
490 0 : }
491 4 : }
492 :
493 : template <typename T>
494 : void
495 40 : MooseUtils::swap(std::vector<T> & data,
496 : const std::size_t idx0,
497 : const std::size_t idx1,
498 : const libMesh::Parallel::Communicator & comm)
499 : {
500 40 : MooseUtils::swap<T>(data, idx0, idx1, &comm);
501 40 : }
502 :
503 : template <typename T>
504 : void
505 4 : MooseUtils::shuffle(std::vector<T> & data, MooseRandom & generator, const std::size_t seed_index)
506 : {
507 4 : return MooseUtils::shuffle(data, generator, seed_index, nullptr);
508 : }
509 :
510 : template <typename T>
511 : void
512 10 : MooseUtils::shuffle(std::vector<T> & data,
513 : MooseRandom & generator,
514 : const libMesh::Parallel::Communicator & comm)
515 : {
516 10 : return MooseUtils::shuffle(data, generator, 0, &comm);
517 : }
518 :
519 : template <typename T>
520 : void
521 : MooseUtils::shuffle(std::vector<T> & data,
522 : MooseRandom & generator,
523 : const std::size_t seed_index,
524 : const libMesh::Parallel::Communicator & comm)
525 : {
526 : return MooseUtils::shuffle(data, generator, seed_index, &comm);
527 : }
528 :
529 : template <typename T>
530 : std::vector<T>
531 4 : MooseUtils::resample(const std::vector<T> & data,
532 : MooseRandom & generator,
533 : const std::size_t seed_index)
534 : {
535 4 : return MooseUtils::resample(data, generator, seed_index, nullptr);
536 : }
537 :
538 : template <typename T>
539 : std::vector<T>
540 10 : MooseUtils::resample(const std::vector<T> & data,
541 : MooseRandom & generator,
542 : const libMesh::Parallel::Communicator & comm)
543 : {
544 10 : return MooseUtils::resample(data, generator, 0, &comm);
545 : }
546 :
547 : template <typename T>
548 : std::vector<T>
549 : MooseUtils::resample(const std::vector<T> & data,
550 : MooseRandom & generator,
551 : const std::size_t seed_index,
552 : const libMesh::Parallel::Communicator & comm)
553 : {
554 : return MooseUtils::resample(data, generator, seed_index, &comm);
555 : }
556 :
557 : template <typename T, typename ActionFunctor>
558 : void
559 4 : MooseUtils::resampleWithFunctor(const std::vector<T> & data,
560 : const ActionFunctor & functor,
561 : MooseRandom & generator,
562 : const std::size_t seed_index)
563 : {
564 4 : return MooseUtils::resampleWithFunctor(data, functor, generator, seed_index, nullptr);
565 : }
566 :
567 : template <typename T, typename ActionFunctor>
568 : void
569 : MooseUtils::resampleWithFunctor(const std::vector<T> & data,
570 : const ActionFunctor & functor,
571 : MooseRandom & generator,
572 : const libMesh::Parallel::Communicator & comm)
573 : {
574 : return MooseUtils::resampleWithFunctor(data, functor, generator, 0, &comm);
575 : }
576 :
577 : template <typename T, typename ActionFunctor>
578 : void
579 : MooseUtils::resampleWithFunctor(const std::vector<T> & data,
580 : const ActionFunctor & functor,
581 : MooseRandom & generator,
582 : const std::size_t seed_index,
583 : const libMesh::Parallel::Communicator & comm)
584 : {
585 : return MooseUtils::resampleWithFunctor(data, functor, generator, seed_index, &comm);
586 : }
|