From f609c91d539c2d8713e2475c2d29e89d5b4bd9d1 Mon Sep 17 00:00:00 2001 From: Martin Kronbichler Date: Fri, 18 Oct 2013 12:19:18 +0000 Subject: [PATCH] Improve handling of ghost elements in parallel vector: Distinguish a state with read access to ghost elements and one with write access (assembly mode). git-svn-id: https://svn.dealii.org/trunk@31301 0785d39b-7218-0410-832d-ea1e28bc413d --- deal.II/doc/news/changes.h | 25 ++ .../deal.II/lac/parallel_block_vector.h | 50 ++- deal.II/include/deal.II/lac/parallel_vector.h | 353 ++++++++++++------ .../deal.II/lac/parallel_vector.templates.h | 168 +++++---- .../include/deal.II/matrix_free/matrix_free.h | 129 +++++-- tests/mpi/matrix_free_02.cc | 2 - tests/mpi/parallel_block_vector_01.cc | 3 +- tests/mpi/parallel_block_vector_02.cc | 162 ++++++++ .../ncpu_1/cmp/generic | 3 + .../ncpu_10/cmp/generic | 3 + .../ncpu_4/cmp/generic | 3 + 11 files changed, 672 insertions(+), 229 deletions(-) create mode 100644 tests/mpi/parallel_block_vector_02.cc create mode 100644 tests/mpi/parallel_block_vector_02/ncpu_1/cmp/generic create mode 100644 tests/mpi/parallel_block_vector_02/ncpu_10/cmp/generic create mode 100644 tests/mpi/parallel_block_vector_02/ncpu_4/cmp/generic diff --git a/deal.II/doc/news/changes.h b/deal.II/doc/news/changes.h index 54d0a2412b..c399798fed 100644 --- a/deal.II/doc/news/changes.h +++ b/deal.II/doc/news/changes.h @@ -24,6 +24,23 @@ inconvenience this causes.

    +
  1. + Changed: The ghost handling of the parallel::distributed::Vector class has + been reworked: The vector now carries a global state that stores whether + ghost elements have been updated or not. If a vector has ghost elements, it + does not allow calls to compress() any more. Instead, a compress operation + can now only be done when the ghost entries have been cleared before by + calling zero_out_ghosts() or operator=0. The state can be queried by the new + method has_ghost_elements(). This change avoids spurious entries to be + inserted with compress(), but requires some change in user codes. The + behavior of a ghosted vector is now very similar to ghosted PETSc and + Trilinos vectors. The only difference is that the same vector can + also be used as a non-ghosted vector which is designed for use in assembly + routines. +
    + (Martin Kronbichler, 2013/10/18) +
  2. +
  3. Removed: GridTools::collect_periodic_face_pairs. This function is superseded by GridTools::collect_periodic_faces which exports an @@ -145,6 +162,14 @@ inconvenience this causes.

    Specific improvements

      +
    1. + New: parallel::distributed::BlockVector has now methods update_ghost_values, + compress, set_out_ghosts, and has_ghost_elements that do the respective + operation on each block of parallel::distributed::Vector. +
      + (Martin Kronbichler, 2013/10/18) +
    2. +
    3. Fixed: When deriving from DataOut to filter the cells where output is generated, there were two different bugs that result in segmentation faults or wrong cells written (example, step-18).
      diff --git a/deal.II/include/deal.II/lac/parallel_block_vector.h b/deal.II/include/deal.II/lac/parallel_block_vector.h index 3be362a635..7e3f1bb190 100644 --- a/deal.II/include/deal.II/lac/parallel_block_vector.h +++ b/deal.II/include/deal.II/lac/parallel_block_vector.h @@ -261,6 +261,21 @@ namespace parallel */ void update_ghost_values () const; + /** + * This method zeros the entries on ghost dofs, but does not touch + * locally owned DoFs. + * + * After calling this method, read access to ghost elements of the + * vector is forbidden and an exception is thrown. Only write access to + * ghost elements is allowed in this state. + */ + void zero_out_ghosts (); + + /** + * Returns if this Vector contains ghost elements. + */ + bool has_ghost_elements() const; + /** * Return whether the vector contains only elements with value * zero. This function is mainly for internal consistency checks and @@ -584,8 +599,12 @@ namespace parallel void BlockVector::compress (::dealii::VectorOperation::values operation) { + // start all requests for all blocks before finishing the transfers as + // this saves repeated synchronizations for (unsigned int block=0; blockn_blocks(); ++block) - this->block(block).compress(operation); + this->block(block).compress_start(block*10 + 8273, operation); + for (unsigned int block=0; blockn_blocks(); ++block) + this->block(block).compress_finish(operation); } @@ -596,7 +615,34 @@ namespace parallel BlockVector::update_ghost_values () const { for (unsigned int block=0; blockn_blocks(); ++block) - this->block(block).update_ghost_values(); + this->block(block).update_ghost_values_start(block*10 + 9923); + for (unsigned int block=0; blockn_blocks(); ++block) + this->block(block).update_ghost_values_finish(); + } + + + + template + inline + void + BlockVector::zero_out_ghosts () + { + for (unsigned int block=0; blockn_blocks(); ++block) + this->block(block).zero_out_ghosts(); + } + + + + template + inline + bool + BlockVector::has_ghost_elements () const + { + bool has_ghost_elements = false; + for (unsigned int block=0; blockn_blocks(); ++block) + if (this->block(block).has_ghost_elements() == true) + has_ghost_elements = true; + return has_ghost_elements; } diff --git a/deal.II/include/deal.II/lac/parallel_vector.h b/deal.II/include/deal.II/lac/parallel_vector.h index 7e1d853e52..c568a4aba7 100644 --- a/deal.II/include/deal.II/lac/parallel_vector.h +++ b/deal.II/include/deal.II/lac/parallel_vector.h @@ -83,6 +83,22 @@ namespace parallel * - Of course, reduction operations (like norms) make use of collective * all-to-all MPI communications. * + * This vector can take two different states with respect to ghost + * elements: + * - After creation and whenever zero_out_ghosts() is called (or + * operator = (0.)), the vector does only allow writing + * into ghost elements but not reading from ghost elements. + * - After a call to update_ghost_values(), the vector does not allow + * writing into ghost elements but only reading from them. This is in + * order to avoid undesired ghost data artifacts when calling compress() + * after modifying some vector entries. + * The current statues of the ghost entries (read mode or write mode) can + * be queried by the method has_ghost_elements(), which returns + * true exactly when ghost elements have been updated and + * false otherwise, irrespective of the actual number of + * ghost entries in the vector layout (for that information, use + * n_ghost_entries() instead). + * * @author Katharina Kormann, Martin Kronbichler, 2010, 2011 */ template @@ -307,6 +323,16 @@ namespace parallel * ghost data is changed. This is needed to allow functions with a @p * const vector to perform the data exchange without creating * temporaries. + * + * After calling this method, write access to ghost elements of the + * vector is forbidden and an exception is thrown. Only read access to + * ghost elements is allowed in this state. Note that all subsequent + * operations on this vector, like global vector addition, etc., will + * also update the ghost values by a call to this method after the + * operation. However, global reduction operations like norms or the + * inner product will always ignore ghost elements in order to avoid + * counting the ghost data more than once. To allow writing to ghost + * elements again, call zero_out_ghosts(). */ void update_ghost_values () const; @@ -332,7 +358,14 @@ namespace parallel * the communication to finish. Once it is finished, add or set the data * (depending on the flag operation) to the respective positions in the * owning processor, and clear the contents in the ghost data - * fields. The meaning of this argument is the same as in compress(). + * fields. The meaning of this argument is the same as in + * compress(). + * + * This function should be called exactly once per vector after calling + * compress_start, otherwise the result is undefined. In particular, it + * is not well-defined to call compress_start on the same vector again + * before compress_finished has been called. However, there is no + * warning to prevent this situation. * * Must follow a call to the @p compress_start function. */ @@ -372,9 +405,23 @@ namespace parallel /** * This method zeros the entries on ghost dofs, but does not touch * locally owned DoFs. + * + * After calling this method, read access to ghost elements of the + * vector is forbidden and an exception is thrown. Only write access to + * ghost elements is allowed in this state. */ void zero_out_ghosts (); + /** + * Returns whether the vector currently is in a state where ghost values + * can be read or not. This is the same functionality as other parallel + * vectors have. If this method returns false, this only means that + * read-access to ghost elements is prohibited whereas write access is + * still possible (to those entries specified as ghosts during + * initialization), not that there are no ghost elements at all. + */ + bool has_ghost_elements() const; + /** * Return whether the vector contains only elements with value * zero. This function is mainly for internal consistency checks and @@ -909,6 +956,15 @@ namespace parallel */ mutable Number *import_data; + /** + * Stores whether the vector currently allows for reading ghost elements + * or not. Note that this is to ensure consistent ghost data and does + * not indicate whether the vector actually can store ghost elements. In + * particular, when assembling a vector we do not allow reading + * elements, only writing them. + */ + mutable bool vector_is_ghosted; + /** * Provide this class with all functionality of ::dealii::Vector by * creating a VectorView object. @@ -977,6 +1033,7 @@ namespace parallel allocated_size (0), val (0), import_data (0), + vector_is_ghosted (false), vector_view (0, static_cast(0)) {} @@ -990,6 +1047,7 @@ namespace parallel allocated_size (0), val (0), import_data (0), + vector_is_ghosted (false), vector_view (0, static_cast(0)) { reinit (v, true); @@ -1007,6 +1065,7 @@ namespace parallel allocated_size (0), val (0), import_data (0), + vector_is_ghosted (false), vector_view (0, static_cast(0)) { reinit (local_range, ghost_indices, communicator); @@ -1017,11 +1076,12 @@ namespace parallel template inline Vector::Vector (const IndexSet &local_range, - const MPI_Comm communicator) + const MPI_Comm communicator) : allocated_size (0), val (0), import_data (0), + vector_is_ghosted (false), vector_view (0, static_cast(0)) { IndexSet ghost_indices(local_range.size()); @@ -1037,6 +1097,7 @@ namespace parallel allocated_size (0), val (0), import_data (0), + vector_is_ghosted (false), vector_view (0, static_cast(0)) { reinit (size, false); @@ -1052,6 +1113,7 @@ namespace parallel allocated_size (0), val (0), import_data (0), + vector_is_ghosted (false), vector_view (0, static_cast(0)) { reinit (partitioner); @@ -1083,11 +1145,14 @@ namespace parallel { Assert (c.partitioner.get() != 0, ExcNotInitialized()); - // check whether the two vectors use the same - // parallel partitioner. if not, check if all - // local ranges are the same (that way, we can - // exchange data between different parallel - // layouts) + // we update ghost values whenever one of the input or output vector + // already held ghost values or when we import data from a vector with + // the same local range but different ghost layout + bool must_update_ghost_values = true; + + // check whether the two vectors use the same parallel partitioner. if + // not, check if all local ranges are the same (that way, we can + // exchange data between different parallel layouts) if (partitioner.get() == 0) reinit (c, true); else if (partitioner.get() != c.partitioner.get()) @@ -1101,8 +1166,12 @@ namespace parallel local_ranges_different_loc) reinit (c, true); } + else + must_update_ghost_values = vector_is_ghosted || c.vector_is_ghosted; + vector_view = c.vector_view; - update_ghost_values(); + if (must_update_ghost_values) + update_ghost_values(); return *this; } @@ -1135,8 +1204,12 @@ namespace parallel reinit (c, true); } vector_view.reinit (partitioner->local_size(), val); - if (partitioner->local_size() > 0) + + if (partitioner->local_size()) vector_view.equ (1., c.vector_view); + + if (vector_is_ghosted || c.vector_is_ghosted) + update_ghost_values(); return *this; } @@ -1195,6 +1268,17 @@ namespace parallel std::fill_n (&val[partitioner->local_size()], partitioner->n_ghost_indices(), Number()); + vector_is_ghosted = false; + } + + + + template + inline + bool + Vector::has_ghost_elements () const + { + return vector_is_ghosted; } @@ -1360,7 +1444,7 @@ namespace parallel Vector::mean_value_local () const { Assert (partitioner->size()!=0, ExcEmptyObject()); - return (partitioner->local_size()>0 ? + return (partitioner->local_size() ? vector_view.mean_value() : Number()); } @@ -1389,7 +1473,7 @@ namespace parallel typename Vector::real_type Vector::l1_norm_local () const { - return partitioner->local_size()>0 ? vector_view.l1_norm() : real_type(); + return partitioner->local_size() ? vector_view.l1_norm() : real_type(); } @@ -1424,7 +1508,7 @@ namespace parallel typename Vector::real_type Vector::lp_norm_local (const real_type p) const { - return partitioner->local_size()>0 ? vector_view.lp_norm(p) : real_type(); + return partitioner->local_size() ? vector_view.lp_norm(p) : real_type(); } @@ -1450,7 +1534,7 @@ namespace parallel typename Vector::real_type Vector::linfty_norm_local () const { - return partitioner->local_size()>0 ? vector_view.linfty_norm() : real_type(); + return partitioner->local_size() ? vector_view.linfty_norm() : real_type(); } @@ -1519,8 +1603,7 @@ namespace parallel { IndexSet is (size()); - const std::pair x = local_range(); - is.add_range (x.first, x.second); + is.add_range (local_range().first, local_range().second); return is; } @@ -1602,6 +1685,10 @@ namespace parallel Number Vector::operator() (const size_type global_index) const { + // do not allow reading a vector which is not in ghost mode + Assert (in_local_range (global_index) || vector_is_ghosted == true, + ExcMessage("You tried to read a ghost element of this vector, " + "but it has not imported its ghost values.")); return val[partitioner->global_to_local(global_index)]; } @@ -1612,6 +1699,12 @@ namespace parallel Number & Vector::operator() (const size_type global_index) { + // we would like to prevent reading ghosts from a vector that does not + // have them imported, but this is not possible because we might be in a + // part of the code where the vector has enabled ghosts but is non-const + // (then, the compiler picks this method according to the C++ rule book + // even if a human would pick the const method when this subsequent use + // is just a read) return val[partitioner->global_to_local (global_index)]; } @@ -1656,10 +1749,11 @@ namespace parallel const ForwardIterator indices_end, OutputIterator values_begin) const { - while (indices_begin != indices_end) { - *values_begin = operator()(*indices_begin); - indices_begin++; values_begin++; - } + while (indices_begin != indices_end) + { + *values_begin = operator()(*indices_begin); + indices_begin++; values_begin++; + } } @@ -1672,6 +1766,10 @@ namespace parallel AssertIndexRange (local_index, partitioner->local_size()+ partitioner->n_ghost_indices()); + // do not allow reading a vector which is not in ghost mode + Assert (local_index < local_size() || vector_is_ghosted == true, + ExcMessage("You tried to read a ghost element of this vector, " + "but it has not imported its ghost values.")); return val[local_index]; } @@ -1695,8 +1793,8 @@ namespace parallel Vector & Vector::operator = (const Number s) { - // if we call Vector::operator=0, we want to - // zero out all the entries plus ghosts. + // if we call Vector::operator=0, we want to zero out all the entries + // plus ghosts. if (partitioner->local_size() > 0) vector_view.dealii::template Vector::operator= (s); if (s==Number()) @@ -1713,11 +1811,15 @@ namespace parallel Vector::operator += (const Vector &v) { AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation + + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation if (local_size()>0) vector_view += v.vector_view; + + if (vector_is_ghosted) + update_ghost_values(); + return *this; } @@ -1729,11 +1831,15 @@ namespace parallel Vector::operator -= (const Vector &v) { AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation + + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation if (local_size()>0) vector_view -= v.vector_view; + + if (vector_is_ghosted) + update_ghost_values(); + return *this; } @@ -1771,7 +1877,7 @@ namespace parallel void Vector::add (const size_type n_indices, const size_type *indices, - const OtherNumber *values) + const OtherNumber *values) { for (size_type i=0; i::add (const Number a) { - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.add (a); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1802,12 +1910,13 @@ namespace parallel void Vector::add (const Vector &v) { - AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.add (v.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1818,12 +1927,13 @@ namespace parallel Vector::add (const Number a, const Vector &v) { - AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.add (a, v.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1836,13 +1946,13 @@ namespace parallel const Number b, const Vector &w) { - AssertDimension (local_size(), v.local_size()); - AssertDimension (local_size(), w.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.add (a, v.vector_view, b, w.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1853,12 +1963,13 @@ namespace parallel Vector::sadd (const Number x, const Vector &v) { - AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.sadd (x, v.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1870,12 +1981,13 @@ namespace parallel const Number a, const Vector &v) { - AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.sadd (x, a, v.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1889,13 +2001,13 @@ namespace parallel const Number b, const Vector &w) { - AssertDimension (local_size(), v.local_size()); - AssertDimension (local_size(), w.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.sadd (x, a, v.vector_view, b, w.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1911,15 +2023,14 @@ namespace parallel const Number c, const Vector &x) { - AssertDimension (local_size(), v.local_size()); - AssertDimension (local_size(), w.local_size()); - AssertDimension (local_size(), x.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.sadd (s, a, v.vector_view, b, w.vector_view, c, x.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1929,11 +2040,7 @@ namespace parallel void Vector::scale (const Number factor) { - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) - vector_view *= factor; + operator *=(factor); } @@ -1943,11 +2050,14 @@ namespace parallel Vector & Vector::operator *= (const Number factor) { - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) - vector_view.operator *= (factor); + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) + vector_view *= factor; + + if (vector_is_ghosted) + update_ghost_values(); + return *this; } @@ -1958,11 +2068,7 @@ namespace parallel Vector & Vector::operator /= (const Number factor) { - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) - vector_view.operator /= (factor); + operator *= (1./factor); return *this; } @@ -1973,11 +2079,13 @@ namespace parallel void Vector::scale (const Vector &scaling_factors) { - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.scale (scaling_factors.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1988,7 +2096,11 @@ namespace parallel void Vector::scale (const Vector &scaling_factors) { - vector_view.template scale (scaling_factors.vector_view); + if (local_size()) + vector_view.template scale (scaling_factors.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -1999,12 +2111,13 @@ namespace parallel Vector::equ (const Number a, const Vector &v) { - AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.equ (a, v.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -2016,12 +2129,13 @@ namespace parallel Vector::equ (const Number a, const Vector &v) { - AssertDimension (local_size(), v.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.equ (a, v.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -2034,13 +2148,13 @@ namespace parallel const Number b, const Vector &w) { - AssertDimension (local_size(), v.local_size()); - AssertDimension (local_size(), w.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.equ (a, v.vector_view, b, w.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -2055,15 +2169,14 @@ namespace parallel const Number c, const Vector &x) { - AssertDimension (local_size(), v.local_size()); - AssertDimension (local_size(), w.local_size()); - AssertDimension (local_size(), w.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.equ (a, v.vector_view, b, w.vector_view, c, x.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } @@ -2074,13 +2187,13 @@ namespace parallel Vector::ratio (const Vector &a, const Vector &b) { - AssertDimension (local_size(), a.local_size()); - AssertDimension (local_size(), b.local_size()); - // dealii::Vector does not allow empty fields - // but this might happen on some processors - // for parallel implementation - if (local_size()>0) + // dealii::Vector does not allow empty fields but this might happen on + // some processors for parallel implementation + if (local_size()) vector_view.ratio (a.vector_view, b.vector_view); + + if (vector_is_ghosted) + update_ghost_values(); } diff --git a/deal.II/include/deal.II/lac/parallel_vector.templates.h b/deal.II/include/deal.II/lac/parallel_vector.templates.h index 334966c5f0..624168b2e7 100644 --- a/deal.II/include/deal.II/lac/parallel_vector.templates.h +++ b/deal.II/include/deal.II/lac/parallel_vector.templates.h @@ -92,6 +92,8 @@ namespace parallel // set entries to zero if so requested if (fast == false) this->operator = (Number()); + + vector_is_ghosted = false; } @@ -134,6 +136,8 @@ namespace parallel // call these methods and hence do not need to have the storage. import_data = 0; } + + vector_is_ghosted = false; } @@ -194,6 +198,8 @@ namespace parallel // call these methods and hence do not need to have the storage. import_data = 0; } + + vector_is_ghosted = false; } @@ -209,6 +215,8 @@ namespace parallel vector_view = c.vector_view; if (call_update_ghost_values == true) update_ghost_values(); + else + vector_is_ghosted = false; } @@ -218,10 +226,12 @@ namespace parallel Vector::compress_start (const unsigned int counter, ::dealii::VectorOperation::values operation) { -#ifdef DEAL_II_WITH_MPI + Assert (vector_is_ghosted == false, + ExcMessage ("Cannot call compress() on a ghosted vector")); +#ifdef DEAL_II_WITH_MPI // nothing to do for insert (only need to zero ghost entries in - // compress_finish(). in debug mode we still want to check consistency + // compress_finish()). in debug mode we want to check consistency // of the inserted data, therefore the communication is still // initialized. Having different code in debug and optimized mode is // somewhat dangerous, but it really saves communication so it seems @@ -244,20 +254,17 @@ namespace parallel const size_type n_import_targets = part.import_targets().size(); const size_type n_ghost_targets = part.ghost_targets().size(); - // Need to send and receive the data. Use - // non-blocking communication, where it is - // generally less overhead to first initiate - // the receive and then actually send the data + // Need to send and receive the data. Use non-blocking communication, + // where it is generally less overhead to first initiate the receive and + // then actually send the data if (compress_requests.size() == 0) { - // set channels in different range from - // update_ghost_values channels + // set channels in different range from update_ghost_values channels const unsigned int channel = counter + 400; unsigned int current_index_start = 0; compress_requests.resize (n_import_targets + n_ghost_targets); - // allocate import_data in case it is not set - // up yet + // allocate import_data in case it is not set up yet if (import_data == 0) import_data = new Number[part.n_import_indices()]; for (size_type i=0; i 0) { - int ierr; - ierr = MPI_Startall(compress_requests.size(),&compress_requests[0]); + int ierr = MPI_Startall(compress_requests.size(),&compress_requests[0]); Assert (ierr == MPI_SUCCESS, ExcInternalError()); } @@ -305,7 +311,6 @@ namespace parallel (void)counter; (void)operation; #endif - } @@ -328,8 +333,7 @@ namespace parallel const Utilities::MPI::Partitioner &part = *partitioner; - // nothing to do when we neither have import - // nor ghost indices. + // nothing to do when we neither have import nor ghost indices. if (part.n_ghost_indices()==0 && part.n_import_indices()==0) return; @@ -346,9 +350,8 @@ namespace parallel // first wait for the receive to complete if (compress_requests.size() > 0 && n_import_targets > 0) { - int ierr; - ierr = MPI_Waitall (n_import_targets, &compress_requests[0], - MPI_STATUSES_IGNORE); + int ierr = MPI_Waitall (n_import_targets, &compress_requests[0], + MPI_STATUSES_IGNORE); Assert (ierr == MPI_SUCCESS, ExcInternalError()); Number *read_position = import_data; @@ -377,10 +380,9 @@ namespace parallel if (compress_requests.size() > 0 && n_ghost_targets > 0) { - int ierr; - ierr = MPI_Waitall (n_ghost_targets, - &compress_requests[n_import_targets], - MPI_STATUSES_IGNORE); + int ierr = MPI_Waitall (n_ghost_targets, + &compress_requests[n_import_targets], + MPI_STATUSES_IGNORE); Assert (ierr == MPI_SUCCESS, ExcInternalError()); } else @@ -401,8 +403,7 @@ namespace parallel #ifdef DEAL_II_WITH_MPI const Utilities::MPI::Partitioner &part = *partitioner; - // nothing to do when we neither have import - // nor ghost indices. + // nothing to do when we neither have import nor ghost indices. if (part.n_ghost_indices()==0 && part.n_import_indices()==0) return; @@ -412,10 +413,9 @@ namespace parallel const size_type n_import_targets = part.import_targets().size(); const size_type n_ghost_targets = part.ghost_targets().size(); - // Need to send and receive the data. Use - // non-blocking communication, where it is - // generally less overhead to first initiate - // the receive and then actually send the data + // Need to send and receive the data. Use non-blocking communication, + // where it is generally less overhead to first initiate the receive and + // then actually send the data if (update_ghost_values_requests.size() == 0) { Assert (part.local_size() == vector_view.size(), @@ -424,8 +424,8 @@ namespace parallel update_ghost_values_requests.resize (n_import_targets+n_ghost_targets); for (size_type i=0; i(&val[current_index_start]), part.ghost_targets()[i].second*sizeof(Number), MPI_BYTE, @@ -439,8 +439,7 @@ namespace parallel AssertDimension (current_index_start, part.local_size()+part.n_ghost_indices()); - // allocate import_data in case it is not set - // up yet + // allocate import_data in case it is not set up yet if (import_data == 0 && part.n_import_indices() > 0) import_data = new Number[part.n_import_indices()]; current_index_start = 0; @@ -458,8 +457,7 @@ namespace parallel AssertDimension (current_index_start, part.n_import_indices()); } - // copy the data that is actually to be send - // to the import_data field + // copy the data that is actually to be send to the import_data field if (part.n_import_indices() > 0) { Assert (import_data != 0, ExcInternalError()); @@ -475,9 +473,8 @@ namespace parallel update_ghost_values_requests.size()); if (update_ghost_values_requests.size() > 0) { - int ierr; - ierr = MPI_Startall(update_ghost_values_requests.size(), - &update_ghost_values_requests[0]); + int ierr = MPI_Startall(update_ghost_values_requests.size(), + &update_ghost_values_requests[0]); Assert (ierr == MPI_SUCCESS, ExcInternalError()); } #else @@ -492,10 +489,8 @@ namespace parallel Vector::update_ghost_values_finish () const { #ifdef DEAL_II_WITH_MPI - // wait for both sends and receives to - // complete, even though only receives are - // really necessary. this gives (much) better - // performance + // wait for both sends and receives to complete, even though only + // receives are really necessary. this gives (much) better performance AssertDimension (partitioner->ghost_targets().size() + partitioner->import_targets().size(), update_ghost_values_requests.size()); @@ -504,13 +499,13 @@ namespace parallel // make this function thread safe Threads::Mutex::ScopedLock lock (mutex); - int ierr; - ierr = MPI_Waitall (update_ghost_values_requests.size(), - &update_ghost_values_requests[0], - MPI_STATUSES_IGNORE); + int ierr = MPI_Waitall (update_ghost_values_requests.size(), + &update_ghost_values_requests[0], + MPI_STATUSES_IGNORE); Assert (ierr == MPI_SUCCESS, ExcInternalError()); } #endif + vector_is_ghosted = true; } @@ -520,28 +515,38 @@ namespace parallel Vector::swap (Vector &v) { #ifdef DEAL_II_WITH_MPI - // introduce a Barrier over all MPI processes - // to make sure that the compress request are - // no longer used before changing the owner - if (v.partitioner->n_mpi_processes() > 1) - MPI_Barrier (v.partitioner->get_communicator()); - if (partitioner->n_mpi_processes() > 1 && - v.partitioner->n_mpi_processes() != - partitioner->n_mpi_processes()) - MPI_Barrier (partitioner->get_communicator()); + +#ifdef DEBUG + // make sure that there are not outstanding requests from updating ghost + // values or compress + int flag = 1; + int ierr = MPI_Testall (update_ghost_values_requests.size(), + &update_ghost_values_requests[0], + &flag, MPI_STATUSES_IGNORE); + Assert (ierr == MPI_SUCCESS, ExcInternalError()); + Assert (flag == 1, + ExcMessage("MPI found unfinished update_ghost_values() requests" + "when calling swap, which is not allowed")); + ierr = MPI_Testall (compress_requests.size(), &compress_requests[0], + &flag, MPI_STATUSES_IGNORE); + Assert (ierr == MPI_SUCCESS, ExcInternalError()); + Assert (flag == 1, + ExcMessage("MPI found unfinished compress() requests " + "when calling swap, which is not allowed")); +#endif std::swap (compress_requests, v.compress_requests); std::swap (update_ghost_values_requests, v.update_ghost_values_requests); #endif - std::swap (partitioner, v.partitioner); - std::swap (allocated_size, v.allocated_size); - std::swap (val, v.val); - std::swap (import_data, v.import_data); + std::swap (partitioner, v.partitioner); + std::swap (allocated_size, v.allocated_size); + std::swap (val, v.val); + std::swap (import_data, v.import_data); + std::swap (vector_is_ghosted, v.vector_is_ghosted); - // vector view cannot be swapped so reset it - // manually (without touching the vector - // elements) + // vector view cannot be swapped so reset it manually (without touching + // the vector elements) vector_view.reinit (partitioner->local_size(), val); v.vector_view.reinit (v.partitioner->local_size(), v.val); } @@ -555,10 +560,9 @@ namespace parallel std::size_t memory = sizeof(*this); memory += sizeof (Number) * static_cast(allocated_size); - // if the partitioner is shared between more - // processors, just count a fraction of that - // memory, since we're not actually using more - // memory for it. + // if the partitioner is shared between more processors, just count a + // fraction of that memory, since we're not actually using more memory + // for it. if (partitioner.use_count() > 0) memory += partitioner->memory_consumption()/partitioner.use_count()+1; if (import_data != 0) @@ -587,10 +591,9 @@ namespace parallel else out.setf (std::ios::fixed, std::ios::floatfield); - // to make the vector write out all the - // information in order, use as many barriers - // as there are processors and start writing - // when it's our turn + // to make the vector write out all the information in order, use as + // many barriers as there are processors and start writing when it's our + // turn #ifdef DEAL_II_WITH_MPI if (partitioner->n_mpi_processes() > 1) for (unsigned int i=0; ithis_mpi_process(); i++) @@ -609,17 +612,22 @@ namespace parallel for (size_type i=0; ilocal_size(); ++i) out << local_element(i) << std::endl; out << std::endl; - out << "Ghost entries (global index / value):" << std::endl; - if (across) - for (size_type i=0; in_ghost_indices(); ++i) - out << '(' << partitioner->ghost_indices().nth_index_in_set(i) - << '/' << local_element(partitioner->local_size()+i) << ") "; - else - for (size_type i=0; in_ghost_indices(); ++i) - out << '(' << partitioner->ghost_indices().nth_index_in_set(i) - << '/' << local_element(partitioner->local_size()+i) << ")" - << std::endl; - out << std::endl << std::flush; + + if (vector_is_ghosted) + { + out << "Ghost entries (global index / value):" << std::endl; + if (across) + for (size_type i=0; in_ghost_indices(); ++i) + out << '(' << partitioner->ghost_indices().nth_index_in_set(i) + << '/' << local_element(partitioner->local_size()+i) << ") "; + else + for (size_type i=0; in_ghost_indices(); ++i) + out << '(' << partitioner->ghost_indices().nth_index_in_set(i) + << '/' << local_element(partitioner->local_size()+i) << ")" + << std::endl; + out << std::endl; + } + out << std::flush; #ifdef DEAL_II_WITH_MPI if (partitioner->n_mpi_processes() > 1) diff --git a/deal.II/include/deal.II/matrix_free/matrix_free.h b/deal.II/include/deal.II/matrix_free/matrix_free.h index 8c8b0a83f6..59b9ae536f 100644 --- a/deal.II/include/deal.II/matrix_free/matrix_free.h +++ b/deal.II/include/deal.II/matrix_free/matrix_free.h @@ -1643,17 +1643,22 @@ reinit(const Mapping &mapping, // internal helper functions that define how to call MPI data exchange // functions: for generic vectors, do nothing at all. For distributed vectors, -// can call update_ghost_values_start function and so on. If we have -// collections of vectors, just do the individual functions of the -// components. this is a bit messy for block vectors, which use some -// additional helper functions to select the blocks +// call update_ghost_values_start function and so on. If we have collections +// of vectors, just do the individual functions of the components. In order to +// keep ghost values consistent (whether we are in read or write mode). the whole situation is a bit complicated by the fact +// that we need to treat block vectors differently, which use some additional +// helper functions to select the blocks and template magic. namespace internal { template - void update_ghost_values_start_block (const VectorStruct &vec, + bool update_ghost_values_start_block (const VectorStruct &vec, const unsigned int channel, internal::bool2type); template + void reset_ghost_values_block (const VectorStruct &vec, + const bool zero_out_ghosts, + internal::bool2type); + template void update_ghost_values_finish_block (const VectorStruct &vec, internal::bool2type); template @@ -1665,9 +1670,16 @@ namespace internal internal::bool2type); template - void update_ghost_values_start_block (const VectorStruct &, + bool update_ghost_values_start_block (const VectorStruct &, const unsigned int, internal::bool2type) + { + return false; + } + template + void reset_ghost_values_block (const VectorStruct &, + const bool, + internal::bool2type) {} template void update_ghost_values_finish_block (const VectorStruct &, @@ -1685,55 +1697,125 @@ namespace internal + // returns true if the vector was in a state without ghost values before, + // i.e., we need to zero out ghosts in the very end template inline - void update_ghost_values_start (const VectorStruct &vec, + bool update_ghost_values_start (const VectorStruct &vec, const unsigned int channel = 0) { - update_ghost_values_start_block(vec, channel, - internal::bool2type::value>()); + return + update_ghost_values_start_block(vec, channel, + internal::bool2type::value>()); } template inline - void update_ghost_values_start (const parallel::distributed::Vector &vec, + bool update_ghost_values_start (const parallel::distributed::Vector &vec, const unsigned int channel = 0) { + bool return_value = !vec.has_ghost_elements(); vec.update_ghost_values_start(channel); + return return_value; } template inline - void update_ghost_values_start (const std::vector &vec) + bool update_ghost_values_start (const std::vector &vec) { + bool return_value = false; for (unsigned int comp=0; comp inline - void update_ghost_values_start (const std::vector &vec) + bool update_ghost_values_start (const std::vector &vec) { + bool return_value = false; for (unsigned int comp=0; comp inline - void update_ghost_values_start_block (const VectorStruct &vec, + bool update_ghost_values_start_block (const VectorStruct &vec, const unsigned int channel, internal::bool2type) + { + bool return_value = false; + for (unsigned int i=0; i + inline + void reset_ghost_values (const VectorStruct &vec, + const bool zero_out_ghosts) + { + reset_ghost_values_block(vec, zero_out_ghosts, + internal::bool2type::value>()); + } + + + + template + inline + void reset_ghost_values (const parallel::distributed::Vector &vec, + const bool zero_out_ghosts) + { + if (zero_out_ghosts) + const_cast&>(vec).zero_out_ghosts(); + } + + + + template + inline + void reset_ghost_values (const std::vector &vec, + const bool zero_out_ghosts) + { + for (unsigned int comp=0; comp + inline + void reset_ghost_values (const std::vector &vec, + const bool zero_out_ghosts) + { + for (unsigned int comp=0; comp + inline + void reset_ghost_values_block (const VectorStruct &vec, + const bool zero_out_ghosts, + internal::bool2type) { for (unsigned int i=0; i::cell_loop OutVector &dst, const InVector &src) const { + // in any case, need to start the ghost import at the beginning + bool ghosts_were_not_set = internal::update_ghost_values_start (src); + #ifdef DEAL_II_WITH_THREADS // Use multithreading if so requested and if there is enough work to do in @@ -2181,7 +2266,6 @@ MatrixFree::cell_loop if (task_info.use_partition_partition == true) { - internal::update_ghost_values_start(src); tbb::empty_task *root = new( tbb::task::allocate_root() ) tbb::empty_task; unsigned int evens = task_info.evens; @@ -2252,11 +2336,9 @@ MatrixFree::cell_loop root->wait_for_all(); root->destroy(*root); - internal::compress_finish(dst); } else // end of partition-partition, start of partition-color { - internal::update_ghost_values_start(src); unsigned int evens = task_info.evens; unsigned int odds = task_info.odds; @@ -2387,7 +2469,6 @@ MatrixFree::cell_loop internal::compress_start(dst); } - internal::compress_finish(dst); } } else @@ -2396,8 +2477,6 @@ MatrixFree::cell_loop { std::pair cell_range; - internal::update_ghost_values_start (src); - // First operate on cells where no ghost data is needed (inner cells) { cell_range.first = 0; @@ -2426,9 +2505,11 @@ MatrixFree::cell_loop cell_range.second = size_info.n_macro_cells; cell_operation (*this, dst, src, cell_range); } - - internal::compress_finish(dst); } + + // In every case, we need to finish transfers at the very end + internal::compress_finish(dst); + internal::reset_ghost_values(src, ghosts_were_not_set); } diff --git a/tests/mpi/matrix_free_02.cc b/tests/mpi/matrix_free_02.cc index a9b26a0512..56fad9a8eb 100644 --- a/tests/mpi/matrix_free_02.cc +++ b/tests/mpi/matrix_free_02.cc @@ -188,8 +188,6 @@ void test () for (unsigned int parallel_option = 0; parallel_option < 3; ++parallel_option) { - out -= ref; - const double diff_norm = out.linfty_norm(); const QGauss<1> quad (fe_degree+1); typename MatrixFree::AdditionalData data; data.mpi_communicator = MPI_COMM_WORLD; diff --git a/tests/mpi/parallel_block_vector_01.cc b/tests/mpi/parallel_block_vector_01.cc index 6efa357d4b..e518efcd3b 100644 --- a/tests/mpi/parallel_block_vector_01.cc +++ b/tests/mpi/parallel_block_vector_01.cc @@ -47,7 +47,8 @@ void test () local_relevant = local_owned; local_relevant.add_range(1,2); - parallel::distributed::Vector v(local_owned, local_owned, MPI_COMM_WORLD); + parallel::distributed::Vector v(local_owned, local_relevant, + MPI_COMM_WORLD); // set local values if (myid < 8) diff --git a/tests/mpi/parallel_block_vector_02.cc b/tests/mpi/parallel_block_vector_02.cc new file mode 100644 index 0000000000..28d08f0df7 --- /dev/null +++ b/tests/mpi/parallel_block_vector_02.cc @@ -0,0 +1,162 @@ +// --------------------------------------------------------------------- +// $Id$ +// +// Copyright (C) 2011 - 2013 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE at +// the top level of the deal.II distribution. +// +// --------------------------------------------------------------------- + + +// check ghost handling on parallel block vectors + +#include "../tests.h" +#include +#include +#include +#include +#include +#include +#include + + +void test () +{ + unsigned int myid = Utilities::MPI::this_mpi_process (MPI_COMM_WORLD); + unsigned int numproc = Utilities::MPI::n_mpi_processes (MPI_COMM_WORLD); + + if (myid==0) deallog << "numproc=" << numproc << std::endl; + + + // each processor from processor 1 to 8 owns 2 indices (the other processors + // do not own any dof), and all processors are ghosting element 1 + IndexSet local_owned(std::min(16U,numproc*2)); + if (myid < 8) + local_owned.add_range(myid*2,myid*2+2); + IndexSet local_relevant(numproc*2); + local_relevant = local_owned; + local_relevant.add_range(1,2); + + parallel::distributed::Vector v(local_owned, local_relevant, + MPI_COMM_WORLD); + + // set local values + if (myid < 8) + { + v(myid*2)=myid*2.0; + v(myid*2+1)=myid*2.0+1.0; + } + v.compress(VectorOperation::insert); + + parallel::distributed::BlockVector w(3); + for (unsigned int i=0; i<3; ++i) + { + w.block(i) = v; + w.block(i) *= (i+1); + } + w.collect_sizes(); + + // see if we can access the ghosted entry 1 in each block with the correct + // value: the initialization should also update the ghost values, so all + // processors should have i+1 + for (unsigned int i=0; i<3; ++i) + AssertDimension(i+1,(unsigned int)w.block(i)(1)); + + // import ghost values, all processors should still have i+1 + w.update_ghost_values(); + for (unsigned int i=0; i<3; ++i) + AssertDimension(i+1,(unsigned int)w.block(i)(1)); + + // zero out ghosts, now all processors except processor 1 should have 0. + w.zero_out_ghosts(); + for (unsigned int i=0; i<3; ++i) + if (myid == 0) + { + AssertDimension(i+1,(unsigned int)w.block(i)(1)); + } + else + { + AssertDimension(0,(unsigned int)w.block(i)(1)); + } + + // create a vector copy that gets the entries from w. First, it should have + // updated the ghosts because it is created from an empty state. + parallel::distributed::BlockVector x(w); + Assert(x.has_ghost_elements() == true, ExcInternalError()); + for (unsigned int i=0; i<3; ++i) + AssertDimension(i+1,(unsigned int)x.block(i)(1)); + + // now we zero the vector, which should disable ghost elements + x = 0; + Assert(x.has_ghost_elements() == false, ExcInternalError()); + + // we copy from w (i.e., the same vector but one that does not have ghosts + // enabled) -> should not have ghosts enabled + x = w; + Assert(x.has_ghost_elements() == false, ExcInternalError()); + for (unsigned int i=0; i<3; ++i) + if (myid == 0) + { + AssertDimension(i+1,(unsigned int)x.block(i)(1)); + } + else + { + AssertDimension(0,(unsigned int)x.block(i)(1)); + } + + x.update_ghost_values(); + Assert(x.has_ghost_elements() == true, ExcInternalError()); + + + // add something to entry 1 on all processors + w(1) += myid+1; + w.compress(VectorOperation::add); + if (myid == 0) + AssertDimension((unsigned int)w(1), 1+(numproc*(numproc+1))/2); + + // add again and check if everything is still correct + w(1+v.size()) += myid+1; + w.compress(VectorOperation::add); + if (myid == 0) + AssertDimension((unsigned int)w(1), 1+(numproc*(numproc+1))/2); + if (myid == 0) + AssertDimension((unsigned int)w(v.size()+1), 2+(numproc*(numproc+1))/2); + + w.update_ghost_values(); + AssertDimension((unsigned int)w(1), 1+(numproc*(numproc+1))/2); + AssertDimension((unsigned int)w(v.size()+1), 2+(numproc*(numproc+1))/2); + + if (myid == 0) + deallog << "OK" << std::endl; +} + + + +int main (int argc, char **argv) +{ + Utilities::System::MPI_InitFinalize mpi_initialization(argc, argv); + + unsigned int myid = Utilities::MPI::this_mpi_process (MPI_COMM_WORLD); + deallog.push(Utilities::int_to_string(myid)); + + if (myid == 0) + { + std::ofstream logfile(output_file_for_mpi("parallel_block_vector_02").c_str()); + deallog.attach(logfile); + deallog << std::setprecision(4); + deallog.depth_console(0); + deallog.threshold_double(1.e-10); + + test(); + } + else + test(); + +} diff --git a/tests/mpi/parallel_block_vector_02/ncpu_1/cmp/generic b/tests/mpi/parallel_block_vector_02/ncpu_1/cmp/generic new file mode 100644 index 0000000000..bb1593da60 --- /dev/null +++ b/tests/mpi/parallel_block_vector_02/ncpu_1/cmp/generic @@ -0,0 +1,3 @@ + +DEAL:0::numproc=1 +DEAL:0::OK diff --git a/tests/mpi/parallel_block_vector_02/ncpu_10/cmp/generic b/tests/mpi/parallel_block_vector_02/ncpu_10/cmp/generic new file mode 100644 index 0000000000..999baf1a98 --- /dev/null +++ b/tests/mpi/parallel_block_vector_02/ncpu_10/cmp/generic @@ -0,0 +1,3 @@ + +DEAL:0::numproc=10 +DEAL:0::OK diff --git a/tests/mpi/parallel_block_vector_02/ncpu_4/cmp/generic b/tests/mpi/parallel_block_vector_02/ncpu_4/cmp/generic new file mode 100644 index 0000000000..6f5d2775d9 --- /dev/null +++ b/tests/mpi/parallel_block_vector_02/ncpu_4/cmp/generic @@ -0,0 +1,3 @@ + +DEAL:0::numproc=4 +DEAL:0::OK -- 2.39.5