170
211
const mpi::communicator& comm() const { return ownership().comm(); }
171
212
size_type first_index () const { return ownership().first_index(); }
172
213
size_type last_index () const { return ownership().last_index(); }
173
size_type par_size () const { return ownership().par_size(); }
174
void set (size_type i, const T& xi);
175
void assembly_begin ();
176
void assembly_end ();
214
size_type dis_size () const { return ownership().dis_size(); }
216
dis_reference dis_entry (size_type dis_i) { return dis_reference (*this, dis_i); }
218
template<class SetOp> void dis_entry_assembly_begin (SetOp my_set_op);
219
template<class SetOp> void dis_entry_assembly_end (SetOp my_set_op);
220
template<class SetOp> void dis_entry_assembly (SetOp my_set_op)
221
{ dis_entry_assembly_begin (my_set_op); dis_entry_assembly_end (my_set_op); }
223
void dis_entry_assembly_begin () { dis_entry_assembly_begin (set_op<T,T>()); }
224
void dis_entry_assembly_end () { dis_entry_assembly_end (set_op<T,T>()); }
225
void dis_entry_assembly () { dis_entry_assembly_begin (); dis_entry_assembly_end (); }
227
template<class Set, class Map>
228
void get_dis_entry (const Set& ext_idx_set, Map& ext_idx_map) const;
231
void set_dis_indexes (const Set& ext_idx_set) { get_dis_entry (ext_idx_set, _ext_x); }
233
const T& dis_at (size_type dis_i) const;
177
235
void repartition ( // old_numbering for *this
178
236
const array_mpi_rep<size_type>& partition, // old_ownership
179
237
array_mpi_rep<T>& new_array, // new_ownership (created)
180
238
array_mpi_rep<size_type>& old_numbering, // new_ownership
181
239
array_mpi_rep<size_type>& new_numbering) const; // old_ownership
182
template<class Set, class Map>
183
void scatter (const Set& ext_idx_set, Map& ext_idx_map) const;
185
iparstream& get (iparstream& s);
186
oparstream& put (oparstream& s) const;
187
template <class GetFunction> iparstream& get (iparstream& ips, GetFunction get_element);
188
template <class PutFunction> oparstream& put (oparstream& ops, PutFunction put_element) const;
189
template <class PutFunction> oparstream& permuted_put (oparstream& ops, const array_mpi_rep<size_type>& perm,
241
void permutation_apply ( // old_numbering for *this
242
const array_mpi_rep<size_type>& new_numbering, // old_ownership
243
array_mpi_rep<T>& new_array) const; // new_ownership (already allocated)
245
idiststream& get_values (idiststream& s);
246
odiststream& put_values (odiststream& s) const;
247
template <class GetFunction> idiststream& get_values (idiststream& ips, GetFunction get_element);
248
template <class PutFunction> odiststream& put_values (odiststream& ops, PutFunction put_element) const;
249
template <class PutFunction> odiststream& permuted_put_values (odiststream& ops, const array_mpi_rep<size_type>& perm,
190
250
PutFunction put_element) const;
191
251
void dump (std::string name) const;
194
// for communication during assembly_begin(), assembly_end()
195
typedef std::map <size_type, T, std::less<size_type>, heap_allocator<std::pair<size_type,T> > > map_type;
253
void set_dis_entry (size_type dis_i, const T& val);
254
void set_add_dis_entry (size_type dis_i, const T& val);
256
/** 1) stash: store data before assembly() communications:
257
* select multimap<U> when T=set<U> and map<T> otherwise
259
template<class U, class IsContainer> struct stash_traits {};
261
struct stash_traits<U,boost::mpl::false_> {
262
typedef U mapped_type;
263
typedef std::map <size_type, U, std::less<size_type>, heap_allocator<std::pair<size_type,U> > > map_type;
266
struct stash_traits<U,boost::mpl::true_> {
267
typedef typename U::value_type mapped_type;
268
typedef std::multimap <size_type, mapped_type, std::less<size_type>, heap_allocator<std::pair<size_type,mapped_type> > > map_type;
270
typedef typename is_container_of_mpi_datatype<T>::type is_container;
271
typedef typename stash_traits<T,is_container>::mapped_type stash_value;
272
typedef typename stash_traits<T,is_container>::map_type map_type;
274
typedef std::map <size_type, T, std::less<size_type>, heap_allocator<std::pair<size_type,T> > > scatter_map_type;
276
typedef std::map <size_type, T> scatter_map_type;
278
/** 2) message: for communication during assembly_begin(), assembly_end()
196
280
struct message_type {
197
std::list<std::pair<size_t,mpi::request> > waits;
198
std::vector<std::pair<size_type,T> > data;
281
std::list<std::pair<size_t,mpi::request> > waits;
282
std::vector<std::pair<size_type,stash_value> > data;
202
message_type _receive;
203
size_type _receive_max_size;
284
/** 3) scatter (get_entry): specialized versions for T=container and T=simple type
286
template<class Set, class Map>
287
void get_dis_entry (const Set& ext_idx_set, Map& ext_idx_map, boost::mpl::true_) const;
288
template<class Set, class Map>
289
void get_dis_entry (const Set& ext_idx_set, Map& ext_idx_map, boost::mpl::false_) const;
292
map_type _stash; // for assembly msgs:
294
message_type _receive;
295
size_type _receive_max_size;
296
scatter_map_type _ext_x; // for ext values (scatter)
205
298
#endif // _RHEOLEF_HAVE_MPI
206
299
// -------------------------------------------------------------
215
308
A sample usage of the class is:
217
int main(int argc, char**argv) {
218
environment parallel(argc, argv);
219
array<double> x(100, 3.14);
223
SEE ALSO: "parstream"(1)
310
int main(int argc, char**argv) @{
311
environment distributed(argc, argv);
312
array<double> x(distributor(100), 3.14);
316
The array<T> interface is similar to those of the std::vector<T> with the
317
addition of some communication features in the distributed case:
318
write accesses with entry/assembly and read access with dis_at.
320
DISTRIBUTED WRITE ACCESS:
321
Loop on any @code{dis_i} that is not managed by the current processor:
323
x.dis_entry (dis_i) = value;
325
and then, after loop, perform all communication:
327
x.dis_entry_assembly();
329
After this command, each value is stored in the array, available the processor
330
associated to @code{dis_i}.
331
DISTRIBUTED READ ACCESS:
332
First, define the set of indexes:
334
std::set<size_t> ext_idx_set;
336
Then, loop on @code{dis_i} indexes that are not managed by the current processor:
338
ext_idx_set.insert (dis_i);
340
After the loop, performs the communications:
342
x.set_dis_indexes (ext_idx_set);
344
After this command, each values associated to the @code{dis_i} index,
345
and that belongs to the index set, is now available also on the
346
current processor as:
348
value = x.dis_at (dis_i);
350
For convenience, if @code{dis_i} is managed by the current processor, this
351
function returns also the value.
353
The class takes two template parameters: one for the type T and the second
354
for the memory model M, that could be either M=distributed or M=sequential.
355
The two cases are associated to two diferent implementations, but proposes
356
exactly the same interface. The sequential interface propose also a supplementary
359
array<double,sequential> x(local_size, init_val);
361
This constructor is a STL-like one but could be consufused in the distributed case,
362
since there are two sizes: a local one and a global one. In that case, the use
363
of the distributor, as a generalization of the size concept, clarify the situation
364
(@pxref{distributor class}).
367
"scatter" via "get_dis_entry".
369
"gather" via "dis_entry(dis_i) = value"
370
or "dis_entry(dis_i) += value". Note that += applies when T=idx_set where
371
idx_set is a wrapper class of std::set<size_t> ; the += operator represents the
372
union of a set. The operator= is used when T=double or others simple T types
373
without algebra. If there is a conflict, i.e. several processes set the dis_i
374
index, then the result of operator+= depends upon the order of the process at
375
each run and is not deterministic. Such ambiguous behavior is not detected
378
AUTHOR: Pierre.Saramito@imag.fr
227
class basic_array : public smart_pointer<R> {
232
typedef typename R::value_type value_type;
233
typedef typename R::value_type T;
234
typedef typename R::size_type size_type;
235
typedef typename R::memory_type memory_type;
236
typedef typename R::iterator iterator;
237
typedef typename R::const_iterator const_iterator;
238
typedef typename R::communicator_type communicator_type;
239
typedef typename R::reference reference;
240
typedef typename R::const_reference const_reference;
242
// allocators/deallocators:
244
basic_array (size_type par_size = 0, const T& init_val = T());
245
void resize (size_type par_size = 0, const T& init_val = T());
246
basic_array (const distributor& ownership, const T& init_val = T());
247
void resize (const distributor& ownership, const T& init_val = T());
252
size_type size () const;
253
size_type par_size () const;
254
const distributor& ownership() const;
255
const communicator_type& comm() const;
257
// range on local memory
258
size_type first_index () const;
259
size_type last_index () const;
263
const_iterator begin() const;
265
const_iterator end() const;
267
reference operator[] (size_type i);
268
const_reference operator[] (size_type i) const;
272
void set (size_type i, const T& xi);
274
void assembly_begin ();
275
void assembly_end ();
277
// apply a partition:
279
template<class RepSize>
280
void repartition ( // old_numbering for *this
281
const RepSize& partition, // old_ownership
282
basic_array<R>& new_array, // new_ownership (created)
283
RepSize& old_numbering, // new_ownership
284
RepSize& new_numbering) const; // old_ownership
288
void dump (std::string name) const;
290
381
template <class T, class M = rheo_default_memory_model>
293
384
typedef M memory_type;
385
typedef typename std::vector<T>::size_type size_type;
386
typedef typename std::vector<T>::iterator iterator;
387
typedef typename std::vector<T>::const_iterator const_iterator;
296
390
template <class T>
297
class array<T,sequential> : public basic_array<array_seq_rep<T> > {
391
class array<T,sequential> : public smart_pointer<array_seq_rep<T> > {
299
typedef sequential memory_type;
300
typedef typename basic_array<array_seq_rep<T> >::size_type size_type;
301
typedef typename basic_array<array_seq_rep<T> >::reference reference;
302
typedef typename basic_array<array_seq_rep<T> >::const_reference const_reference;
303
array (size_type par_size = 0, const T& init_val = T());
304
array (const distributor& ownership, const T& init_val = T());
305
reference operator[] (size_type i) { return basic_array<array_seq_rep<T> >::operator[] (i); }
306
const_reference operator[] (size_type i) const { return basic_array<array_seq_rep<T> >::operator[] (i); }
307
oparstream& put (oparstream& ops) const { return basic_array<array_seq_rep<T> >::data().put(ops); }
308
iparstream& get (iparstream& ips) { return basic_array<array_seq_rep<T> >::data().get(ips); }
396
typedef array_seq_rep<T> rep;
397
typedef smart_pointer<rep> base;
399
typedef sequential memory_type;
400
typedef typename rep::size_type size_type;
401
typedef typename rep::value_type value_type;
402
typedef typename rep::reference reference;
403
typedef typename rep::dis_reference dis_reference;
404
typedef typename rep::iterator iterator;
405
typedef typename rep::const_reference const_reference;
406
typedef typename rep::const_iterator const_iterator;
411
array (size_type loc_size = 0, const T& init_val = T());
412
void resize (size_type loc_size = 0, const T& init_val = T());
413
array (const distributor& ownership, const T& init_val = T());
414
void resize (const distributor& ownership, const T& init_val = T());
416
// local accessors & modifiers:
418
size_type size () const { return base::data().size(); }
419
size_type dis_size () const { return base::data().dis_size(); }
420
const distributor& ownership() const { return base::data().ownership(); }
421
const communicator& comm() const { return ownership().comm(); }
423
reference operator[] (size_type i) { return base::data().operator[] (i); }
424
const_reference operator[] (size_type i) const { return base::data().operator[] (i); }
426
iterator begin() { return base::data().begin(); }
427
const_iterator begin() const { return base::data().begin(); }
428
iterator end() { return base::data().end(); }
429
const_iterator end() const { return base::data().end(); }
431
// global modifiers (for compatibility with distributed interface):
433
dis_reference dis_entry (size_type dis_i) { return base::data().dis_entry(dis_i); }
434
void dis_entry_assembly() {}
435
template<class SetOp>
436
void dis_entry_assembly(SetOp my_set_op) {}
437
template<class SetOp>
438
void dis_entry_assembly_begin (SetOp my_set_op) {}
439
template<class SetOp>
440
void dis_entry_assembly_end (SetOp my_set_op) {}
442
// apply a partition:
444
template<class RepSize>
445
void repartition ( // old_numbering for *this
446
const RepSize& partition, // old_ownership
447
array<T,sequential>& new_array, // new_ownership (created)
448
RepSize& old_numbering, // new_ownership
449
RepSize& new_numbering) const // old_ownership
450
{ return base::data().repartition (partition, new_array, old_numbering, new_numbering); }
452
template<class RepSize>
453
void permutation_apply ( // old_numbering for *this
454
const RepSize& new_numbering, // old_ownership
455
array<T,sequential>& new_array) const // new_ownership (already allocated)
456
{ return base::data().permutation_apply (new_numbering, new_array); }
460
odiststream& put_values (odiststream& ops) const { return base::data().put_values(ops); }
461
idiststream& get_values (idiststream& ips) { return base::data().get_values(ips); }
309
462
template <class GetFunction>
310
iparstream& get (iparstream& ips, GetFunction get_element) { return basic_array<array_seq_rep<T> >::data().get(ips, get_element); }
463
idiststream& get_values (idiststream& ips, GetFunction get_element) { return base::data().get_values(ips, get_element); }
311
464
template <class PutFunction>
312
oparstream& put (oparstream& ops, PutFunction put_element) const { return basic_array<array_seq_rep<T> >::data().put(ops, put_element); }
465
odiststream& put_values (odiststream& ops, PutFunction put_element) const { return base::data().put_values(ops, put_element); }
466
void dump (std::string name) const { return base::data().dump(name); }
471
array<T,sequential>::array (
474
: base(new_macro(rep(loc_size,init_val)))
479
array<T,sequential>::array (
480
const distributor& ownership,
482
: base(new_macro(rep(ownership,init_val)))
488
array<T,sequential>::resize (
492
base::data().resize (loc_size,init_val);
497
array<T,sequential>::resize (
498
const distributor& ownership,
501
base::data().resize (ownership,init_val);
315
503
#ifdef _RHEOLEF_HAVE_MPI
317
505
template <class T>
318
class array<T,distributed> : public basic_array<array_mpi_rep<T> > {
506
class array<T,distributed> : public smart_pointer<array_mpi_rep<T> > {
320
typedef distributed memory_type;
321
typedef typename basic_array<array_mpi_rep<T> >::size_type size_type;
322
typedef typename basic_array<array_mpi_rep<T> >::reference reference;
323
typedef typename basic_array<array_mpi_rep<T> >::const_reference const_reference;
324
array (size_type par_size = 0, const T& init_val = T());
325
array (const distributor& ownership, const T& init_val = T());
326
reference operator[] (size_type i) { return basic_array<array_mpi_rep<T> >::operator[] (i); }
327
const_reference operator[] (size_type i) const { return basic_array<array_mpi_rep<T> >::operator[] (i); }
328
oparstream& put (oparstream& ops) const { return basic_array<array_mpi_rep<T> >::data().put(ops); }
329
iparstream& get (iparstream& ips) { return basic_array<array_mpi_rep<T> >::data().get(ips); }
511
typedef array_mpi_rep<T> rep;
512
typedef smart_pointer<rep> base;
514
typedef distributed memory_type;
515
typedef typename rep::size_type size_type;
516
typedef typename rep::value_type value_type;
517
typedef typename rep::reference reference;
518
typedef typename rep::dis_reference dis_reference;
519
typedef typename rep::iterator iterator;
520
typedef typename rep::const_reference const_reference;
521
typedef typename rep::const_iterator const_iterator;
525
array (const distributor& ownership = distributor(), const T& init_val = T());
526
void resize (const distributor& ownership = distributor(), const T& init_val = T());
528
// local accessors & modifiers:
530
size_type size () const { return base::data().size(); }
531
size_type dis_size () const { return base::data().dis_size(); }
532
const distributor& ownership() const { return base::data().ownership(); }
533
const communicator& comm() const { return base::data().comm(); }
535
reference operator[] (size_type i) { return base::data().operator[] (i); }
536
const_reference operator[] (size_type i) const { return base::data().operator[] (i); }
538
iterator begin() { return base::data().begin(); }
539
const_iterator begin() const { return base::data().begin(); }
540
iterator end() { return base::data().end(); }
541
const_iterator end() const { return base::data().end(); }
330
545
template<class Set, class Map>
331
void scatter (const Set& ext_idx_set, Map& ext_idx_map) const {
332
return basic_array<array_mpi_rep<T> >::data().scatter (ext_idx_set, ext_idx_map);
546
void get_dis_entry (const Set& ext_idx_set, Map& ext_idx_map) const { base::data().get_dis_entry (ext_idx_set, ext_idx_map); }
549
void set_dis_indexes (const Set& ext_idx_set) { base::data().set_dis_indexes (ext_idx_set); }
551
const T& dis_at (size_type dis_i) const { return base::data().dis_at (dis_i); }
553
// global modifiers (for compatibility with distributed interface):
555
dis_reference dis_entry (size_type dis_i) { return base::data().dis_entry(dis_i); }
557
void dis_entry_assembly() { return base::data().dis_entry_assembly(); }
559
template<class SetOp>
560
void dis_entry_assembly (SetOp my_set_op) { return base::data().dis_entry_assembly (my_set_op); }
561
template<class SetOp>
562
void dis_entry_assembly_begin (SetOp my_set_op) { return base::data().dis_entry_assembly_begin (my_set_op); }
563
template<class SetOp>
564
void dis_entry_assembly_end (SetOp my_set_op) { return base::data().dis_entry_assembly_end (my_set_op); }
566
// apply a partition:
568
template<class RepSize>
569
void repartition ( // old_numbering for *this
570
const RepSize& partition, // old_ownership
571
array<T,distributed>& new_array, // new_ownership (created)
572
RepSize& old_numbering, // new_ownership
573
RepSize& new_numbering) const // old_ownership
574
{ return base::data().repartition (partition.data(), new_array.data(), old_numbering.data(), new_numbering.data()); }
576
template<class RepSize>
577
void permutation_apply ( // old_numbering for *this
578
const RepSize& new_numbering, // old_ownership
579
array<T,distributed>& new_array) const // new_ownership (already allocated)
580
{ base::data().permutation_apply (new_numbering.data(), new_array.data()); }
584
odiststream& put_values (odiststream& ops) const { return base::data().put_values(ops); }
585
idiststream& get_values (idiststream& ips) { return base::data().get_values(ips); }
586
void dump (std::string name) const { return base::data().dump(name); }
334
588
template <class GetFunction>
335
iparstream& get (iparstream& ips, GetFunction get_element) {
336
return basic_array<array_mpi_rep<T> >::data().get(ips, get_element); }
337
template <class PutFunction>
338
oparstream& put (oparstream& ops, PutFunction put_element) const {
339
return basic_array<array_mpi_rep<T> >::data().put(ops, put_element); }
340
template <class PutFunction>
341
oparstream& permuted_put (oparstream& ops, const array<size_type,distributed>& perm, PutFunction put_element) const {
342
return basic_array<array_mpi_rep<T> >::data().permuted_put (ops, perm.data(), put_element); }
589
idiststream& get_values (idiststream& ips, GetFunction get_element) { return base::data().get_values(ips, get_element); }
590
template <class PutFunction>
591
odiststream& put_values (odiststream& ops, PutFunction put_element) const { return base::data().put_values(ops, put_element); }
592
template <class PutFunction> odiststream& permuted_put_values (
593
odiststream& ops, const array<size_type,distributed>& perm, PutFunction put_element) const
594
{ return base::data().permuted_put_values (ops, perm.data(), put_element); }
345
#endif // _RHEOLEF_HAVE_MPI
348
template <class T, class M>
349
iparstream& operator >> (iparstream& s, array<T,M>& x);
351
template <class T, class M>
352
oparstream& operator << (oparstream& s, const array<T,M>& x);
353
// -------------------------------------------------------------
354
// inline'd : basic_array
355
// -------------------------------------------------------------
358
basic_array<R>::basic_array (
360
const value_type& init_val)
361
: smart_pointer<R> (new_macro(R(par_size,init_val)))
367
basic_array<R>::resize (
369
const value_type& init_val)
371
smart_pointer<R>::data().resize(par_size,init_val);
375
basic_array<R>::basic_array (
376
const distributor& ownership,
378
: smart_pointer<R> (new_macro(R(ownership,init_val)))
384
basic_array<R>::resize (
385
const distributor& ownership,
388
smart_pointer<R>::data().resize(ownership,init_val);
393
basic_array<R>::ownership () const
395
return smart_pointer<R>::data().ownership();
399
const typename basic_array<R>::communicator_type&
400
basic_array<R>::comm () const
402
return ownership().comm();
406
typename basic_array<R>::size_type
407
basic_array<R>::last_index () const
409
return smart_pointer<R>::data().last_index();
413
typename basic_array<R>::size_type
414
basic_array<R>::first_index () const
416
return smart_pointer<R>::data().first_index();
420
typename basic_array<R>::size_type
421
basic_array<R>::size () const
423
return smart_pointer<R>::data().size();
427
typename basic_array<R>::size_type
428
basic_array<R>::par_size () const
430
return smart_pointer<R>::data().par_size();
434
typename basic_array<R>::iterator
435
basic_array<R>::begin ()
437
return smart_pointer<R>::data().begin();
441
typename basic_array<R>::iterator
442
basic_array<R>::end ()
444
return smart_pointer<R>::data().end();
448
typename basic_array<R>::const_iterator
449
basic_array<R>::begin () const
451
return smart_pointer<R>::data().begin();
455
typename basic_array<R>::const_iterator
456
basic_array<R>::end () const
458
return smart_pointer<R>::data().end();
463
basic_array<R>::assembly_begin ()
465
smart_pointer<R>::data().assembly_begin();
470
basic_array<R>::assembly_end ()
472
smart_pointer<R>::data().assembly_end();
477
basic_array<R>::assembly()
479
smart_pointer<R>::data().assembly_begin();
480
smart_pointer<R>::data().assembly_end();
483
template<class RepSize>
486
basic_array<R>::repartition (
487
const RepSize& partition,
488
basic_array<R>& new_array,
489
RepSize& old_numbering,
490
RepSize& new_numbering) const
492
smart_pointer<R>::data().repartition(
495
old_numbering.data(),
496
new_numbering.data());
501
basic_array<R>::set (size_type i, const value_type& val)
503
smart_pointer<R>::data().set(i, val);
507
typename basic_array<R>::reference
508
basic_array<R>::operator[] (size_type i)
510
return smart_pointer<R>::data().operator[] (i);
514
typename basic_array<R>::const_reference
515
basic_array<R>::operator[] (size_type i) const
517
return smart_pointer<R>::data().operator[] (i);
522
basic_array<R>::dump (std::string name) const
524
return smart_pointer<R>::data().dump (name);
526
// ===========================================================
528
// ===========================================================
531
array<T,sequential>::array (size_type par_size, const T& init_val)
532
: basic_array<array_seq_rep<T> >(par_size,init_val)
537
array<T,sequential>::array (const distributor& ownership, const T& init_val)
538
: basic_array<array_seq_rep<T> >(ownership,init_val)
541
#ifdef _RHEOLEF_HAVE_MPI
544
array<T,distributed>::array (size_type par_size, const T& init_val)
545
: basic_array<array_mpi_rep<T> >(par_size,init_val)
550
array<T,distributed>::array (const distributor& ownership, const T& init_val)
551
: basic_array<array_mpi_rep<T> >(ownership,init_val)
554
#endif // _RHEOLEF_HAVE_MPI
559
operator >> (iparstream& ips, array<T,sequential>& x)
566
operator << (oparstream& ops, const array<T,sequential>& x)
570
#ifdef _RHEOLEF_HAVE_MPI
574
operator >> (iparstream& ips, array<T,distributed>& x)
581
operator << (oparstream& ops, const array<T,distributed>& x)
599
array<T,distributed>::array (
600
const distributor& ownership,
602
: base(new_macro(rep(ownership,init_val)))
608
array<T,distributed>::resize (
609
const distributor& ownership,
612
base::data().resize (ownership,init_val);
614
#endif // _RHEOLEF_HAVE_MPI
616
// -------------------------------------------------------------
617
// i/o with operator<< & >>
618
// -------------------------------------------------------------
622
operator >> (idiststream& ips, array<T,sequential>& x)
624
return x.get_values(ips);
629
operator << (odiststream& ops, const array<T,sequential>& x)
631
return x.put_values(ops);
633
#ifdef _RHEOLEF_HAVE_MPI
637
operator >> (idiststream& ips, array<T,distributed>& x)
639
return x.get_values(ips);
644
operator << (odiststream& ops, const array<T,distributed>& x)
646
return x.put_values(ops);
585
648
#endif // _RHEOLEF_HAVE_MPI
586
649
} // namespace rheolef