diff --git a/include/mesh/mesh_base.h b/include/mesh/mesh_base.h index b1c40a44ee..2a45f9ccb0 100644 --- a/include/mesh/mesh_base.h +++ b/include/mesh/mesh_base.h @@ -165,7 +165,7 @@ class MeshBase : public ParallelObject virtual ~MeshBase (); /** - * A partitioner to use at each prepare_for_use() + * A partitioner to use at each partitioning */ virtual std::unique_ptr & partitioner() { return _partitioner; } @@ -194,7 +194,7 @@ class MeshBase : public ParallelObject * No Node is removed from the mesh, however even NodeElem elements * are deleted, so the remaining Nodes will be considered "unused" * and cleared unless they are reconnected to new elements before - * the next \p prepare_for_use() + * the next preparation step. * * This does not affect BoundaryInfo data; any boundary information * associated elements should already be cleared. @@ -202,17 +202,150 @@ class MeshBase : public ParallelObject virtual void clear_elems () = 0; /** - * \returns \p true if the mesh has been prepared via a call - * to \p prepare_for_use, \p false otherwise. + * \returns \p true if the mesh is marked as having undergone all of + * the preparation done in a call to \p prepare_for_use, \p false + * otherwise. */ bool is_prepared () const - { return _is_prepared; } + { return _preparation; } /** - * Tells this we have done some operation where we should no longer consider ourself prepared + * \returns the \p Preparation structure with details about in what + * ways \p this mesh is currently prepared or unprepared. This + * structure may change in the future when cache designs change. + */ + struct Preparation; + + Preparation preparation () const + { return _preparation; } + +#ifdef LIBMESH_ENABLE_DEPRECATED + /** + * Tells this we have done some operation where we should no longer + * consider ourself prepared. This is a very coarse setting; it is + * generally more efficient to mark finer-grained settings instead. + * + * This method name is now deprecated, in part to match the less + * awkward unset_has_ names of the more fine-grained methods, in + * part as a way to prompt older user codes to use the more + * fine-grained methods where they can, to speed up the + * complete_preparation() calls afterward. */ void set_isnt_prepared() - { _is_prepared = false; } + { libmesh_deprecated(); _preparation = false; } +#endif // LIBMESH_ENABLE_DEPRECATED + + /** + * Tells this we have done some operation where we should no longer + * consider ourself prepared. This is a very coarse setting; it is + * generally more efficient to mark finer-grained settings instead. + */ + void unset_is_prepared() + { _preparation = false; } + + /** + * Tells this we have done some operation creating unpartitioned + * elements. + * + * User code which adds elements to this mesh must either partition + * them too or call this method. + */ + void unset_is_partitioned() + { _preparation.is_partitioned = false; } + + /** + * Tells this we have done some operation (e.g. adding objects to a + * distributed mesh on one processor only) which can lose + * synchronization of id counts. + * + * User code which does distributed additions of nodes or elements + * must call either this method or \p update_parallel_id_counts(). + */ + void unset_has_synched_id_counts() + { _preparation.has_synched_id_counts = false; } + + /** + * Tells this we have done some operation (e.g. adding elements + * without setting their neighbor pointers, or adding disjoint + * neighbor boundary pairs) which requires neighbor pointers to be + * determined later. + * + * User code which adds new elements to this mesh must call this + * function or manually set neighbor pointer from and to those + * elements. + */ + void unset_has_neighbor_ptrs() + { _preparation.has_neighbor_ptrs = false; } + + /** + * Tells this we have done some operation (e.g. adding elements with + * a new dimension or subdomain value) which may invalidate cached + * summaries of element data. + * + * User code which adds new elements to this mesh must call this + * function. + */ + void unset_has_cached_elem_data() + { _preparation.has_cached_elem_data = false; } + + /** + * Tells this we have done some operation (e.g. refining elements + * with interior parents) which requires interior parent pointers to + * be found later. + * + * Most user code will not need to call this method; any user code + * that manipulates interior parents or their boundary elements may + * be an exception. + */ + void unset_has_interior_parent_ptrs() + { _preparation.has_interior_parent_ptrs = false; } + + /** + * Tells this we have done some operation (e.g. repartitioning) + * which may have left elements as ghosted which on a distributed + * mesh should be remote. + * + * User code should probably never need to use this; we can set it + * in Partitioner. Any user code which manually repartitions + * elements on distributed meshes may need to call this manually, in + * addition to manually communicating elements with newly-created + * ghosting requirements. + */ + void unset_has_removed_remote_elements() + { _preparation.has_removed_remote_elements = false; } + + /** + * Tells this we have done some operation (e.g. coarsening) + * which may have left orphaned nodes in need of removal. + * + * Most user code should probably never need to use this; we can set + * it in MeshRefinement. User code which deletes elements without + * carefully deleting orphaned nodes should call this manually. + */ + void unset_has_removed_orphaned_nodes() + { _preparation.has_removed_orphaned_nodes = false; } + + /** + * Tells this we have done some operation (e.g. adding or removing + * elements) which may require a reinit() of custom ghosting + * functors. + * + * User code which adds or removes elements should call this method. + * User code which moves nodes ... should probably call this method, + * in case ghosting functors depending on position exist? + */ + void unset_has_reinit_ghosting_functors() + { _preparation.has_reinit_ghosting_functors = false; } + + /** + * Tells this we have done some operation which may have invalidated + * our cached boundary id sets. + * + * User code which removes elements, or which adds or removes + * boundary entries, should call this method. + */ + void unset_has_boundary_id_sets() + { _preparation.has_boundary_id_sets = false; } /** * \returns \p true if all elements and nodes of the mesh @@ -260,7 +393,9 @@ class MeshBase : public ParallelObject * except for "ghosts" which touch a local element, and deletes * all nodes which are not part of a local or ghost element */ - virtual void delete_remote_elements () {} + virtual void delete_remote_elements () { + _preparation.has_removed_remote_elements = true; + } /** * Loops over ghosting functors and calls mesh_reinit() @@ -400,16 +535,17 @@ class MeshBase : public ParallelObject * higher dimensions is checked. Also, x-z and y-z planar meshes are * considered to have spatial dimension == 3. * - * The spatial dimension is updated during prepare_for_use() based + * The spatial dimension is updated during mesh preparation based * on the dimensions of the various elements present in the Mesh, - * but is *never automatically decreased* by this function. + * but is *never automatically decreased*. * * For example, if the user calls set_spatial_dimension(2) and then * later inserts 3D elements into the mesh, * Mesh::spatial_dimension() will return 3 after the next call to - * prepare_for_use(). On the other hand, if the user calls - * set_spatial_dimension(3) and then inserts only x-aligned 1D - * elements into the Mesh, mesh.spatial_dimension() will remain 3. + * prepare_for_use() or complete_preparation(). On the other hand, + * if the user calls set_spatial_dimension(3) and then inserts only + * x-aligned 1D elements into the Mesh, mesh.spatial_dimension() + * will remain 3. */ unsigned int spatial_dimension () const; @@ -742,7 +878,7 @@ class MeshBase : public ParallelObject * To ensure a specific element id, call e->set_id() before adding it; * only do this in parallel if you are manually keeping ids consistent. * - * Users should call MeshBase::prepare_for_use() after elements are + * Users should call MeshBase::complete_preparation() after elements are * added to and/or deleted from the mesh. */ virtual Elem * add_elem (Elem * e) = 0; @@ -761,7 +897,7 @@ class MeshBase : public ParallelObject * Insert elem \p e to the element array, preserving its id * and replacing/deleting any existing element with the same id. * - * Users should call MeshBase::prepare_for_use() after elements are + * Users should call MeshBase::complete_preparation() after elements are * added to and/or deleted from the mesh. */ virtual Elem * insert_elem (Elem * e) = 0; @@ -780,8 +916,8 @@ class MeshBase : public ParallelObject * Removes element \p e from the mesh. This method must be * implemented in derived classes in such a way that it does not * invalidate element iterators. Users should call - * MeshBase::prepare_for_use() after elements are added to and/or - * deleted from the mesh. + * MeshBase::complete_preparation() after elements are added to + * and/or deleted from the mesh. * * \note Calling this method may produce isolated nodes, i.e. nodes * not connected to any element. @@ -848,7 +984,7 @@ class MeshBase : public ParallelObject /** * Removes any orphaned nodes, nodes not connected to any elements. - * Typically done automatically in prepare_for_use + * Typically done automatically in a preparation step */ void remove_orphaned_nodes (); @@ -1122,12 +1258,26 @@ class MeshBase : public ParallelObject const std::vector * default_values = nullptr); /** - * Prepare a newly ecreated (or read) mesh for use. - * This involves 4 steps: - * 1.) call \p find_neighbors() - * 2.) call \p partition() - * 3.) call \p renumber_nodes_and_elements() - * 4.) call \p cache_elem_data() + * Prepare a newly created (or read) mesh for use. + * This involves several steps: + * 1.) renumbering (if enabled) + * 2.) removing any orphaned nodes + * 3.) updating parallel id counts + * 4.) finding neighbor links + * 5.) caching summarized element data + * 6.) finding interior parent links + * 7.) clearing any old point locator + * 8.) calling reinit() on ghosting functors + * 9.) repartitioning (if enabled) + * 10.) removing any remote elements (if enabled) + * 11.) regenerating summarized boundary id sets + * + * For backwards compatibility, prepare_for_use() performs *all* those + * steps, regardless of the official preparation() state of the + * mesh. In codes which have maintained a valid preparation() state + * via methods such as unset_has_synched_id_counts(), calling + * complete_preparation() will result in a fully-prepared mesh at + * less cost. * * The argument to skip renumbering is now deprecated - to prevent a * mesh from being renumbered, set allow_renumbering(false). The argument to skip @@ -1144,6 +1294,15 @@ class MeshBase : public ParallelObject #endif // LIBMESH_ENABLE_DEPRECATED void prepare_for_use (); + /* + * Prepare a newly created or modified mesh for use. + * + * Unlike \p prepare_for_use(), \p complete_preparation() performs + * *only* those preparatory steps that have been marked as + * necessary in the MeshBase::Preparation state. + */ + void complete_preparation(); + /** * Call the default partitioner (currently \p metis_partition()). */ @@ -1844,6 +2003,59 @@ class MeshBase : public ParallelObject const boundary_id_type b2); #endif + /** + * Flags indicating in what ways a mesh has been prepared for use. + */ + struct Preparation + { + bool is_partitioned = false, + has_synched_id_counts = false, + has_neighbor_ptrs = false, + has_cached_elem_data = false, + has_interior_parent_ptrs = false, + has_removed_remote_elements = false, + has_removed_orphaned_nodes = false, + has_boundary_id_sets = false, + has_reinit_ghosting_functors = false; + + operator bool() const { + return is_partitioned && + has_synched_id_counts && + has_neighbor_ptrs && + has_cached_elem_data && + has_interior_parent_ptrs && + has_removed_remote_elements && + has_removed_orphaned_nodes && + has_reinit_ghosting_functors && + has_boundary_id_sets; + } + + Preparation & operator= (bool set_all) { + is_partitioned = set_all; + has_synched_id_counts = set_all; + has_neighbor_ptrs = set_all; + has_cached_elem_data = set_all; + has_interior_parent_ptrs = set_all; + has_removed_remote_elements = set_all; + has_removed_orphaned_nodes = set_all; + has_reinit_ghosting_functors = set_all; + has_boundary_id_sets = set_all; + + return *this; + } + + bool operator== (const Preparation & other) { + return is_partitioned == other.is_partitioned && + has_synched_id_counts == other.has_synched_id_counts && + has_neighbor_ptrs == other.has_neighbor_ptrs && + has_cached_elem_data == other.has_cached_elem_data && + has_interior_parent_ptrs == other.has_interior_parent_ptrs && + has_removed_remote_elements == other.has_removed_remote_elements && + has_removed_orphaned_nodes == other.has_removed_orphaned_nodes && + has_reinit_ghosting_functors == other.has_reinit_ghosting_functors && + has_boundary_id_sets == other.has_boundary_id_sets; + } + }; protected: @@ -1923,9 +2135,9 @@ class MeshBase : public ParallelObject unsigned char _default_mapping_data; /** - * Flag indicating if the mesh has been prepared for use. + * Flags indicating in what ways \p this mesh has been prepared. */ - bool _is_prepared; + Preparation _preparation; /** * A \p PointLocator class for this mesh. diff --git a/include/mesh/unstructured_mesh.h b/include/mesh/unstructured_mesh.h index be3c5798f4..7c2bda2766 100644 --- a/include/mesh/unstructured_mesh.h +++ b/include/mesh/unstructured_mesh.h @@ -263,6 +263,16 @@ class UnstructuredMesh : public MeshBase * If an \p id_remapping map is provided, then element subdomain ids * in \p other_mesh will be converted using it before adding them to * \p this mesh. + * + * For backwards compatibility, this does some limited mesh + * preparation after the copy: everything except for renumbering, + * remote element removal, and partitioning. To skip just the + * step of that preparation which finds new neighbor_ptr links + * between elements, set \p skip_find_neighbors. To skip all of + * that preparation, set \p skip_preparation. If preparation is + * skipped, it is the users responsibility to set the flags + * indicating what preparation may still be necessary before using + * the mesh later. */ virtual void copy_nodes_and_elements (const MeshBase & other_mesh, const bool skip_find_neighbors = false, @@ -270,7 +280,8 @@ class UnstructuredMesh : public MeshBase dof_id_type node_id_offset = 0, unique_id_type unique_id_offset = 0, std::unordered_map * - id_remapping = nullptr); + id_remapping = nullptr, + const bool skip_preparation = false); /** * Move node and elements from other_mesh to this mesh. diff --git a/src/mesh/boundary_info.C b/src/mesh/boundary_info.C index b1878f510d..a50323ed7e 100644 --- a/src/mesh/boundary_info.C +++ b/src/mesh/boundary_info.C @@ -435,6 +435,8 @@ void BoundaryInfo::synchronize_global_id_set() libmesh_assert(_mesh); if (!_mesh->is_serial()) _communicator.set_union(_global_boundary_ids); + + _mesh->_preparation.has_boundary_id_sets = true; } diff --git a/src/mesh/distributed_mesh.C b/src/mesh/distributed_mesh.C index 24e352322d..9fdc2e3fae 100644 --- a/src/mesh/distributed_mesh.C +++ b/src/mesh/distributed_mesh.C @@ -191,21 +191,17 @@ DistributedMesh::DistributedMesh (const MeshBase & other_mesh) : _next_free_unpartitioned_node_id(this->n_processors()), _next_free_unpartitioned_elem_id(this->n_processors()) { - this->copy_nodes_and_elements(other_mesh, true); + // Just copy, skipping preparation + this->copy_nodes_and_elements(other_mesh, true, 0, 0, 0, nullptr, true); this->allow_find_neighbors(other_mesh.allow_find_neighbors()); this->allow_renumbering(other_mesh.allow_renumbering()); this->allow_remote_element_removal(other_mesh.allow_remote_element_removal()); this->skip_partitioning(other_mesh.skip_partitioning()); - // The prepare_for_use() in copy_nodes_and_elements() is going to be - // tricky to remove without breaking backwards compatibility, but it - // updates some things we want to just copy. - this->copy_cached_data(other_mesh); - this->copy_constraint_rows(other_mesh); - this->_is_prepared = other_mesh.is_prepared(); + this->_preparation = other_mesh.preparation(); auto & this_boundary_info = this->get_boundary_info(); const auto & other_boundary_info = other_mesh.get_boundary_info(); @@ -291,6 +287,8 @@ void DistributedMesh::update_parallel_id_counts() ((_next_unique_id + this->n_processors() - 1) / (this->n_processors() + 1) + 1) * (this->n_processors() + 1) + this->processor_id(); #endif + + this->_preparation.has_synched_id_counts = true; } @@ -1611,6 +1609,8 @@ void DistributedMesh::renumber_nodes_and_elements () } } + this->_preparation.has_removed_orphaned_nodes = true; + if (_skip_renumber_nodes_and_elements) { this->update_parallel_id_counts(); @@ -1776,6 +1776,8 @@ void DistributedMesh::delete_remote_elements() this->libmesh_assert_valid_parallel_ids(); this->libmesh_assert_valid_parallel_flags(); #endif + + this->_preparation.has_removed_remote_elements = true; } diff --git a/src/mesh/mesh_base.C b/src/mesh/mesh_base.C index 535117af6b..9282a104d3 100644 --- a/src/mesh/mesh_base.C +++ b/src/mesh/mesh_base.C @@ -66,7 +66,7 @@ MeshBase::MeshBase (const Parallel::Communicator & comm_in, _n_parts (1), _default_mapping_type(LAGRANGE_MAP), _default_mapping_data(0), - _is_prepared (false), + _preparation (), _point_locator (), _count_lower_dim_elems_in_point_locator(true), _partitioner (), @@ -99,7 +99,7 @@ MeshBase::MeshBase (const MeshBase & other_mesh) : _n_parts (other_mesh._n_parts), _default_mapping_type(other_mesh._default_mapping_type), _default_mapping_data(other_mesh._default_mapping_data), - _is_prepared (other_mesh._is_prepared), + _preparation (other_mesh._preparation), _point_locator (), _count_lower_dim_elems_in_point_locator(other_mesh._count_lower_dim_elems_in_point_locator), _partitioner (), @@ -119,6 +119,7 @@ MeshBase::MeshBase (const MeshBase & other_mesh) : _elem_dims(other_mesh._elem_dims), _elem_default_orders(other_mesh._elem_default_orders), _supported_nodal_order(other_mesh._supported_nodal_order), + _mesh_subdomains(other_mesh._mesh_subdomains), _elemset_codes_inverse_map(other_mesh._elemset_codes_inverse_map), _all_elemset_ids(other_mesh._all_elemset_ids), _spatial_dimension(other_mesh._spatial_dimension), @@ -187,7 +188,7 @@ MeshBase& MeshBase::operator= (MeshBase && other_mesh) _n_parts = other_mesh.n_partitions(); _default_mapping_type = other_mesh.default_mapping_type(); _default_mapping_data = other_mesh.default_mapping_data(); - _is_prepared = other_mesh.is_prepared(); + _preparation = other_mesh._preparation; _point_locator = std::move(other_mesh._point_locator); _count_lower_dim_elems_in_point_locator = other_mesh.get_count_lower_dim_elems_in_point_locator(); #ifdef LIBMESH_ENABLE_UNIQUE_ID @@ -207,6 +208,7 @@ MeshBase& MeshBase::operator= (MeshBase && other_mesh) _elem_dims = std::move(other_mesh.elem_dimensions()); _elem_default_orders = std::move(other_mesh.elem_default_orders()); _supported_nodal_order = other_mesh.supported_nodal_order(); + _mesh_subdomains = other_mesh._mesh_subdomains; _elemset_codes = std::move(other_mesh._elemset_codes); _elemset_codes_inverse_map = std::move(other_mesh._elemset_codes_inverse_map); _all_elemset_ids = std::move(other_mesh._all_elemset_ids); @@ -283,7 +285,7 @@ bool MeshBase::locally_equals (const MeshBase & other_mesh) const return false; if (_default_mapping_data != other_mesh._default_mapping_data) return false; - if (_is_prepared != other_mesh._is_prepared) + if (_preparation != other_mesh._preparation) return false; if (_count_lower_dim_elems_in_point_locator != other_mesh._count_lower_dim_elems_in_point_locator) @@ -794,6 +796,8 @@ void MeshBase::remove_orphaned_nodes () for (const auto & node : this->node_ptr_range()) if (!connected_nodes.count(node)) this->delete_node(node); + + _preparation.has_removed_orphaned_nodes = true; } @@ -834,9 +838,23 @@ void MeshBase::prepare_for_use (const bool skip_renumber_nodes_and_elements) + void MeshBase::prepare_for_use () { - LOG_SCOPE("prepare_for_use()", "MeshBase"); + // Mark everything as unprepared, except for those things we've been + // told we don't need to prepare, for backwards compatibility + this->clear_point_locator(); + _preparation = false; + _preparation.has_neighbor_ptrs = _skip_find_neighbors; + _preparation.has_removed_remote_elements = !_allow_remote_element_removal; + + this->complete_preparation(); +} + + +void MeshBase::complete_preparation() +{ + LOG_SCOPE("complete_preparation()", "MeshBase"); parallel_object_only(); @@ -871,15 +889,21 @@ void MeshBase::prepare_for_use () // using, but our partitioner might need that consistency and/or // might be confused by orphaned nodes. if (!_skip_renumber_nodes_and_elements) - this->renumber_nodes_and_elements(); + { + if (!_preparation.has_removed_orphaned_nodes || + !_preparation.has_synched_id_counts) + this->renumber_nodes_and_elements(); + } else { - this->remove_orphaned_nodes(); - this->update_parallel_id_counts(); + if (!_preparation.has_removed_orphaned_nodes) + this->remove_orphaned_nodes(); + if (!_preparation.has_synched_id_counts) + this->update_parallel_id_counts(); } // Let all the elements find their neighbors - if (!_skip_find_neighbors) + if (!_skip_find_neighbors && !_preparation.has_neighbor_ptrs) this->find_neighbors(); // The user may have set boundary conditions. We require that the @@ -892,11 +916,13 @@ void MeshBase::prepare_for_use () // Search the mesh for all the dimensions of the elements // and cache them. - this->cache_elem_data(); + if (!_preparation.has_cached_elem_data) + this->cache_elem_data(); // Search the mesh for elements that have a neighboring element // of dim+1 and set that element as the interior parent - this->detect_interior_parents(); + if (!_preparation.has_interior_parent_ptrs) + this->detect_interior_parents(); // Fix up node unique ids in case mesh generation code didn't take // exceptional care to do so. @@ -908,39 +934,49 @@ void MeshBase::prepare_for_use () MeshTools::libmesh_assert_valid_unique_ids(*this); #endif - // Reset our PointLocator. Any old locator is invalidated any time - // the elements in the underlying elements in the mesh have changed, - // so we clear it here. - this->clear_point_locator(); - // Allow our GhostingFunctor objects to reinit if necessary. // Do this before partitioning and redistributing, and before // deleting remote elements. - this->reinit_ghosting_functors(); + if (!_preparation.has_reinit_ghosting_functors) + this->reinit_ghosting_functors(); // Partition the mesh unless *all* partitioning is to be skipped. // If only noncritical partitioning is to be skipped, the // partition() call will still check for orphaned nodes. - if (!skip_partitioning()) + if (!skip_partitioning() && !_preparation.is_partitioned) this->partition(); + else if (!this->n_unpartitioned_elem() && + !this->n_unpartitioned_nodes()) + _preparation.is_partitioned = true; // If we're using DistributedMesh, we'll probably want it // parallelized. - if (this->_allow_remote_element_removal) + if (this->_allow_remote_element_removal && + !_preparation.has_removed_remote_elements) this->delete_remote_elements(); + else + _preparation.has_removed_remote_elements = true; // Much of our boundary info may have been for now-remote parts of the mesh, // in which case we don't want to keep local copies of data meant to be // local. On the other hand we may have deleted, or the user may have added in // a distributed fashion, boundary data that is meant to be global. So we // handle both of those scenarios here - this->get_boundary_info().regenerate_id_sets(); + if (!_preparation.has_boundary_id_sets) + this->get_boundary_info().regenerate_id_sets(); if (!_skip_renumber_nodes_and_elements) this->renumber_nodes_and_elements(); - // The mesh is now prepared for use. - _is_prepared = true; + // The mesh is now prepared for use, with the possible exception of + // partitioning that was supposed to be skipped, and it should know + // it. +#ifndef NDEBUG + Preparation completed_preparation = _preparation; + if (skip_partitioning()) + completed_preparation.is_partitioned = true; + libmesh_assert(completed_preparation); +#endif #ifdef DEBUG MeshTools::libmesh_assert_valid_boundary_ids(*this); @@ -958,6 +994,8 @@ MeshBase::reinit_ghosting_functors() libmesh_assert(gf); gf->mesh_reinit(); } + + _preparation.has_reinit_ghosting_functors = true; } void MeshBase::clear () @@ -965,8 +1003,8 @@ void MeshBase::clear () // Reset the number of partitions _n_parts = 1; - // Reset the _is_prepared flag - _is_prepared = false; + // Reset the preparation flags + _preparation = false; // Clear boundary information if (boundary_info) @@ -1683,6 +1721,8 @@ void MeshBase::partition (const unsigned int n_parts) // Make sure any other locally cached data is correct this->update_post_partitioning(); } + + _preparation.is_partitioned = true; } void MeshBase::all_second_order (const bool full_ordered) @@ -1886,6 +1926,8 @@ void MeshBase::cache_elem_data() #endif } } + + _preparation.has_cached_elem_data = true; } @@ -1903,10 +1945,16 @@ void MeshBase::detect_interior_parents() // This requires an inspection on every processor parallel_object_only(); + // This requires up-to-date mesh dimensions in cache + libmesh_assert(_preparation.has_cached_elem_data); + // Check if the mesh contains mixed dimensions. If so, then we may // have interior parents to set. Otherwise return. if (this->elem_dimensions().size() == 1) - return; + { + _preparation.has_interior_parent_ptrs = true; + return; + } // Do we have interior parent pointers going to a different mesh? // If so then we'll still check to make sure that's the only place @@ -2002,6 +2050,8 @@ void MeshBase::detect_interior_parents() ("interior_parent() values in multiple meshes are unsupported."); } } + + _preparation.has_interior_parent_ptrs = true; } diff --git a/src/mesh/mesh_modification.C b/src/mesh/mesh_modification.C index a7429475e5..4ee5f66911 100644 --- a/src/mesh/mesh_modification.C +++ b/src/mesh/mesh_modification.C @@ -215,6 +215,10 @@ void MeshTools::Modification::distort (MeshBase & mesh, #endif } } + + // We haven't changed any topology, but just changing geometry could + // have invalidated a point locator. + mesh.clear_point_locator(); } @@ -302,6 +306,10 @@ void MeshTools::Modification::redistribute (MeshBase & mesh, (*node)(2) = output_vec(2); #endif } + + // We haven't changed any topology, but just changing geometry could + // have invalidated a point locator. + mesh.clear_point_locator(); } @@ -315,6 +323,10 @@ void MeshTools::Modification::translate (MeshBase & mesh, for (auto & node : mesh.node_ptr_range()) *node += p; + + // We haven't changed any topology, but just changing geometry could + // have invalidated a point locator. + mesh.clear_point_locator(); } @@ -346,6 +358,10 @@ MeshTools::Modification::rotate (MeshBase & mesh, const Real theta, const Real psi) { + // We won't change any topology, but just changing geometry could + // invalidate a point locator. + mesh.clear_point_locator(); + #if LIBMESH_DIM == 3 const auto R = RealTensorValue::intrinsic_rotation_matrix(phi, theta, psi); @@ -402,6 +418,10 @@ void MeshTools::Modification::scale (MeshBase & mesh, for (auto & node : mesh.node_ptr_range()) (*node)(2) *= z_scale; + + // We haven't changed any topology, but just changing geometry could + // have invalidated a point locator. + mesh.clear_point_locator(); } @@ -1425,8 +1445,6 @@ void MeshTools::Modification::all_tri (MeshBase & mesh) MeshCommunication().make_nodes_parallel_consistent (mesh); } - - // Prepare the newly created mesh for use. mesh.prepare_for_use(); @@ -1585,6 +1603,10 @@ void MeshTools::Modification::smooth (MeshBase & mesh, } } // refinement_level loop } // end iteration + + // We haven't changed any topology, but just changing geometry could + // have invalidated a point locator. + mesh.clear_point_locator(); } @@ -1741,6 +1763,10 @@ void MeshTools::Modification::change_subdomain_id (MeshBase & mesh, if (elem->subdomain_id() == old_id) elem->subdomain_id() = new_id; } + + // We just invalidated mesh.get_subdomain_ids(), but it might not be + // efficient to fix that here. + mesh.unset_has_cached_elem_data(); } diff --git a/src/mesh/mesh_smoother_vsmoother.C b/src/mesh/mesh_smoother_vsmoother.C index 5cbc842996..5efb135c9e 100644 --- a/src/mesh/mesh_smoother_vsmoother.C +++ b/src/mesh/mesh_smoother_vsmoother.C @@ -88,6 +88,16 @@ void VariationalMeshSmoother::setup() // Create a new mesh, EquationSystems, and System _mesh_copy = std::make_unique(_mesh); + + // If the _mesh wasn't prepared, that's fine (we'll just be moving + // its nodes), but we do need the copy to be prepared before our + // solve does things like looking at neighbors. We'll disable + // repartitioning and renumbering first to make sure that we can + // transfer our geometry changes back to the original mesh. + _mesh_copy->allow_renumbering(false); + _mesh_copy->skip_partitioning(true); + _mesh_copy->complete_preparation(); + _equation_systems = std::make_unique(*_mesh_copy); _system = &(_equation_systems->add_system("variational_smoother_system")); diff --git a/src/mesh/mesh_tools.C b/src/mesh/mesh_tools.C index 7e98732dcb..7fdcf9406a 100644 --- a/src/mesh/mesh_tools.C +++ b/src/mesh/mesh_tools.C @@ -1227,7 +1227,7 @@ void clear_spline_nodes(MeshBase & mesh) bool valid_is_prepared (const MeshBase & mesh) { - LOG_SCOPE("libmesh_assert_valid_is_prepared()", "MeshTools"); + LOG_SCOPE("valid_is_prepared()", "MeshTools"); if (!mesh.is_prepared()) return true; diff --git a/src/mesh/replicated_mesh.C b/src/mesh/replicated_mesh.C index 059d3fcbe7..2e8a4cf48f 100644 --- a/src/mesh/replicated_mesh.C +++ b/src/mesh/replicated_mesh.C @@ -101,21 +101,17 @@ ReplicatedMesh::ReplicatedMesh (const MeshBase & other_mesh) : UnstructuredMesh (other_mesh), _n_nodes(0), _n_elem(0) // copy_* will increment this { - this->copy_nodes_and_elements(other_mesh, true); + // Just copy, skipping preparation + this->copy_nodes_and_elements(other_mesh, true, 0, 0, 0, nullptr, true); this->allow_find_neighbors(other_mesh.allow_find_neighbors()); this->allow_renumbering(other_mesh.allow_renumbering()); this->allow_remote_element_removal(other_mesh.allow_remote_element_removal()); this->skip_partitioning(other_mesh.skip_partitioning()); - // The prepare_for_use() in copy_nodes_and_elements() is going to be - // tricky to remove without breaking backwards compatibility, but it - // updates some things we want to just copy. - this->copy_cached_data(other_mesh); - this->copy_constraint_rows(other_mesh); - this->_is_prepared = other_mesh.is_prepared(); + this->_preparation = other_mesh.preparation(); auto & this_boundary_info = this->get_boundary_info(); const auto & other_boundary_info = other_mesh.get_boundary_info(); @@ -643,6 +639,8 @@ void ReplicatedMesh::update_parallel_id_counts() #ifdef LIBMESH_ENABLE_UNIQUE_ID _next_unique_id = this->parallel_max_unique_id(); #endif + + this->_preparation.has_synched_id_counts = true; } @@ -820,6 +818,8 @@ void ReplicatedMesh::renumber_nodes_and_elements () } } + this->_preparation.has_removed_orphaned_nodes = true; + libmesh_assert_equal_to (next_free_elem, _elements.size()); libmesh_assert_equal_to (next_free_node, _nodes.size()); diff --git a/src/mesh/unstructured_mesh.C b/src/mesh/unstructured_mesh.C index 935780b5dd..9463024124 100644 --- a/src/mesh/unstructured_mesh.C +++ b/src/mesh/unstructured_mesh.C @@ -650,10 +650,15 @@ void UnstructuredMesh::copy_nodes_and_elements(const MeshBase & other_mesh, #endif , std::unordered_map * - id_remapping) + id_remapping, + const bool skip_preparation) { LOG_SCOPE("copy_nodes_and_elements()", "UnstructuredMesh"); + // If we're asked to skip all preparation, we should be skipping + // find_neighbors specifically. + libmesh_assert(!skip_preparation || skip_find_neighbors); + std::pair, std::vector> extra_int_maps = this->merge_extra_integer_names(other_mesh); @@ -672,10 +677,12 @@ void UnstructuredMesh::copy_nodes_and_elements(const MeshBase & other_mesh, // We're assuming the other mesh has proper element number ordering, // so that we add parents before their children, and that the other - // mesh is consistently partitioned. + // mesh is consistently partitioned. We're not assuming that node + // proc ids are topologically consistent, so we don't just + // libmesh_assert_valid_procids. #ifdef DEBUG MeshTools::libmesh_assert_valid_amr_elem_ids(other_mesh); - MeshTools::libmesh_assert_valid_procids(other_mesh); + MeshTools::libmesh_assert_parallel_consistent_procids(other_mesh); #endif //Copy in Nodes @@ -867,39 +874,57 @@ void UnstructuredMesh::copy_nodes_and_elements(const MeshBase & other_mesh, this->set_next_unique_id(other_mesh.parallel_max_unique_id() + unique_id_offset + 1); #endif - // Finally, partially prepare the new Mesh for use. - // This is for backwards compatibility, so we don't want to prepare - // everything. - // - // Keep the same numbering and partitioning and distribution status - // for now, but save our original policies to restore later. - const bool allowed_renumbering = this->allow_renumbering(); - const bool allowed_find_neighbors = this->allow_find_neighbors(); - const bool allowed_elem_removal = this->allow_remote_element_removal(); - this->allow_renumbering(false); - this->allow_remote_element_removal(false); - this->allow_find_neighbors(!skip_find_neighbors); + // Finally, partially prepare the new Mesh for use, if that isn't + // being skipped. + // Even the default behavior here is for backwards compatibility, + // and we don't want to prepare everything. - // We should generally be able to skip *all* partitioning here - // because we're only adding one already-consistent mesh to another. - const bool skipped_partitioning = this->skip_partitioning(); - this->skip_partitioning(true); + if (!skip_preparation) + { + // Keep the same numbering and partitioning and distribution + // status for now, but save our original policies to restore + // later. + const bool allowed_renumbering = this->allow_renumbering(); + const bool allowed_find_neighbors = this->allow_find_neighbors(); + const bool allowed_elem_removal = this->allow_remote_element_removal(); + this->allow_renumbering(false); + this->allow_remote_element_removal(false); + this->allow_find_neighbors(!skip_find_neighbors); - const bool was_prepared = this->is_prepared(); - this->prepare_for_use(); + // We should generally be able to skip *all* partitioning here + // because we're only adding one already-consistent mesh to + // another. + const bool skipped_partitioning = this->skip_partitioning(); + this->skip_partitioning(true); - //But in the long term, don't change our policies. - this->allow_find_neighbors(allowed_find_neighbors); - this->allow_renumbering(allowed_renumbering); - this->allow_remote_element_removal(allowed_elem_removal); - this->skip_partitioning(skipped_partitioning); - - // That prepare_for_use() call marked us as prepared, but we - // specifically avoided some important preparation, so we might not - // actually be prepared now. - if (skip_find_neighbors || - !was_prepared || !other_mesh.is_prepared()) - this->set_isnt_prepared(); + const bool was_prepared = this->is_prepared(); + this->prepare_for_use(); + + //But in the long term, don't change our policies. + this->allow_find_neighbors(allowed_find_neighbors); + this->allow_renumbering(allowed_renumbering); + this->allow_remote_element_removal(allowed_elem_removal); + this->skip_partitioning(skipped_partitioning); + + // That prepare_for_use() call marked us as prepared, but we + // specifically avoided some important preparation, so we might not + // actually be prepared now. + if (skip_find_neighbors || + !was_prepared || !other_mesh.is_prepared()) + this->unset_is_prepared(); + } + + // In general we've just invalidated just about everything, and we'd + // like to unset_is_prepared(), but specific use cases might know a + // priori that they're still partitioned well, or that they've + // copied in a disjoint mesh component and don't need new neighbor + // pointers, or that they're not adding anything that would change + // cached subdomain/element/boundary sets, etc., so we'll rely on + // users of the "advanced" skip_preparation option to also set what + // preparation they still need. + + // else + // this->unset_is_prepared(); } @@ -1298,15 +1323,15 @@ void UnstructuredMesh::find_neighbors (const bool reset_remote_elements, libmesh_assert(current_elem->interior_parent()); } } - #endif // AMR - #ifdef DEBUG MeshTools::libmesh_assert_valid_neighbors(*this, !reset_remote_elements); MeshTools::libmesh_assert_valid_amr_interior_parents(*this); #endif + + this->_preparation.has_neighbor_ptrs = true; } diff --git a/tests/mesh/mesh_base_test.C b/tests/mesh/mesh_base_test.C index 3c4902017a..f97cb1cea4 100644 --- a/tests/mesh/mesh_base_test.C +++ b/tests/mesh/mesh_base_test.C @@ -22,6 +22,15 @@ public: /* Tests need a 2d mesh */ #if LIBMESH_DIM > 1 + CPPUNIT_TEST( testDistributedMeshVerifyHasNeighborPtrs ); + CPPUNIT_TEST( testMeshVerifyHasNeighborPtrs ); + CPPUNIT_TEST( testReplicatedMeshVerifyHasNeighborPtrs ); + CPPUNIT_TEST( testDistributedMeshVerifyHasCachedElemData ); + CPPUNIT_TEST( testMeshVerifyHasCachedElemData ); + CPPUNIT_TEST( testReplicatedMeshVerifyHasCachedElemData ); + CPPUNIT_TEST( testDistributedMeshVerifyRemovalPreparation ); + CPPUNIT_TEST( testMeshVerifyRemovalPreparation ); + CPPUNIT_TEST( testReplicatedMeshVerifyRemovalPreparation ); CPPUNIT_TEST( testDistributedMeshVerifyIsPrepared ); CPPUNIT_TEST( testMeshVerifyIsPrepared ); CPPUNIT_TEST( testReplicatedMeshVerifyIsPrepared ); @@ -35,7 +44,7 @@ public: void tearDown() {} - void testMeshBaseVerifyIsPrepared(UnstructuredMesh & mesh) + void BrokenNeighborMesh(UnstructuredMesh & mesh) { /** * Build a 2d 2x2 square mesh (mesh_one) covering [0.0, 1.0] x [0.0, 1.0] @@ -47,10 +56,6 @@ public: 0., 1., QUAD9); - // build_square does its own prepare_for_use(), so we should be - // prepared. - CPPUNIT_ASSERT(MeshTools::valid_is_prepared(mesh)); - // Break some neighbor links. Of course nobody would do this in // real life, right? Elem * elem0 = mesh.query_elem_ptr(0); @@ -66,6 +71,136 @@ public: elem0->set_neighbor(n, nullptr); neigh->set_neighbor(n_neigh, nullptr); } + } + + void testMeshBaseVerifyHasNeighborPtrs(UnstructuredMesh & mesh) + { + this->BrokenNeighborMesh(mesh); + mesh.unset_has_neighbor_ptrs(); + mesh.complete_preparation(); + CPPUNIT_ASSERT(mesh.is_prepared()); + CPPUNIT_ASSERT(MeshTools::valid_is_prepared(mesh)); + } + + void testDistributedMeshVerifyHasNeighborPtrs () + { + DistributedMesh mesh(*TestCommWorld); + testMeshBaseVerifyHasNeighborPtrs(mesh); + } + + void testMeshVerifyHasNeighborPtrs () + { + Mesh mesh(*TestCommWorld); + testMeshBaseVerifyHasNeighborPtrs(mesh); + } + + void testReplicatedMeshVerifyHasNeighborPtrs () + { + ReplicatedMesh mesh(*TestCommWorld); + testMeshBaseVerifyHasNeighborPtrs(mesh); + } + + void testMeshBaseVerifyHasCachedElemData(UnstructuredMesh & mesh) + { + /** + * Build a 2d 2x2 square mesh (mesh_one) covering [0.0, 1.0] x [0.0, 1.0] + * with linear Quad elements. + */ + MeshTools::Generation::build_square(mesh, + 2, 2, + 0., 1., + 0., 1., + QUAD9); + + // Invalidate the subdomain ids cache + Elem * elem0 = mesh.query_elem_ptr(0); + if (elem0) + elem0->subdomain_id() = 1; + + // We're unprepared (prepare_for_use() will update that cache) but + // we're not marked that way. + CPPUNIT_ASSERT(!MeshTools::valid_is_prepared(mesh)); + + mesh.unset_has_cached_elem_data(); + mesh.complete_preparation(); + CPPUNIT_ASSERT(mesh.is_prepared()); + CPPUNIT_ASSERT(MeshTools::valid_is_prepared(mesh)); + } + + void testDistributedMeshVerifyHasCachedElemData () + { + DistributedMesh mesh(*TestCommWorld); + testMeshBaseVerifyHasCachedElemData(mesh); + } + + void testMeshVerifyHasCachedElemData () + { + Mesh mesh(*TestCommWorld); + testMeshBaseVerifyHasCachedElemData(mesh); + } + + void testReplicatedMeshVerifyHasCachedElemData () + { + ReplicatedMesh mesh(*TestCommWorld); + testMeshBaseVerifyHasCachedElemData(mesh); + } + + void testMeshBaseVerifyRemovalPreparation(UnstructuredMesh & mesh) + { + /** + * Build a 2d 2x2 square mesh (mesh_one) covering [0.0, 1.0] x [0.0, 1.0] + * with linear Quad elements. + */ + MeshTools::Generation::build_square(mesh, + 2, 2, + 0., 1., + 0., 1., + QUAD9); + + // Remove elements on one side, orphaning 4 nodes and removing one + // boundary condition. Remove dangling neighbor pointers too; we + // can't even clone a mesh with dangling pointers. + for (auto & elem : mesh.element_ptr_range()) + if (elem->vertex_average()(0) > 0.5) + mesh.delete_elem(elem); + else + elem->set_neighbor(1, nullptr); + + // We're unprepared (prepare_for_use() will remove those orphaned + // nodes and fix the boundary id sets and fix the partitioning of + // nodes that might need new owners) but we're not marked that + // way. + CPPUNIT_ASSERT(!MeshTools::valid_is_prepared(mesh)); + + mesh.unset_is_partitioned(); + mesh.unset_has_removed_orphaned_nodes(); + mesh.unset_has_boundary_id_sets(); + mesh.complete_preparation(); + CPPUNIT_ASSERT(mesh.is_prepared()); + CPPUNIT_ASSERT(MeshTools::valid_is_prepared(mesh)); + } + + void testDistributedMeshVerifyRemovalPreparation () + { + DistributedMesh mesh(*TestCommWorld); + testMeshBaseVerifyRemovalPreparation(mesh); + } + + void testMeshVerifyRemovalPreparation () + { + Mesh mesh(*TestCommWorld); + testMeshBaseVerifyRemovalPreparation(mesh); + } + + void testReplicatedMeshVerifyRemovalPreparation () + { + ReplicatedMesh mesh(*TestCommWorld); + testMeshBaseVerifyRemovalPreparation(mesh); + } + + void testMeshBaseVerifyIsPrepared(UnstructuredMesh & mesh) + { + this->BrokenNeighborMesh(mesh); // We're unprepared (prepare_for_use() will restitch those // neighbor pointers) but we're not marked that way.