]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // Copyright 2004 The Trustees of Indiana University. |
2 | ||
3 | // Use, modification and distribution is subject to the Boost Software | |
4 | // License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at | |
5 | // http://www.boost.org/LICENSE_1_0.txt) | |
6 | ||
7 | // Authors: Douglas Gregor | |
8 | // Peter Gottschling | |
9 | // Andrew Lumsdaine | |
10 | #ifndef BOOST_PARALLEL_DISTRIBUTION_HPP | |
11 | #define BOOST_PARALLEL_DISTRIBUTION_HPP | |
12 | ||
13 | #ifndef BOOST_GRAPH_USE_MPI | |
14 | #error "Parallel BGL files should not be included unless <boost/graph/use_mpi.hpp> has been included" | |
15 | #endif | |
16 | ||
17 | #include <cstddef> | |
18 | #include <vector> | |
19 | #include <algorithm> | |
20 | #include <numeric> | |
21 | #include <boost/assert.hpp> | |
22 | #include <boost/iterator/counting_iterator.hpp> | |
23 | #include <boost/random/uniform_int.hpp> | |
24 | #include <boost/shared_ptr.hpp> | |
25 | #include <typeinfo> | |
26 | ||
27 | namespace boost { namespace parallel { | |
28 | ||
29 | template<typename ProcessGroup, typename SizeType = std::size_t> | |
30 | class variant_distribution | |
31 | { | |
32 | public: | |
33 | typedef typename ProcessGroup::process_id_type process_id_type; | |
34 | typedef typename ProcessGroup::process_size_type process_size_type; | |
35 | typedef SizeType size_type; | |
36 | ||
37 | private: | |
38 | struct basic_distribution | |
39 | { | |
40 | virtual ~basic_distribution() {} | |
41 | virtual size_type block_size(process_id_type, size_type) const = 0; | |
42 | virtual process_id_type in_process(size_type) const = 0; | |
43 | virtual size_type local(size_type) const = 0; | |
44 | virtual size_type global(size_type) const = 0; | |
45 | virtual size_type global(process_id_type, size_type) const = 0; | |
46 | virtual void* address() = 0; | |
47 | virtual const void* address() const = 0; | |
48 | virtual const std::type_info& type() const = 0; | |
49 | }; | |
50 | ||
51 | template<typename Distribution> | |
52 | struct poly_distribution : public basic_distribution | |
53 | { | |
54 | explicit poly_distribution(const Distribution& distribution) | |
55 | : distribution_(distribution) { } | |
56 | ||
57 | virtual size_type block_size(process_id_type id, size_type n) const | |
58 | { return distribution_.block_size(id, n); } | |
59 | ||
60 | virtual process_id_type in_process(size_type i) const | |
61 | { return distribution_(i); } | |
62 | ||
63 | virtual size_type local(size_type i) const | |
64 | { return distribution_.local(i); } | |
65 | ||
66 | virtual size_type global(size_type n) const | |
67 | { return distribution_.global(n); } | |
68 | ||
69 | virtual size_type global(process_id_type id, size_type n) const | |
70 | { return distribution_.global(id, n); } | |
71 | ||
72 | virtual void* address() { return &distribution_; } | |
73 | virtual const void* address() const { return &distribution_; } | |
74 | virtual const std::type_info& type() const { return typeid(Distribution); } | |
75 | ||
76 | private: | |
77 | Distribution distribution_; | |
78 | }; | |
79 | ||
80 | public: | |
81 | variant_distribution() { } | |
82 | ||
83 | template<typename Distribution> | |
84 | variant_distribution(const Distribution& distribution) | |
85 | : distribution_(new poly_distribution<Distribution>(distribution)) { } | |
86 | ||
87 | size_type block_size(process_id_type id, size_type n) const | |
88 | { return distribution_->block_size(id, n); } | |
89 | ||
90 | process_id_type operator()(size_type i) const | |
91 | { return distribution_->in_process(i); } | |
92 | ||
93 | size_type local(size_type i) const | |
94 | { return distribution_->local(i); } | |
95 | ||
96 | size_type global(size_type n) const | |
97 | { return distribution_->global(n); } | |
98 | ||
99 | size_type global(process_id_type id, size_type n) const | |
100 | { return distribution_->global(id, n); } | |
101 | ||
102 | operator bool() const { return distribution_; } | |
103 | ||
104 | void clear() { distribution_.reset(); } | |
105 | ||
106 | template<typename T> | |
107 | T* as() | |
108 | { | |
109 | if (distribution_->type() == typeid(T)) | |
110 | return static_cast<T*>(distribution_->address()); | |
111 | else | |
112 | return 0; | |
113 | } | |
114 | ||
115 | template<typename T> | |
116 | const T* as() const | |
117 | { | |
118 | if (distribution_->type() == typeid(T)) | |
119 | return static_cast<T*>(distribution_->address()); | |
120 | else | |
121 | return 0; | |
122 | } | |
123 | ||
124 | private: | |
125 | shared_ptr<basic_distribution> distribution_; | |
126 | }; | |
127 | ||
128 | struct block | |
129 | { | |
130 | template<typename LinearProcessGroup> | |
131 | explicit block(const LinearProcessGroup& pg, std::size_t n) | |
132 | : id(process_id(pg)), p(num_processes(pg)), n(n) { } | |
133 | ||
134 | // If there are n elements in the distributed data structure, returns the number of elements stored locally. | |
135 | template<typename SizeType> | |
136 | SizeType block_size(SizeType n) const | |
137 | { return (n / p) + ((std::size_t)(n % p) > id? 1 : 0); } | |
138 | ||
139 | // If there are n elements in the distributed data structure, returns the number of elements stored on processor ID | |
140 | template<typename SizeType, typename ProcessID> | |
141 | SizeType block_size(ProcessID id, SizeType n) const | |
142 | { return (n / p) + ((ProcessID)(n % p) > id? 1 : 0); } | |
143 | ||
144 | // Returns the processor on which element with global index i is stored | |
145 | template<typename SizeType> | |
146 | SizeType operator()(SizeType i) const | |
147 | { | |
148 | SizeType cutoff_processor = n % p; | |
149 | SizeType cutoff = cutoff_processor * (n / p + 1); | |
150 | ||
151 | if (i < cutoff) return i / (n / p + 1); | |
152 | else return cutoff_processor + (i - cutoff) / (n / p); | |
153 | } | |
154 | ||
155 | // Find the starting index for processor with the given id | |
156 | template<typename ID> | |
157 | std::size_t start(ID id) const | |
158 | { | |
159 | std::size_t estimate = id * (n / p + 1); | |
160 | ID cutoff_processor = n % p; | |
161 | if (id < cutoff_processor) return estimate; | |
162 | else return estimate - (id - cutoff_processor); | |
163 | } | |
164 | ||
165 | // Find the local index for the ith global element | |
166 | template<typename SizeType> | |
167 | SizeType local(SizeType i) const | |
168 | { | |
169 | SizeType owner = (*this)(i); | |
170 | return i - start(owner); | |
171 | } | |
172 | ||
173 | // Returns the global index of local element i | |
174 | template<typename SizeType> | |
175 | SizeType global(SizeType i) const | |
176 | { return global(id, i); } | |
177 | ||
178 | // Returns the global index of the ith local element on processor id | |
179 | template<typename ProcessID, typename SizeType> | |
180 | SizeType global(ProcessID id, SizeType i) const | |
181 | { return i + start(id); } | |
182 | ||
183 | private: | |
184 | std::size_t id; //< The ID number of this processor | |
185 | std::size_t p; //< The number of processors | |
186 | std::size_t n; //< The size of the problem space | |
187 | }; | |
188 | ||
189 | // Block distribution with arbitrary block sizes | |
190 | struct uneven_block | |
191 | { | |
192 | typedef std::vector<std::size_t> size_vector; | |
193 | ||
194 | template<typename LinearProcessGroup> | |
195 | explicit uneven_block(const LinearProcessGroup& pg, const std::vector<std::size_t>& local_sizes) | |
196 | : id(process_id(pg)), p(num_processes(pg)), local_sizes(local_sizes) | |
197 | { | |
198 | BOOST_ASSERT(local_sizes.size() == p); | |
199 | local_starts.resize(p + 1); | |
200 | local_starts[0] = 0; | |
201 | std::partial_sum(local_sizes.begin(), local_sizes.end(), &local_starts[1]); | |
202 | n = local_starts[p]; | |
203 | } | |
204 | ||
205 | // To do maybe: enter local size in each process and gather in constructor (much handier) | |
206 | // template<typename LinearProcessGroup> | |
207 | // explicit uneven_block(const LinearProcessGroup& pg, std::size_t my_local_size) | |
208 | ||
209 | // If there are n elements in the distributed data structure, returns the number of elements stored locally. | |
210 | template<typename SizeType> | |
211 | SizeType block_size(SizeType) const | |
212 | { return local_sizes[id]; } | |
213 | ||
214 | // If there are n elements in the distributed data structure, returns the number of elements stored on processor ID | |
215 | template<typename SizeType, typename ProcessID> | |
216 | SizeType block_size(ProcessID id, SizeType) const | |
217 | { return local_sizes[id]; } | |
218 | ||
219 | // Returns the processor on which element with global index i is stored | |
220 | template<typename SizeType> | |
221 | SizeType operator()(SizeType i) const | |
222 | { | |
223 | BOOST_ASSERT (i >= (SizeType) 0 && i < (SizeType) n); // check for valid range | |
224 | size_vector::const_iterator lb = std::lower_bound(local_starts.begin(), local_starts.end(), (std::size_t) i); | |
225 | return ((SizeType)(*lb) == i ? lb : --lb) - local_starts.begin(); | |
226 | } | |
227 | ||
228 | // Find the starting index for processor with the given id | |
229 | template<typename ID> | |
230 | std::size_t start(ID id) const | |
231 | { | |
232 | return local_starts[id]; | |
233 | } | |
234 | ||
235 | // Find the local index for the ith global element | |
236 | template<typename SizeType> | |
237 | SizeType local(SizeType i) const | |
238 | { | |
239 | SizeType owner = (*this)(i); | |
240 | return i - start(owner); | |
241 | } | |
242 | ||
243 | // Returns the global index of local element i | |
244 | template<typename SizeType> | |
245 | SizeType global(SizeType i) const | |
246 | { return global(id, i); } | |
247 | ||
248 | // Returns the global index of the ith local element on processor id | |
249 | template<typename ProcessID, typename SizeType> | |
250 | SizeType global(ProcessID id, SizeType i) const | |
251 | { return i + start(id); } | |
252 | ||
253 | private: | |
254 | std::size_t id; //< The ID number of this processor | |
255 | std::size_t p; //< The number of processors | |
256 | std::size_t n; //< The size of the problem space | |
257 | std::vector<std::size_t> local_sizes; //< The sizes of all blocks | |
258 | std::vector<std::size_t> local_starts; //< Lowest global index of each block | |
259 | }; | |
260 | ||
261 | ||
262 | struct oned_block_cyclic | |
263 | { | |
264 | template<typename LinearProcessGroup> | |
265 | explicit oned_block_cyclic(const LinearProcessGroup& pg, std::size_t size) | |
266 | : id(process_id(pg)), p(num_processes(pg)), size(size) { } | |
267 | ||
268 | template<typename SizeType> | |
269 | SizeType block_size(SizeType n) const | |
270 | { | |
271 | return block_size(id, n); | |
272 | } | |
273 | ||
274 | template<typename SizeType, typename ProcessID> | |
275 | SizeType block_size(ProcessID id, SizeType n) const | |
276 | { | |
277 | SizeType all_blocks = n / size; | |
278 | SizeType extra_elements = n % size; | |
279 | SizeType everyone_gets = all_blocks / p; | |
280 | SizeType extra_blocks = all_blocks % p; | |
281 | SizeType my_blocks = everyone_gets + (p < extra_blocks? 1 : 0); | |
282 | SizeType my_elements = my_blocks * size | |
283 | + (p == extra_blocks? extra_elements : 0); | |
284 | return my_elements; | |
285 | } | |
286 | ||
287 | template<typename SizeType> | |
288 | SizeType operator()(SizeType i) const | |
289 | { | |
290 | return (i / size) % p; | |
291 | } | |
292 | ||
293 | template<typename SizeType> | |
294 | SizeType local(SizeType i) const | |
295 | { | |
296 | return ((i / size) / p) * size + i % size; | |
297 | } | |
298 | ||
299 | template<typename SizeType> | |
300 | SizeType global(SizeType i) const | |
301 | { return global(id, i); } | |
302 | ||
303 | template<typename ProcessID, typename SizeType> | |
304 | SizeType global(ProcessID id, SizeType i) const | |
305 | { | |
306 | return ((i / size) * p + id) * size + i % size; | |
307 | } | |
308 | ||
309 | private: | |
310 | std::size_t id; //< The ID number of this processor | |
311 | std::size_t p; //< The number of processors | |
312 | std::size_t size; //< Block size | |
313 | }; | |
314 | ||
315 | struct twod_block_cyclic | |
316 | { | |
317 | template<typename LinearProcessGroup> | |
318 | explicit twod_block_cyclic(const LinearProcessGroup& pg, | |
319 | std::size_t block_rows, std::size_t block_columns, | |
320 | std::size_t data_columns_per_row) | |
321 | : id(process_id(pg)), p(num_processes(pg)), | |
322 | block_rows(block_rows), block_columns(block_columns), | |
323 | data_columns_per_row(data_columns_per_row) | |
324 | { } | |
325 | ||
326 | template<typename SizeType> | |
327 | SizeType block_size(SizeType n) const | |
328 | { | |
329 | return block_size(id, n); | |
330 | } | |
331 | ||
332 | template<typename SizeType, typename ProcessID> | |
333 | SizeType block_size(ProcessID id, SizeType n) const | |
334 | { | |
335 | // TBD: This is really lame :) | |
336 | int result = -1; | |
337 | while (n > 0) { | |
338 | --n; | |
339 | if ((*this)(n) == id && (int)local(n) > result) result = local(n); | |
340 | } | |
341 | ++result; | |
342 | ||
343 | // std::cerr << "Block size of id " << id << " is " << result << std::endl; | |
344 | return result; | |
345 | } | |
346 | ||
347 | template<typename SizeType> | |
348 | SizeType operator()(SizeType i) const | |
349 | { | |
350 | SizeType result = get_block_num(i) % p; | |
351 | // std::cerr << "Item " << i << " goes on processor " << result << std::endl; | |
352 | return result; | |
353 | } | |
354 | ||
355 | template<typename SizeType> | |
356 | SizeType local(SizeType i) const | |
357 | { | |
358 | // Compute the start of the block | |
359 | std::size_t block_num = get_block_num(i); | |
360 | // std::cerr << "Item " << i << " is in block #" << block_num << std::endl; | |
361 | ||
362 | std::size_t local_block_num = block_num / p; | |
363 | std::size_t block_start = local_block_num * block_rows * block_columns; | |
364 | ||
365 | // Compute the offset into the block | |
366 | std::size_t data_row = i / data_columns_per_row; | |
367 | std::size_t data_col = i % data_columns_per_row; | |
368 | std::size_t block_offset = (data_row % block_rows) * block_columns | |
369 | + (data_col % block_columns); | |
370 | ||
371 | // std::cerr << "Item " << i << " maps to local index " << block_start+block_offset << std::endl; | |
372 | return block_start + block_offset; | |
373 | } | |
374 | ||
375 | template<typename SizeType> | |
376 | SizeType global(SizeType i) const | |
377 | { | |
378 | // Compute the (global) block in which this element resides | |
379 | SizeType local_block_num = i / (block_rows * block_columns); | |
380 | SizeType block_offset = i % (block_rows * block_columns); | |
381 | SizeType block_num = local_block_num * p + id; | |
382 | ||
383 | // Compute the position of the start of the block (globally) | |
384 | SizeType block_start = block_num * block_rows * block_columns; | |
385 | ||
386 | std::cerr << "Block " << block_num << " starts at index " << block_start | |
387 | << std::endl; | |
388 | ||
389 | // Compute the row and column of this block | |
390 | SizeType block_row = block_num / (data_columns_per_row / block_columns); | |
391 | SizeType block_col = block_num % (data_columns_per_row / block_columns); | |
392 | ||
393 | SizeType row_in_block = block_offset / block_columns; | |
394 | SizeType col_in_block = block_offset % block_columns; | |
395 | ||
396 | std::cerr << "Local index " << i << " is in block at row " << block_row | |
397 | << ", column " << block_col << ", in-block row " << row_in_block | |
398 | << ", in-block col " << col_in_block << std::endl; | |
399 | ||
400 | SizeType result = block_row * block_rows + block_col * block_columns | |
401 | + row_in_block * block_rows + col_in_block; | |
402 | ||
403 | ||
404 | std::cerr << "global(" << i << "@" << id << ") = " << result | |
405 | << " =? " << local(result) << std::endl; | |
406 | BOOST_ASSERT(i == local(result)); | |
407 | return result; | |
408 | } | |
409 | ||
410 | private: | |
411 | template<typename SizeType> | |
412 | std::size_t get_block_num(SizeType i) const | |
413 | { | |
414 | std::size_t data_row = i / data_columns_per_row; | |
415 | std::size_t data_col = i % data_columns_per_row; | |
416 | std::size_t block_row = data_row / block_rows; | |
417 | std::size_t block_col = data_col / block_columns; | |
418 | std::size_t blocks_in_row = data_columns_per_row / block_columns; | |
419 | std::size_t block_num = block_col * blocks_in_row + block_row; | |
420 | return block_num; | |
421 | } | |
422 | ||
423 | std::size_t id; //< The ID number of this processor | |
424 | std::size_t p; //< The number of processors | |
425 | std::size_t block_rows; //< The # of rows in each block | |
426 | std::size_t block_columns; //< The # of columns in each block | |
427 | std::size_t data_columns_per_row; //< The # of columns per row of data | |
428 | }; | |
429 | ||
430 | class twod_random | |
431 | { | |
432 | template<typename RandomNumberGen> | |
433 | struct random_int | |
434 | { | |
435 | explicit random_int(RandomNumberGen& gen) : gen(gen) { } | |
436 | ||
437 | template<typename T> | |
438 | T operator()(T n) const | |
439 | { | |
440 | uniform_int<T> distrib(0, n-1); | |
441 | return distrib(gen); | |
442 | } | |
443 | ||
444 | private: | |
445 | RandomNumberGen& gen; | |
446 | }; | |
447 | ||
448 | public: | |
449 | template<typename LinearProcessGroup, typename RandomNumberGen> | |
450 | explicit twod_random(const LinearProcessGroup& pg, | |
451 | std::size_t block_rows, std::size_t block_columns, | |
452 | std::size_t data_columns_per_row, | |
453 | std::size_t n, | |
454 | RandomNumberGen& gen) | |
455 | : id(process_id(pg)), p(num_processes(pg)), | |
456 | block_rows(block_rows), block_columns(block_columns), | |
457 | data_columns_per_row(data_columns_per_row), | |
458 | global_to_local(n / (block_rows * block_columns)) | |
459 | { | |
460 | std::copy(make_counting_iterator(std::size_t(0)), | |
461 | make_counting_iterator(global_to_local.size()), | |
462 | global_to_local.begin()); | |
463 | ||
464 | random_int<RandomNumberGen> rand(gen); | |
465 | std::random_shuffle(global_to_local.begin(), global_to_local.end(), rand); | |
466 | } | |
467 | ||
468 | template<typename SizeType> | |
469 | SizeType block_size(SizeType n) const | |
470 | { | |
471 | return block_size(id, n); | |
472 | } | |
473 | ||
474 | template<typename SizeType, typename ProcessID> | |
475 | SizeType block_size(ProcessID id, SizeType n) const | |
476 | { | |
477 | // TBD: This is really lame :) | |
478 | int result = -1; | |
479 | while (n > 0) { | |
480 | --n; | |
481 | if ((*this)(n) == id && (int)local(n) > result) result = local(n); | |
482 | } | |
483 | ++result; | |
484 | ||
485 | // std::cerr << "Block size of id " << id << " is " << result << std::endl; | |
486 | return result; | |
487 | } | |
488 | ||
489 | template<typename SizeType> | |
490 | SizeType operator()(SizeType i) const | |
491 | { | |
492 | SizeType result = get_block_num(i) % p; | |
493 | // std::cerr << "Item " << i << " goes on processor " << result << std::endl; | |
494 | return result; | |
495 | } | |
496 | ||
497 | template<typename SizeType> | |
498 | SizeType local(SizeType i) const | |
499 | { | |
500 | // Compute the start of the block | |
501 | std::size_t block_num = get_block_num(i); | |
502 | // std::cerr << "Item " << i << " is in block #" << block_num << std::endl; | |
503 | ||
504 | std::size_t local_block_num = block_num / p; | |
505 | std::size_t block_start = local_block_num * block_rows * block_columns; | |
506 | ||
507 | // Compute the offset into the block | |
508 | std::size_t data_row = i / data_columns_per_row; | |
509 | std::size_t data_col = i % data_columns_per_row; | |
510 | std::size_t block_offset = (data_row % block_rows) * block_columns | |
511 | + (data_col % block_columns); | |
512 | ||
513 | // std::cerr << "Item " << i << " maps to local index " << block_start+block_offset << std::endl; | |
514 | return block_start + block_offset; | |
515 | } | |
516 | ||
517 | private: | |
518 | template<typename SizeType> | |
519 | std::size_t get_block_num(SizeType i) const | |
520 | { | |
521 | std::size_t data_row = i / data_columns_per_row; | |
522 | std::size_t data_col = i % data_columns_per_row; | |
523 | std::size_t block_row = data_row / block_rows; | |
524 | std::size_t block_col = data_col / block_columns; | |
525 | std::size_t blocks_in_row = data_columns_per_row / block_columns; | |
526 | std::size_t block_num = block_col * blocks_in_row + block_row; | |
527 | return global_to_local[block_num]; | |
528 | } | |
529 | ||
530 | std::size_t id; //< The ID number of this processor | |
531 | std::size_t p; //< The number of processors | |
532 | std::size_t block_rows; //< The # of rows in each block | |
533 | std::size_t block_columns; //< The # of columns in each block | |
534 | std::size_t data_columns_per_row; //< The # of columns per row of data | |
535 | std::vector<std::size_t> global_to_local; | |
536 | }; | |
537 | ||
538 | class random_distribution | |
539 | { | |
540 | template<typename RandomNumberGen> | |
541 | struct random_int | |
542 | { | |
543 | explicit random_int(RandomNumberGen& gen) : gen(gen) { } | |
544 | ||
545 | template<typename T> | |
546 | T operator()(T n) const | |
547 | { | |
548 | uniform_int<T> distrib(0, n-1); | |
549 | return distrib(gen); | |
550 | } | |
551 | ||
552 | private: | |
553 | RandomNumberGen& gen; | |
554 | }; | |
555 | ||
556 | public: | |
557 | template<typename LinearProcessGroup, typename RandomNumberGen> | |
558 | random_distribution(const LinearProcessGroup& pg, RandomNumberGen& gen, | |
559 | std::size_t n) | |
560 | : base(pg, n), local_to_global(n), global_to_local(n) | |
561 | { | |
562 | std::copy(make_counting_iterator(std::size_t(0)), | |
563 | make_counting_iterator(n), | |
564 | local_to_global.begin()); | |
565 | ||
566 | random_int<RandomNumberGen> rand(gen); | |
567 | std::random_shuffle(local_to_global.begin(), local_to_global.end(), rand); | |
568 | ||
569 | ||
570 | for (std::vector<std::size_t>::size_type i = 0; i < n; ++i) | |
571 | global_to_local[local_to_global[i]] = i; | |
572 | } | |
573 | ||
574 | template<typename SizeType> | |
575 | SizeType block_size(SizeType n) const | |
576 | { return base.block_size(n); } | |
577 | ||
578 | template<typename SizeType, typename ProcessID> | |
579 | SizeType block_size(ProcessID id, SizeType n) const | |
580 | { return base.block_size(id, n); } | |
581 | ||
582 | template<typename SizeType> | |
583 | SizeType operator()(SizeType i) const | |
584 | { | |
585 | return base(global_to_local[i]); | |
586 | } | |
587 | ||
588 | template<typename SizeType> | |
589 | SizeType local(SizeType i) const | |
590 | { | |
591 | return base.local(global_to_local[i]); | |
592 | } | |
593 | ||
594 | template<typename ProcessID, typename SizeType> | |
595 | SizeType global(ProcessID p, SizeType i) const | |
596 | { | |
597 | return local_to_global[base.global(p, i)]; | |
598 | } | |
599 | ||
600 | template<typename SizeType> | |
601 | SizeType global(SizeType i) const | |
602 | { | |
603 | return local_to_global[base.global(i)]; | |
604 | } | |
605 | ||
606 | private: | |
607 | block base; | |
608 | std::vector<std::size_t> local_to_global; | |
609 | std::vector<std::size_t> global_to_local; | |
610 | }; | |
611 | ||
612 | } } // end namespace boost::parallel | |
613 | ||
614 | #endif // BOOST_PARALLEL_DISTRIBUTION_HPP | |
615 |