Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ set( tribol_tests
tribol_mortar_wts.cpp
tribol_nodal_nrmls.cpp
tribol_quad_integ.cpp
tribol_residual_gap.cpp
tribol_tet_mesh.cpp
tribol_timestep_vote.cpp
tribol_twb_integ.cpp
Expand Down
37 changes: 37 additions & 0 deletions src/tests/tribol_enzyme_element_mortar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,43 @@ TEST_F( EnzymeElementMortarTest, NoOverlap )
FDCheck( x1, x2, n1, p1, x1_stencil, x2_stencil );
}

TEST_F( EnzymeElementMortarTest, ExactOverlapResidualGap )
{
// clang-format off
// Setup two overlapping elements with a gap of 0.1
double x1[12] = { 0.0, 1.0, 1.0, 0.0,
0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 0.0, 0.0 };
// Define x2 in CW order so ElemReverse makes it CCW
double x2[12] = { 0.0, 0.0, 1.0, 1.0,
0.0, 1.0, 1.0, 0.0,
0.1, 0.1, 0.1, 0.1 };
double n1[12] = { 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
1.0, 1.0, 1.0, 1.0 };
double p1[4] = { 1.0, 1.0, 1.0, 1.0 };
// clang-format on

double residual_gap = 0.15;

double f1[12], f2[12], g1[4];
int num_nodes = 4;

// Call with residual_gap
tribol::ComputeMortarForceEnzyme( x1, n1, p1, f1, g1, num_nodes, x2, f2, num_nodes, residual_gap );

// Kinematic gap is 0.1. Effective gap is 0.1 - 0.15 = -0.05.
// Integrated gap for unit square (overlap area 1.0) distributed to 4 nodes:
// Each node i has integrated gap G_i = \int \phi_i (g_kin - g_r) dA.
// For constant g_kin and g_r: G_i = (g_kin - g_r) \int \phi_i dA.
// \int \phi_i dA for bilinear quad node is 0.25 (1/4 of element area).
// So G_i = -0.05 * 0.25 = -0.0125.

for ( int i = 0; i < 4; ++i ) {
EXPECT_NEAR( g1[i], -0.0125, 1e-12 );
}
}

} // namespace tribol

//------------------------------------------------------------------------------
Expand Down
14 changes: 10 additions & 4 deletions src/tests/tribol_mfem_mortar_lm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* @brief This tests the Tribol MFEM interface running a contact patch test.
*
*/
class MfemMortarTest : public testing::TestWithParam<std::tuple<int, mfem::Element::Type>> {
class MfemMortarTest : public testing::TestWithParam<std::tuple<int, mfem::Element::Type, double>> {
protected:
tribol::RealT max_disp_;
void SetUp() override
Expand All @@ -46,6 +46,8 @@ class MfemMortarTest : public testing::TestWithParam<std::tuple<int, mfem::Eleme
// polynomial order of the finite element discretization
int order = 1;

double residual_gap = std::get<2>( GetParam() );

// fixed options
// boundary element attributes of mortar surface
auto mortar_attrs = std::set<int>( { 4 } );
Expand Down Expand Up @@ -145,6 +147,7 @@ class MfemMortarTest : public testing::TestWithParam<std::tuple<int, mfem::Eleme
tribol::SINGLE_MORTAR, tribol::FRICTIONLESS, tribol::LAGRANGE_MULTIPLIER,
tribol::BINNING_GRID );
tribol::setLagrangeMultiplierOptions( 0, tribol::ImplicitEvalMode::MORTAR_RESIDUAL_JACOBIAN );
tribol::setResidualGap( coupling_scheme_id, residual_gap );

coords.ReadWrite();
// update tribol (compute contact contribution to force and stiffness)
Expand Down Expand Up @@ -221,14 +224,17 @@ class MfemMortarTest : public testing::TestWithParam<std::tuple<int, mfem::Eleme

TEST_P( MfemMortarTest, check_mortar_displacement )
{
EXPECT_LT( std::abs( max_disp_ - 0.005 ), 1.0e-6 );
double residual_gap = std::get<2>( GetParam() );
EXPECT_LT( std::abs( max_disp_ - ( 0.005 + residual_gap / 2.0 ) ), 1.0e-6 );

MPI_Barrier( MPI_COMM_WORLD );
}

INSTANTIATE_TEST_SUITE_P( tribol, MfemMortarTest,
testing::Values( std::make_tuple( 2, mfem::Element::Type::HEXAHEDRON ),
std::make_tuple( 2, mfem::Element::Type::TETRAHEDRON ) ) );
testing::Values( std::make_tuple( 2, mfem::Element::Type::HEXAHEDRON, 0.0 ),
std::make_tuple( 2, mfem::Element::Type::HEXAHEDRON, 0.01 ),
std::make_tuple( 2, mfem::Element::Type::TETRAHEDRON, 0.0 ),
std::make_tuple( 2, mfem::Element::Type::TETRAHEDRON, 0.01 ) ) );

//------------------------------------------------------------------------------
#include "axom/slic/core/SimpleLogger.hpp"
Expand Down
179 changes: 179 additions & 0 deletions src/tests/tribol_residual_gap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// 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)

// gtest includes
#include "gtest/gtest.h"

// Tribol includes
#include "tribol/interface/tribol.hpp"
#include "tribol/common/Parameters.hpp"
#include "tribol/mesh/CouplingScheme.hpp"

using RealT = tribol::RealT;

class ResidualGapTest : public ::testing::Test {
protected:
void SetUp() override {}
void TearDown() override { tribol::finalize(); }
};

TEST_F( ResidualGapTest, common_plane_residual_gap )
{
// Setup two 3D quads with a 0.1 gap
// Quad 1: z = 0, x in [0, 1], y in [0, 1]
// Quad 2: z = 0.1, x in [0, 1], y in [0, 1]

constexpr int numVerts = 4;
constexpr int numElems = 1;
RealT x1[numVerts] = { 0.0, 1.0, 1.0, 0.0 };
RealT y1[numVerts] = { 0.0, 0.0, 1.0, 1.0 };
RealT z1[numVerts] = { 0.0, 0.0, 0.0, 0.0 };

RealT x2[numVerts] = { 0.0, 1.0, 1.0, 0.0 };
RealT y2[numVerts] = { 0.0, 0.0, 1.0, 1.0 };
RealT z2[numVerts] = { 0.1, 0.1, 0.1, 0.1 };

// Connectivity for 3D quads
tribol::IndexT conn1[numVerts] = { 0, 1, 2, 3 };
// Quad 2 normal points -z: 0-3-2-1 is CCW viewed from -z.
tribol::IndexT conn2[numVerts] = { 0, 3, 2, 1 };

tribol::registerMesh( 0, numElems, numVerts, &conn1[0], static_cast<int>( tribol::LINEAR_QUAD ), &x1[0], &y1[0],
&z1[0], tribol::MemorySpace::Host );
tribol::registerMesh( 1, numElems, numVerts, &conn2[0], static_cast<int>( tribol::LINEAR_QUAD ), &x2[0], &y2[0],
&z2[0], tribol::MemorySpace::Host );

RealT fx1[numVerts] = { 0., 0., 0., 0. };
RealT fy1[numVerts] = { 0., 0., 0., 0. };
RealT fz1[numVerts] = { 0., 0., 0., 0. };
RealT fx2[numVerts] = { 0., 0., 0., 0. };
RealT fy2[numVerts] = { 0., 0., 0., 0. };
RealT fz2[numVerts] = { 0., 0., 0., 0. };

tribol::registerNodalResponse( 0, &fx1[0], &fy1[0], &fz1[0] );
tribol::registerNodalResponse( 1, &fx2[0], &fy2[0], &fz2[0] );

RealT penalty = 1.0;
tribol::setKinematicConstantPenalty( 0, penalty );
tribol::setKinematicConstantPenalty( 1, penalty );

tribol::registerCouplingScheme( 0, 0, 1, tribol::SURFACE_TO_SURFACE, tribol::NO_CASE, tribol::COMMON_PLANE,
tribol::FRICTIONLESS, tribol::PENALTY, tribol::BINNING_GRID,
tribol::ExecutionMode::Sequential );

tribol::setPenaltyOptions( 0, tribol::KINEMATIC, tribol::KINEMATIC_CONSTANT );

// Default residual_gap is 0.0. Gap is 0.1, so no contact.
RealT dt = 1.0;
tribol::update( 1, 1.0, dt );

// there still should be an active pair even with no contact...
auto& cs = tribol::CouplingSchemeManager::getInstance().at( 0 );
EXPECT_EQ( 1, cs.getNumActivePairs() );

// ...but the pair should have zero force
for ( int i = 0; i < numVerts; ++i ) {
EXPECT_NEAR( fz1[i], 0.0, 1.e-10 );
EXPECT_NEAR( fz2[i], 0.0, 1.e-10 );
}

// Set residual_gap to 0.15. Now gap (0.1) < residual_gap (0.15), so contact.
RealT residual_gap = 0.15;
tribol::setResidualGap( 0, residual_gap );

tribol::update( 2, 2.0, dt );

EXPECT_EQ( 1, cs.getNumActivePairs() );

// Check forces.
// actual gap g = 0.1.
// pressure p = (g - residual_gap) * effective_penalty = (0.1 - 0.15) * 0.5 = -0.025
// Force on mesh 1 nodes: repulsion points DOWN (-z).
// Total force = p * A = -0.025 * 1.0 = -0.025.
// Distributed to 4 nodes: -0.025 / 4 = -0.00625.

for ( int i = 0; i < numVerts; ++i ) {
EXPECT_NEAR( fz1[i], -0.00625, 1.e-10 );
EXPECT_NEAR( fz2[i], 0.00625, 1.e-10 );
}
}

TEST_F( ResidualGapTest, mortar_residual_gap )
{
// Setup two 3D quads with a 0.1 gap
constexpr int numVerts = 4;
constexpr int numElems = 1;
RealT x1[numVerts] = { 0.0, 1.0, 1.0, 0.0 };
RealT y1[numVerts] = { 0.0, 0.0, 1.0, 1.0 };
RealT z1[numVerts] = { 0.0, 0.0, 0.0, 0.0 };

RealT x2[numVerts] = { 0.0, 1.0, 1.0, 0.0 };
RealT y2[numVerts] = { 0.0, 0.0, 1.0, 1.0 };
RealT z2[numVerts] = { 0.1, 0.1, 0.1, 0.1 };

tribol::IndexT conn1[numVerts] = { 0, 1, 2, 3 };
tribol::IndexT conn2[numVerts] = { 0, 3, 2, 1 };

tribol::registerMesh( 0, numElems, numVerts, &conn1[0], (int)( tribol::LINEAR_QUAD ), &x1[0], &y1[0], &z1[0],
tribol::MemorySpace::Host );
tribol::registerMesh( 1, numElems, numVerts, &conn2[0], (int)( tribol::LINEAR_QUAD ), &x2[0], &y2[0], &z2[0],
tribol::MemorySpace::Host );

RealT fx1[numVerts] = { 0., 0., 0., 0. };
RealT fy1[numVerts] = { 0., 0., 0., 0. };
RealT fz1[numVerts] = { 0., 0., 0., 0. };
RealT fx2[numVerts] = { 0., 0., 0., 0. };
RealT fy2[numVerts] = { 0., 0., 0., 0. };
RealT fz2[numVerts] = { 0., 0., 0., 0. };

tribol::registerNodalResponse( 0, &fx1[0], &fy1[0], &fz1[0] );
tribol::registerNodalResponse( 1, &fx2[0], &fy2[0], &fz2[0] );

RealT gaps[numVerts] = { 0., 0., 0., 0. };
RealT pressures[numVerts] = { 0., 0., 0., 0. };
tribol::registerMortarGaps( 1, gaps );
tribol::registerMortarPressures( 1, pressures );

tribol::registerCouplingScheme( 0, 0, 1, tribol::SURFACE_TO_SURFACE, tribol::NO_CASE, tribol::SINGLE_MORTAR,
tribol::FRICTIONLESS, tribol::LAGRANGE_MULTIPLIER, tribol::BINNING_GRID,
tribol::ExecutionMode::Sequential );

tribol::setLagrangeMultiplierOptions( 0, tribol::ImplicitEvalMode::MORTAR_RESIDUAL );

// Default residual_gap is 0.0. Gap is 0.1.
RealT dt = 1.0;
tribol::update( 1, 1.0, dt );

auto& cs = tribol::CouplingSchemeManager::getInstance().at( 0 );
EXPECT_EQ( 1, cs.getNumActivePairs() );

// Nodal gaps in mortar are integrated: 0.1 * 0.25 = 0.025
for ( int i = 0; i < numVerts; ++i ) {
EXPECT_NEAR( gaps[i], 0.025, 1.e-10 );
}

// Set residual_gap to 0.15.
RealT residual_gap = 0.15;
tribol::setResidualGap( 0, residual_gap );

// Clear gaps to avoid accumulation in test
for ( int i = 0; i < numVerts; ++i ) gaps[i] = 0.0;

tribol::update( 2, 2.0, dt );

// Kinematic gap is (0.1 - 0.15) = -0.05. Integrated: -0.05 * 0.25 = -0.0125
for ( int i = 0; i < numVerts; ++i ) {
EXPECT_NEAR( gaps[i], -0.0125, 1.e-10 );
}
}

int main( int argc, char* argv[] )
{
int result = 0;
::testing::InitGoogleTest( &argc, argv );
axom::slic::SimpleLogger logger;
result = RUN_ALL_TESTS();
return result;
}
2 changes: 2 additions & 0 deletions src/tribol/common/Parameters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,8 @@ struct Parameters {
// constituent face elements, then we don't consider the face-pair a contact candidate.
// Note, auto-contact will require registration of element thicknesses.
bool auto_contact_check = false; ///! True if auto-contact checks should be enabled

RealT residual_gap = 0.0; ///! User defined residual gap constraint
};

} // namespace tribol
Expand Down
2 changes: 1 addition & 1 deletion src/tribol/geom/CompGeom.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ TRIBOL_HOST_DEVICE inline bool CommonPlanePair::exceedsMaxAutoInterpen( const Me
RealT max_interpen = -1. * params.auto_contact_pen_frac *
axom::utilities::min( mesh1.getElementData().m_thickness[faceId1],
mesh2.getElementData().m_thickness[faceId2] );
if ( gap < max_interpen ) {
if ( ( gap - params.residual_gap ) < max_interpen ) {
return true;
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/tribol/interface/mfem_tribol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,11 @@ void updateMfemParallelDecomposition( int n_ranks, bool force_new_redecomp )
if ( mfem_data->GetLORFactor() > 1 ) {
effective_binning_proximity *= static_cast<RealT>( mfem_data->GetLORFactor() );
}
auto residual_gap = cs.getParameters().residual_gap;
// 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 new_redecomp =
mfem_data->UpdateMfemMeshData( effective_binning_proximity, n_ranks, force_new_redecomp, residual_gap );
auto coord_ptrs = mfem_data->GetRedecompCoordsPtrs();

registerMesh( mesh_ids[0], mfem_data->GetMesh1NE(), mfem_data->GetNV(), mfem_data->GetMesh1Conn(),
Expand Down
13 changes: 13 additions & 0 deletions src/tribol/interface/tribol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,19 @@ void setBinningProximityScale( IndexT cs_id, RealT binning_proximity_scale )

} // end setBinningProximityScale()

//------------------------------------------------------------------------------
void setResidualGap( IndexT cs_id, RealT residual_gap )
{
auto cs = CouplingSchemeManager::getInstance().findData( cs_id );

// check to see if coupling scheme exists
SLIC_ERROR_ROOT_IF(
!cs, "tribol::setResidualGap(): call tribol::registerCouplingScheme() " << "prior to calling this routine." );

cs->getParameters().residual_gap = residual_gap;

} // end setResidualGap()

//------------------------------------------------------------------------------
void enableTimestepVote( IndexT cs_id, const bool enable )
{
Expand Down
9 changes: 9 additions & 0 deletions src/tribol/interface/tribol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ void setLoggingLevel( IndexT cs_id, LoggingLevel log_level );
*/
void setBinningProximityScale( IndexT cs_id, RealT binning_proximity_scale );

/*!
* @brief Sets the residual gap for a coupling scheme
*
* @param [in] cs_id coupling scheme id
* @param [in] residual_gap the gap offset. Positive values shift the contact surface away from the mesh
* surface, making contact occur earlier (with a gap). Effective gap = kinematic gap - residual gap.
*/
void setResidualGap( IndexT cs_id, RealT residual_gap );

/*!
* \brief Enable the contact timestep vote
*
Expand Down
5 changes: 3 additions & 2 deletions src/tribol/mesh/CouplingScheme.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1013,8 +1013,9 @@ TRIBOL_HOST_DEVICE inline RealT CouplingScheme::Viewer::getGapTol( int fid1, int
break;

default:
gap_tol = -1. * m_parameters.gap_tol_ratio *
axom::utilities::max( m_mesh1.getFaceRadius()[fid1], m_mesh2.getFaceRadius()[fid2] );
gap_tol = m_parameters.residual_gap -
m_parameters.gap_tol_ratio *
axom::utilities::max( m_mesh1.getFaceRadius()[fid1], m_mesh2.getFaceRadius()[fid2] );
break;

} // end switch over m_contactModel
Expand Down
Loading