Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
61c3ac3
initial implementation
ebchin Feb 10, 2026
0d10106
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Feb 13, 2026
092562e
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Feb 13, 2026
aec5c35
updated naming
ebchin Feb 13, 2026
d83b7f4
new method name and caliper annotations
ebchin Feb 13, 2026
e56d66b
formatting and updated naming
ebchin Feb 13, 2026
9c705f3
change LoggingLevel enum to lowercase
ebchin Feb 13, 2026
0218bb3
add a wrapper class for element contributions
ebchin Feb 14, 2026
06ba4c0
update comments
ebchin Feb 14, 2026
e3536ac
update comments
ebchin Feb 14, 2026
8f9d476
fix formatting and includes
ebchin Feb 14, 2026
5bbc9f4
size empty blocks correctly
ebchin Mar 12, 2026
e24c745
temp workaround for axom Array issue
ebchin Mar 12, 2026
13f6547
implement LOR transfer as a matrix operator
ebchin Mar 27, 2026
29f5d1a
make test work on arbitrary number of ranks
ebchin Mar 27, 2026
c891400
add an explicit check for the fallback constructed R operator
ebchin Mar 27, 2026
3d8e281
different LOR factors and orders in test
ebchin Mar 27, 2026
a9246f0
fix bug when setting new lor factor
ebchin Mar 27, 2026
f3bfa37
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Apr 3, 2026
d12f952
fix build issues
ebchin Apr 3, 2026
ad2be0b
update LOR transfer code
ebchin Apr 3, 2026
c358fee
formatting
ebchin Apr 3, 2026
c00707a
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Apr 13, 2026
af3246a
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Apr 16, 2026
dce2f38
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Apr 16, 2026
b177a5b
lowercase fixes
ebchin Apr 16, 2026
7049cfb
ensure memory is on host before calling hypre method
ebchin Apr 17, 2026
b5e120b
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Apr 17, 2026
2e512e9
Merge branch 'develop' into feature/ebchin/simplified-jacobian-xfers
ebchin Apr 17, 2026
b2dd282
fix empty matrix issue
ebchin Apr 17, 2026
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/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ if ( BUILD_REDECOMP )
set( examples
mfem_mortar_lm_patch.cpp
mfem_common_plane.cpp
jacobian_transfer_comparison.cpp
)

foreach( example ${examples} )
Expand Down
195 changes: 195 additions & 0 deletions src/examples/jacobian_transfer_comparison.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// 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 <set>
#include <vector>
#include <chrono>

#include "tribol/config.hpp"

#ifdef TRIBOL_USE_UMPIRE
#include "umpire/ResourceManager.hpp"
#endif

#include "mfem.hpp"

#include "axom/core.hpp"
#include "axom/slic.hpp"

#include "shared/mesh/MeshBuilder.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"
#include "tribol/mesh/CouplingScheme.hpp"

int main( int argc, char** argv )
{
MPI_Init( &argc, &argv );
int rank, n_ranks;
MPI_Comm_rank( MPI_COMM_WORLD, &rank );
MPI_Comm_size( MPI_COMM_WORLD, &n_ranks );

#ifdef TRIBOL_USE_UMPIRE
umpire::ResourceManager::getInstance();
#endif

axom::slic::SimpleLogger logger;
axom::slic::setIsRoot( rank == 0 );

int ref_levels = 2;
axom::CLI::App app{ "jacobian_transfer_comparison" };
app.add_option( "-r,--refine", ref_levels, "Number of times to refine the mesh uniformly." )->capture_default_str();
CLI11_PARSE( app, argc, argv );

// Setup two-block mesh
int nel_per_dir = std::pow( 2, ref_levels );
auto elem_type = mfem::Element::HEXAHEDRON;
auto mortar_attrs = std::set<int>( { 4 } );
auto nonmortar_attrs = std::set<int>( { 5 } );

mfem::ParMesh mesh = shared::ParMeshBuilder(
MPI_COMM_WORLD,
shared::MeshBuilder::Unify( { shared::MeshBuilder::CubeMesh( nel_per_dir, nel_per_dir, nel_per_dir, elem_type )
.updateBdrAttrib( 4, 7 )
.updateBdrAttrib( 6, 4 ),
shared::MeshBuilder::CubeMesh( nel_per_dir, nel_per_dir, nel_per_dir, elem_type )
.translate( { 0.0, 0.0, 0.99 } )
.updateBdrAttrib( 8, 5 ) } ) );

int dim = mesh.SpaceDimension();
mfem::H1_FECollection fec( 1, dim );
mfem::ParFiniteElementSpace fespace( &mesh, &fec, dim );
mfem::ParGridFunction coords( &fespace );
mesh.GetNodes( coords );

// Register Tribol coupling scheme
int cs_id = 0;
tribol::registerMfemCouplingScheme( cs_id, 0, 1, mesh, coords, mortar_attrs, nonmortar_attrs,
tribol::SURFACE_TO_SURFACE, tribol::NO_SLIDING, tribol::SINGLE_MORTAR,
tribol::FRICTIONLESS, tribol::LAGRANGE_MULTIPLIER, tribol::BINNING_GRID );
tribol::setMPIComm( cs_id, MPI_COMM_WORLD );
tribol::updateMfemParallelDecomposition();

// Setup MFEM Jacobian data
auto& cs_manager = tribol::CouplingSchemeManager::getInstance();
auto* cs = cs_manager.findData( cs_id );
if ( !cs->hasMfemJacobianData() ) {
cs->setMfemJacobianData( std::make_unique<tribol::MfemJacobianData>(
*cs->getMfemMeshData(), *cs->getMfemSubmeshData(), cs->getContactMethod() ) );
}
auto* jac_data = cs->getMfemJacobianData();
jac_data->UpdateJacobianXfer();

// Synthesize Jacobian data for comparison
int ne1 = cs->getMfemMeshData()->GetMesh1NE();
int num_nodes_per_elem = 4; // Quad faces
int num_dofs_per_elem = num_nodes_per_elem * dim;

// Populate ComputedElementData
tribol::ComputedElementData new_data;
new_data.row_space = tribol::BlockSpace::MORTAR;
new_data.col_space = tribol::BlockSpace::MORTAR;
new_data.jacobian_data.resize( ne1 * num_dofs_per_elem * num_dofs_per_elem );
new_data.jacobian_offsets.resize( ne1 );
new_data.row_elem_ids.resize( ne1 );
new_data.col_elem_ids.resize( ne1 );

// Setup MethodData for comparison
if ( cs->getMethodData() == nullptr ) {
cs->allocateMethodData();
}
auto* method_data = cs->getMethodData();
SLIC_ASSERT( method_data != nullptr );

tribol::ArrayT<tribol::BlockSpace> spaces( { tribol::BlockSpace::MORTAR } );
method_data->reserveBlockJ( std::move( spaces ), ne1 );

for ( int e = 0; e < ne1; ++e ) {
new_data.row_elem_ids[e] = e;
new_data.col_elem_ids[e] = e;
new_data.jacobian_offsets[e] = e * num_dofs_per_elem * num_dofs_per_elem;

tribol::StackArray<tribol::DeviceArray2D<tribol::RealT>, 9> blockJ;
blockJ[0] = tribol::DeviceArray2D<tribol::RealT>( num_dofs_per_elem, num_dofs_per_elem );

for ( int i = 0; i < num_dofs_per_elem; ++i ) {
for ( int j = 0; j < num_dofs_per_elem; ++j ) {
double val = static_cast<double>( e + i + j );
blockJ[0]( i, j ) = val;
new_data.jacobian_data[new_data.jacobian_offsets[e] + i + j * num_dofs_per_elem] = val;
}
}
tribol::ArrayT<int> ids( { e } );
method_data->storeElemBlockJ( std::move( ids ), blockJ );
}

// Time and assemble with old method
auto start_old = std::chrono::high_resolution_clock::now();

auto xfer = cs->getMfemJacobianData()->GetMfemBlockJacobian( *method_data, { { 0, tribol::BlockSpace::MORTAR } },
{ { 0, tribol::BlockSpace::MORTAR } } );

auto end_old = std::chrono::high_resolution_clock::now();

// Time and assemble with new method
std::vector<tribol::ComputedElementData> contribs_vec;
if ( ne1 > 0 ) {
contribs_vec.push_back( std::move( new_data ) );
}

auto start_new = std::chrono::high_resolution_clock::now();
auto par_J_new = jac_data->GetMfemJacobian( contribs_vec );
auto end_new = std::chrono::high_resolution_clock::now();

// Verify results
auto* old_hypre = dynamic_cast<mfem::HypreParMatrix*>( &xfer->GetBlock( 0, 0 ) );
auto* new_hypre = &par_J_new.get();

// Check difference
shared::ParSparseMat diff_psm = shared::ParSparseMatView( old_hypre ) - shared::ParSparseMatView( new_hypre );

// Verify match by checking max error
double max_err = 0.0;
HYPRE_ParCSRMatrix diff_csr = diff_psm.get();
hypre_ParCSRMatrix* diff_parcsr = (hypre_ParCSRMatrix*)diff_csr;
hypre_CSRMatrix* diag = hypre_ParCSRMatrixDiag( diff_parcsr );
double* data = hypre_CSRMatrixData( diag );
int num_nonzeros = hypre_CSRMatrixNumNonzeros( diag );
for ( int i = 0; i < num_nonzeros; ++i ) {
max_err = std::max( max_err, std::abs( data[i] ) );
}
hypre_CSRMatrix* offd = hypre_ParCSRMatrixOffd( diff_parcsr );
data = hypre_CSRMatrixData( offd );
num_nonzeros = hypre_CSRMatrixNumNonzeros( offd );
for ( int i = 0; i < num_nonzeros; ++i ) {
max_err = std::max( max_err, std::abs( data[i] ) );
}

double global_max_err = 0.0;
MPI_Allreduce( &max_err, &global_max_err, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD );

if ( rank == 0 ) {
std::cout << "Old method time: "
<< std::chrono::duration_cast<std::chrono::microseconds>( end_old - start_old ).count() << " us"
<< std::endl;
std::cout << "New method time: "
<< std::chrono::duration_cast<std::chrono::microseconds>( end_new - start_new ).count() << " us"
<< std::endl;
std::cout << "Matrix difference max err: " << global_max_err << std::endl;
}

if ( global_max_err > 1e-12 ) {
SLIC_ERROR_ROOT( "Matrices do not match!" );
} else {
SLIC_INFO_ROOT( "Verification successful: Matrices match." );
}

tribol::finalize();
MPI_Finalize();
return 0;
}
Loading
Loading