diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index a74711ca..5b0a90ac 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -122,6 +122,7 @@ if ( BUILD_REDECOMP AND TRIBOL_USE_MPI ) tribol_mfem_common_plane.cpp tribol_mfem_mortar_lm.cpp tribol_proximity_check.cpp + tribol_redecomp_tol.cpp ) set(combined_test_depends tribol gtest) diff --git a/src/tests/tribol_redecomp_tol.cpp b/src/tests/tribol_redecomp_tol.cpp new file mode 100644 index 00000000..93a987c5 --- /dev/null +++ b/src/tests/tribol_redecomp_tol.cpp @@ -0,0 +1,139 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Tribol Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (MIT) + +#include + +#include + +// Tribol includes +#include "tribol/config.hpp" +#include "tribol/common/Parameters.hpp" +#include "tribol/interface/tribol.hpp" +#include "tribol/interface/mfem_tribol.hpp" +#include "tribol/mesh/CouplingScheme.hpp" +#include "tribol/mesh/MfemData.hpp" + +// Shared includes +#include "shared/mesh/MeshBuilder.hpp" + +#ifdef TRIBOL_USE_UMPIRE +// Umpire includes +#include "umpire/ResourceManager.hpp" +#endif + +// MFEM includes +#include "mfem.hpp" + +/** + * @brief This tests the Tribol MFEM interface's ability to skip expensive RedecompMesh + * recreation when nodal displacement change is below a certain threshold. + */ +class MfemRedecompSkipTest : public testing::Test { + protected: + void SetUp() override {} +}; + +TEST_F( MfemRedecompSkipTest, test_skip_redecomp ) +{ + tribol::ExecutionMode exec_mode = tribol::ExecutionMode::Sequential; + + // 1. Initialize simplified problem: Two unit cubes separated by a small gap + // Mesh is refined once, so element size is 0.5. + // clang-format off + double initial_sep = 0.1; + mfem::ParMesh mesh = shared::ParMeshBuilder( MPI_COMM_WORLD, shared::MeshBuilder::Unify( { + shared::MeshBuilder::CubeMesh( 1, 1, 1 ), + shared::MeshBuilder::CubeMesh( 1, 1, 1 ) + .translate( { 0.0, 0.0, 1.0 + initial_sep } ) + .updateAttrib( 1, 2 ) + .updateBdrAttrib( 1, 7 ) // Bottom of top cube -> 7 + .updateBdrAttrib( 6, 8 ) // set the top surface to boundary attribute 8 (prevent multiple 6) + } ).refine( 1 ) ); + // clang-format on + + auto fe_coll = mfem::H1_FECollection( 1, mesh.SpaceDimension() ); + auto par_fe_space = mfem::ParFiniteElementSpace( &mesh, &fe_coll, mesh.SpaceDimension() ); + auto coords = mfem::ParGridFunction( &par_fe_space ); + mesh.GetNodes( coords ); + + int coupling_scheme_id = 0; + // Top of bottom cube: 6, Bottom of top cube: 7 + tribol::registerMfemCouplingScheme( coupling_scheme_id, 0, 1, mesh, coords, { 6 }, { 7 }, tribol::SURFACE_TO_SURFACE, + tribol::NO_CASE, tribol::COMMON_PLANE, tribol::FRICTIONLESS, tribol::PENALTY, + tribol::BINNING_BVH, exec_mode ); + + // Set threshold explicitly to 0.2 + tribol::setMfemRedecompTriggerDisplacement( coupling_scheme_id, 0.2 ); + tribol::setMfemKinematicConstantPenalty( coupling_scheme_id, 1.0e5, 1.0e5 ); + + // 2. Initial redecomp + tribol::updateMfemParallelDecomposition(); + auto* mfem_data = tribol::CouplingSchemeManager::getInstance().at( coupling_scheme_id ).getMfemMeshData(); + const mfem::Mesh* redecomp_mesh_ptr_1 = &mfem_data->GetRedecompMesh(); + + // Verify we can compute forces (should be zero as gap > 0) + double dt = 0.1; + tribol::update( 0, 0.0, dt ); + mfem::Vector force_1( coords.Size() ); + force_1 = 0.0; + tribol::getMfemResponse( coupling_scheme_id, force_1 ); + + // 3. Small shift (0.1 < 0.2) + // Shift ALL nodes by 0.1. This preserves relative gap, so forces should remain same (zero). + coords += 0.1; + + tribol::updateMfemParallelDecomposition(); + const mfem::Mesh* redecomp_mesh_ptr_2 = &mfem_data->GetRedecompMesh(); + + // Expect no rebuild + EXPECT_EQ( redecomp_mesh_ptr_1, redecomp_mesh_ptr_2 ); + + tribol::update( 1, 0.1, dt ); + mfem::Vector force_2( coords.Size() ); + force_2 = 0.0; + tribol::getMfemResponse( coupling_scheme_id, force_2 ); + // Forces should still be zero (invariant) + EXPECT_LT( force_2.Norml2(), 1.0e-10 ); + + // 4. Large shift (accumulated 0.3 > 0.2) + // Shift ALL nodes by another 0.2 (total 0.3) + coords += 0.2; + + tribol::updateMfemParallelDecomposition(); + const mfem::Mesh* redecomp_mesh_ptr_3 = &mfem_data->GetRedecompMesh(); + + // Expect rebuild + EXPECT_NE( redecomp_mesh_ptr_1, redecomp_mesh_ptr_3 ); + + tribol::update( 2, 0.2, dt ); + mfem::Vector force_3( coords.Size() ); + force_3 = 0.0; + tribol::getMfemResponse( coupling_scheme_id, force_3 ); + // Forces should still be zero (invariant) + EXPECT_LT( force_3.Norml2(), 1.0e-10 ); + + tribol::finalize(); +} + +int main( int argc, char* argv[] ) +{ + int result = 0; + + MPI_Init( &argc, &argv ); + + ::testing::InitGoogleTest( &argc, argv ); + +#ifdef TRIBOL_USE_UMPIRE + umpire::ResourceManager::getInstance(); // initialize umpire's ResouceManager +#endif + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + MPI_Finalize(); + + return result; +} diff --git a/src/tribol/common/BasicTypes.hpp b/src/tribol/common/BasicTypes.hpp index 49617b31..f8927524 100644 --- a/src/tribol/common/BasicTypes.hpp +++ b/src/tribol/common/BasicTypes.hpp @@ -12,6 +12,9 @@ // C includes #include +// C++ includes +#include + // MPI includes #ifdef TRIBOL_USE_MPI #include @@ -20,6 +23,9 @@ // Axom includes #include "axom/core/Types.hpp" +// MFEM includes +#include "mfem.hpp" + namespace tribol { #ifdef TRIBOL_USE_MPI @@ -53,6 +59,9 @@ using RealT = double; #endif +// mfem's real_t should match ours +static_assert( std::is_same_v, "tribol::RealT and mfem::real_t are required to match" ); + #define TRIBOL_UNUSED_VAR AXOM_UNUSED_VAR #define TRIBOL_UNUSED_PARAM AXOM_UNUSED_PARAM diff --git a/src/tribol/interface/mfem_tribol.cpp b/src/tribol/interface/mfem_tribol.cpp index f06f70f1..e8d0a842 100644 --- a/src/tribol/interface/mfem_tribol.cpp +++ b/src/tribol/interface/mfem_tribol.cpp @@ -119,6 +119,20 @@ void setMfemLORFactor( IndexT cs_id, int lor_factor ) cs->getMfemMeshData()->SetLORFactor( lor_factor ); } +void setMfemRedecompTriggerDisplacement( IndexT cs_id, RealT val ) +{ + auto cs = CouplingSchemeManager::getInstance().findData( cs_id ); + SLIC_ERROR_ROOT_IF( + !cs, axom::fmt::format( "Coupling scheme cs_id={0} does not exist. Call tribol::registerMfemCouplingScheme() " + "to create a coupling scheme with this cs_id.", + cs_id ) ); + SLIC_ERROR_ROOT_IF( + !cs->hasMfemData(), + "Coupling scheme does not contain MFEM data. " + "Create the coupling scheme using registerMfemCouplingScheme() to set the trigger displacement." ); + cs->getMfemMeshData()->SetRedecompTriggerDisplacement( val ); +} + void setMfemKinematicConstantPenalty( IndexT cs_id, RealT mesh1_penalty, RealT mesh2_penalty ) { auto cs = CouplingSchemeManager::getInstance().findData( cs_id ); @@ -369,7 +383,7 @@ mfem::ParGridFunction& getMfemPressure( IndexT cs_id ) return cs->getMfemSubmeshData()->GetSubmeshPressure(); } -void updateMfemParallelDecomposition( int n_ranks ) +void updateMfemParallelDecomposition( int n_ranks, bool force_new_redecomp ) { for ( auto& cs_pair : CouplingSchemeManager::getInstance() ) { auto& cs = cs_pair.second; @@ -386,9 +400,9 @@ void updateMfemParallelDecomposition( int n_ranks ) if ( mfem_data->GetLORFactor() > 1 ) { effective_binning_proximity *= static_cast( mfem_data->GetLORFactor() ); } - // creates a new redecomp mesh based on updated coordinates and updates transfer operators and displacement, - // velocity, and response grid functions based on new redecomp mesh - mfem_data->UpdateMfemMeshData( effective_binning_proximity, n_ranks ); + // creates a new redecomp mesh based on updated coordinates (if criteria is met) and updates transfer operators + // and displacement, velocity, and response grid functions based on new redecomp mesh + auto new_redecomp = mfem_data->UpdateMfemMeshData( effective_binning_proximity, n_ranks, force_new_redecomp ); auto coord_ptrs = mfem_data->GetRedecompCoordsPtrs(); registerMesh( mesh_ids[0], mfem_data->GetMesh1NE(), mfem_data->GetNV(), mfem_data->GetMesh1Conn(), @@ -417,12 +431,12 @@ void updateMfemParallelDecomposition( int n_ranks ) auto submesh_data = cs.getMfemSubmeshData(); // updates submesh-native grid functions and transfer operators on // the new redecomp mesh - submesh_data->UpdateMfemSubmeshData( mfem_data->GetRedecompMesh() ); + submesh_data->UpdateMfemSubmeshData( mfem_data->GetRedecompMesh(), new_redecomp ); auto g_ptrs = submesh_data->GetRedecompGapPtrs(); registerMortarGaps( mesh_ids[1], g_ptrs[0] ); auto p_ptrs = submesh_data->GetRedecompPressurePtrs(); registerMortarPressures( mesh_ids[1], p_ptrs[0] ); - if ( cs.hasMfemJacobianData() ) { + if ( cs.hasMfemJacobianData() && new_redecomp ) { // updates Jacobian transfer operator for new redecomp mesh cs.getMfemJacobianData()->UpdateJacobianXfer(); } diff --git a/src/tribol/interface/mfem_tribol.hpp b/src/tribol/interface/mfem_tribol.hpp index 35388270..2a142962 100644 --- a/src/tribol/interface/mfem_tribol.hpp +++ b/src/tribol/interface/mfem_tribol.hpp @@ -85,6 +85,16 @@ void registerMfemCouplingScheme( IndexT cs_id, int mesh_id_1, int mesh_id_2, con */ void setMfemLORFactor( IndexT cs_id, int lor_factor ); +/** + * @brief Sets the displacement threshold for triggering a new parallel decomposition + * + * @pre Coupling scheme cs_id must be registered using registerMfemCouplingScheme() + * + * @param [in] cs_id The ID of the coupling scheme + * @param [in] val Threshold value + */ +void setMfemRedecompTriggerDisplacement( IndexT cs_id, RealT val ); + /** * @brief Clears existing penalty data and sets kinematic constant penalty * @@ -289,10 +299,12 @@ mfem::ParGridFunction& getMfemPressure( IndexT cs_id ); * @brief Updates mesh parallel decomposition and related grid functions/Jacobian when coordinates are updated * * @param n_ranks Number of ranks in the parallel decomposition; automatically determine when set to 0 (default) + * @param force_new_redecomp If true, construct a new RedecompMesh even if displacement threshold is not met (default = + * false) * * @pre Coupling schemes must be registered using registerMfemCouplingScheme() */ -void updateMfemParallelDecomposition( int n_ranks = 0 ); +void updateMfemParallelDecomposition( int n_ranks = 0, bool force_new_redecomp = false ); /** * @brief Create VisIt output of the parallel repartitioned RedecompMesh diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 96e64044..47671456 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -326,6 +326,10 @@ MfemMeshData::MfemMeshData( IndexT mesh_id_1, IndexT mesh_id_2, const mfem::ParM if ( current_coords.FESpace()->FEColl()->GetOrder() > 1 ) { SetLORFactor( current_coords.FESpace()->FEColl()->GetOrder() ); } + + // set default redecomp trigger displacement + auto mpi = redecomp::MPIUtility( parent_mesh_.GetComm() ); + redecomp_trigger_displacement_ = redecomp::RedecompMesh::MaxElementSize( parent_mesh_, mpi ); } void MfemMeshData::SetParentCoords( const mfem::ParGridFunction& current_coords ) @@ -342,35 +346,82 @@ void MfemMeshData::SetParentReferenceCoords( const mfem::ParGridFunction& refere } } -void MfemMeshData::UpdateMfemMeshData( RealT binning_proximity_scale, int n_ranks ) +bool MfemMeshData::UpdateMfemMeshData( RealT binning_proximity_scale, int n_ranks, bool force_new_redecomp ) { TRIBOL_MARK_FUNCTION; - // update coordinates of submesh and LOR mesh - auto submesh_nodes = dynamic_cast( submesh_.GetNodes() ); - SLIC_ERROR_ROOT_IF( !submesh_nodes, "submesh_ Nodes is not a ParGridFunction." ); - TRIBOL_MARK_BEGIN( "Update SubMesh coords" ); - submesh_.Transfer( coords_.GetParentGridFn(), *submesh_nodes ); - TRIBOL_MARK_END( "Update SubMesh coords" ); - if ( lor_mesh_.get() ) { - TRIBOL_MARK_BEGIN( "Update LOR coords" ); - auto lor_nodes = dynamic_cast( lor_mesh_->GetNodes() ); - SLIC_ERROR_ROOT_IF( !lor_nodes, "lor_mesh_ Nodes is not a ParGridFunction." ); - submesh_lor_xfer_->SubmeshToLOR( *submesh_nodes, *lor_nodes ); - TRIBOL_MARK_END( "Update LOR coords" ); + + // check if redecomp mesh needs to be updated + TRIBOL_MARK_BEGIN( "Check if new Redecomp mesh is needed" ); + if ( !force_new_redecomp && update_data_ ) { + // compute max displacement change + auto& current_coords_gf = coords_.GetParentGridFn(); + // Use inf-norm of coordinate differences as a proxy for max displacement change. + const RealT* d_curr = current_coords_gf.Read(); + const RealT* d_last = coords_at_last_redecomp_.Read(); + mfem::Vector max_diff( 1 ); + max_diff.UseDevice( use_device_ ); + max_diff = 0.0; + RealT* d_max_diff = max_diff.Write(); + forAllExec( exec_mode_, current_coords_gf.Size(), [d_curr, d_last, d_max_diff] TRIBOL_HOST_DEVICE( int i ) { +#ifdef TRIBOL_USE_RAJA + RAJA::atomicMax( d_max_diff, std::abs( d_curr[i] - d_last[i] ) ); +#else + d_max_diff[0] = std::max( d_max_diff[0], std::abs( d_curr[i] - d_last[i] ) ); +#endif + } ); + RealT* h_max_diff = max_diff.HostReadWrite(); + // Allreduce to get global max + MPI_Allreduce( MPI_IN_PLACE, h_max_diff, 1, MPI_DOUBLE, MPI_MAX, parent_mesh_.GetComm() ); + + // If max change is greater than threshold, make a new RedecompMesh + // NOTE: max_diff is the max component diff, i.e. x, y, z components are considered separately + if ( *h_max_diff > redecomp_trigger_displacement_ ) { + force_new_redecomp = true; + } } + TRIBOL_MARK_END( "Check if new Redecomp mesh is needed" ); + TRIBOL_MARK_BEGIN( "Build new Redecomp mesh" ); - update_data_ = std::make_unique( submesh_, lor_mesh_.get(), *coords_.GetParentGridFn().ParFESpace(), - submesh_xfer_gridfn_, submesh_lor_xfer_.get(), attributes_1_, - attributes_2_, binning_proximity_scale, allocator_id_, n_ranks ); + bool rebuilt = false; + if ( force_new_redecomp || !update_data_ ) { + // update coordinates of submesh and LOR mesh + auto submesh_nodes = dynamic_cast( submesh_.GetNodes() ); + SLIC_ERROR_ROOT_IF( !submesh_nodes, "submesh_ Nodes is not a ParGridFunction." ); + TRIBOL_MARK_BEGIN( "Update SubMesh coords" ); + submesh_.Transfer( coords_.GetParentGridFn(), *submesh_nodes ); + TRIBOL_MARK_END( "Update SubMesh coords" ); + if ( lor_mesh_.get() ) { + TRIBOL_MARK_BEGIN( "Update LOR coords" ); + auto lor_nodes = dynamic_cast( lor_mesh_->GetNodes() ); + SLIC_ERROR_ROOT_IF( !lor_nodes, "lor_mesh_ Nodes is not a ParGridFunction." ); + submesh_lor_xfer_->SubmeshToLOR( *submesh_nodes, *lor_nodes ); + TRIBOL_MARK_END( "Update LOR coords" ); + } + update_data_ = + std::make_unique( submesh_, lor_mesh_.get(), *coords_.GetParentGridFn().ParFESpace(), + submesh_xfer_gridfn_, submesh_lor_xfer_.get(), attributes_1_, attributes_2_, + binning_proximity_scale, n_ranks, allocator_id_, redecomp_trigger_displacement_ ); + rebuilt = true; + } + + // this is done here so the redecomp grid fn is updated before we update redecomp_response_ + coords_.UpdateField( update_data_->vector_xfer_, use_device_ ); + + if ( rebuilt ) { + // NOTE: SetSpace() would be preferrable to call here, but it looks like all memory isn't mapped to + // mfem::MemoryManager when this is used. TODO: Debug this and switch to SetSpace() + redecomp_response_ = std::make_unique( coords_.GetRedecompGridFn().FESpace() ); + redecomp_response_->UseDevice( use_device_ ); + + // Store current coordinates + coords_at_last_redecomp_.SetSize( coords_.GetParentGridFn().Size() ); + coords_at_last_redecomp_ = coords_.GetParentGridFn(); + } TRIBOL_MARK_END( "Build new Redecomp mesh" ); TRIBOL_MARK_BEGIN( "Copy fields to Redecomp mesh" ); - coords_.UpdateField( update_data_->vector_xfer_, use_device_ ); - // NOTE: SetSpace() would be preferrable to call here, but it looks like all memory isn't mapped to - // mfem::MemoryManager when this is used. TODO: Debug this and switch to SetSpace() - redecomp_response_ = std::make_unique( coords_.GetRedecompGridFn().FESpace() ); - redecomp_response_->UseDevice( use_device_ ); ( *redecomp_response_ ) = 0.0; + if ( reference_coords_ ) { reference_coords_->UpdateField( update_data_->vector_xfer_, use_device_ ); } @@ -378,7 +429,8 @@ void MfemMeshData::UpdateMfemMeshData( RealT binning_proximity_scale, int n_rank velocity_->UpdateField( update_data_->vector_xfer_, use_device_ ); } TRIBOL_MARK_END( "Copy fields to Redecomp mesh" ); - if ( elem_thickness_ ) { + + if ( rebuilt && elem_thickness_ ) { if ( !material_modulus_ ) { SLIC_ERROR_ROOT( "Kinematic element penalty requires material modulus information. " @@ -440,6 +492,8 @@ void MfemMeshData::UpdateMfemMeshData( RealT binning_proximity_scale, int n_rank } ); TRIBOL_MARK_END( "Copy element thickness to Redecomp mesh" ); } + + return rebuilt; } void MfemMeshData::GetParentResponse( mfem::Vector& r ) const @@ -587,17 +641,20 @@ MfemMeshData::UpdateData::UpdateData( mfem::ParSubMesh& submesh, mfem::ParMesh* const mfem::ParFiniteElementSpace& parent_fes, mfem::ParGridFunction& submesh_gridfn, SubmeshLORTransfer* submesh_lor_xfer, const std::set& attributes_1, const std::set& attributes_2, - RealT binning_proximity_scale, int allocator_id, int n_ranks ) + RealT binning_proximity_scale, int n_ranks, int allocator_id, + RealT redecomp_trigger_displacement ) : redecomp_mesh_{ lor_mesh ? redecomp::RedecompMesh( *lor_mesh, binning_proximity_scale * redecomp::RedecompMesh::MaxElementSize( - *lor_mesh, redecomp::MPIUtility( lor_mesh->GetComm() ) ), + *lor_mesh, redecomp::MPIUtility( lor_mesh->GetComm() ) ) + + redecomp_trigger_displacement, redecomp::RedecompMesh::RCB, n_ranks ) : redecomp::RedecompMesh( submesh, binning_proximity_scale * redecomp::RedecompMesh::MaxElementSize( - submesh, redecomp::MPIUtility( submesh.GetComm() ) ), + submesh, redecomp::MPIUtility( submesh.GetComm() ) ) + + redecomp_trigger_displacement, redecomp::RedecompMesh::RCB, n_ranks ) }, vector_xfer_{ parent_fes, submesh_gridfn, submesh_lor_xfer, redecomp_mesh_ }, allocator_id_{ allocator_id } @@ -737,10 +794,12 @@ MfemSubmeshData::MfemSubmeshData( mfem::ParSubMesh& submesh, mfem::ParMesh* lor_ submesh_pressure_ = 0.0; } -void MfemSubmeshData::UpdateMfemSubmeshData( redecomp::RedecompMesh& redecomp_mesh ) +void MfemSubmeshData::UpdateMfemSubmeshData( redecomp::RedecompMesh& redecomp_mesh, bool new_redecomp ) { - update_data_ = - std::make_unique( *submesh_pressure_.ParFESpace(), submesh_lor_xfer_.get(), redecomp_mesh ); + if ( new_redecomp || !update_data_ ) { + update_data_ = + std::make_unique( *submesh_pressure_.ParFESpace(), submesh_lor_xfer_.get(), redecomp_mesh ); + } pressure_.UpdateField( update_data_->pressure_xfer_ ); redecomp_gap_.SetSpace( pressure_.GetRedecompGridFn().FESpace() ); redecomp_gap_ = 0.0; diff --git a/src/tribol/mesh/MfemData.hpp b/src/tribol/mesh/MfemData.hpp index 6ac22dfc..de2846d8 100644 --- a/src/tribol/mesh/MfemData.hpp +++ b/src/tribol/mesh/MfemData.hpp @@ -637,10 +637,13 @@ class MfemMeshData { * @param binning_proximity_scale Element length multiplier for coarse binning and proximity detection inclusion. This * is needed to size the ghost element layer in the redecomp mesh. * @param n_ranks Number of ranks in the parallel decomposition + * @param force_new_redecomp If true, construct a new RedecompMesh even if displacement threshold is not met (default + * = false) + * @return True if a new RedecompMesh is created by this method * * @note This method should be called after the coordinate grid function is updated. */ - void UpdateMfemMeshData( RealT binning_proximity_scale, int n_ranks ); + bool UpdateMfemMeshData( RealT binning_proximity_scale, int n_ranks, bool force_new_redecomp = false ); /** * @brief Get the integer identifier for the first Tribol registered mesh @@ -1087,6 +1090,13 @@ class MfemMeshData { */ MemorySpace GetMemorySpace() const { return mem_space_; } + /** + * @brief Sets the displacement threshold for triggering a new parallel decomposition + * + * @param val Threshold value + */ + void SetRedecompTriggerDisplacement( RealT val ) { redecomp_trigger_displacement_ = val; } + private: /** * @brief Creates and stores data that changes when the RedecompMesh is updated @@ -1107,11 +1117,13 @@ class MfemMeshData { * This is needed to size the ghost element layer in the redecomp mesh. * @param n_ranks Number of ranks in the parallel decomposition * @param allocator_id Allocation space ID for Tribol memory + * @param redecomp_trigger_displacement Additional length to add to redecomp ghost length equal to the + * displacement required to trigger a new RedecompMesh to be built. */ UpdateData( mfem::ParSubMesh& submesh, mfem::ParMesh* lor_mesh, const mfem::ParFiniteElementSpace& parent_fes, mfem::ParGridFunction& submesh_gridfn, SubmeshLORTransfer* submesh_lor_xfer, const std::set& attributes_1, const std::set& attributes_2, RealT binning_proximity_scale, - int n_ranks, int allocator_id ); + int n_ranks, int allocator_id, RealT redecomp_trigger_displacement ); /** * @brief Redecomposed boundary element mesh @@ -1401,6 +1413,16 @@ class MfemMeshData { */ int allocator_id_; + /** + * @brief Threshold for max nodal displacement change to trigger a new parallel decomposition + */ + RealT redecomp_trigger_displacement_; + + /** + * @brief Parent coordinate values at the time of the last parallel decomposition + */ + mutable mfem::Vector coords_at_last_redecomp_; + /** * @brief Merges two STL containers * @@ -1459,8 +1481,9 @@ class MfemSubmeshData { * functions * * @param redecomp_mesh Updated redecomp mesh + * @param new_redecomp If true, construct an updated SubmeshRedecompTransfer object for new RedecompMesh */ - void UpdateMfemSubmeshData( redecomp::RedecompMesh& redecomp_mesh ); + void UpdateMfemSubmeshData( redecomp::RedecompMesh& redecomp_mesh, bool new_redecomp = true ); /** * @brief Get pointers to component arrays of the pressure on the redecomp