Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
4668d31
Neohookean enzyme test
Jun 12, 2025
286f982
new method and old method updates
Jan 23, 2026
5afc035
updated mortar contact
Jan 30, 2026
d75e26f
Merge branch '2d_contact_method' into feature/ebchin/smooth-mortar-pl…
ebchin Feb 2, 2026
0b464dd
add base class
ebchin Jan 29, 2026
16d2432
initial templated mortar class
ebchin Jan 29, 2026
ac22d65
use tribol::MeshData and fix warnings
ebchin Feb 2, 2026
9208f93
formatting
ebchin Feb 2, 2026
b82cccf
add a new diagonalMatrix() method
ebchin Feb 4, 2026
8552811
implement adapter for 2d smooth contact
ebchin Feb 4, 2026
3acf423
Merge branch 'develop' into feature/ebchin/smooth-mortar-plumbing
ebchin Feb 4, 2026
8a6c42a
bugfixes and add energy method
ebchin Feb 4, 2026
7167e03
add factory for creating formulations
ebchin Feb 4, 2026
897b625
add formulation hooks for coupling scheme
ebchin Feb 4, 2026
764e38a
add ContactFormulation hooks
ebchin Feb 4, 2026
b6033e3
add ENERGY_MORTAR
ebchin Feb 4, 2026
893736c
update file list
ebchin Feb 4, 2026
ca8480d
add an example and test
ebchin Feb 4, 2026
2b3c8f1
make test 2d and add debug prints
ebchin Feb 6, 2026
136b8c4
get rid of mpi wait for debugging
ebchin Feb 6, 2026
a9ca63c
test cleanup
ebchin Feb 6, 2026
c86c39d
remove unneeded calls
ebchin Feb 6, 2026
4899cbc
cleanup to work with changes to method
ebchin Feb 6, 2026
608d107
various bugfixes and cleanup
ebchin Feb 6, 2026
b3bf93a
add options output for debug
ebchin Feb 6, 2026
61c3ac3
initial implementation
ebchin Feb 10, 2026
22b4451
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Feb 11, 2026
25818d8
switch to new ParSparseMat and ParVector
ebchin Feb 11, 2026
4553d3c
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Feb 11, 2026
a05c1f8
simplify interface return types
ebchin Feb 12, 2026
d338766
Merge branch 'feature/ebchin/hypreparmatrix-wrapper' into feature/ebc…
ebchin Feb 12, 2026
0209f68
formatting
ebchin Feb 12, 2026
3e97826
add more TRIBOL_USE_ENZYME guards; new API functions
ebchin Feb 12, 2026
cc61686
fix new vs free issues
ebchin Feb 12, 2026
f7d078f
fix ambiguous operator
ebchin Feb 12, 2026
f9875e2
bugfixes
ebchin Feb 12, 2026
ed4a366
changes
Feb 4, 2026
995c853
Patch test added
Feb 12, 2026
42673b4
Patch Test added
Feb 12, 2026
8008d15
fix leak
ebchin Feb 12, 2026
b9a3eb7
update file list
ebchin Feb 12, 2026
2fd1af3
formatting
ebchin Feb 12, 2026
9f80592
test/example consistency updates
ebchin Feb 12, 2026
6caac3b
force submesh and jacobian data for ENERGY_MORTAR
ebchin Feb 12, 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
4f108a5
backup defs when mfem is built without enzyme
ebchin Feb 13, 2026
b7e090b
more energy mortar checks
ebchin Feb 13, 2026
8ba043c
add option for non-tied contact
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
00c2a6e
Merge branch 'feature/ebchin/simplified-jacobian-xfers' into feature/…
ebchin Feb 13, 2026
029e711
start using getMfemJacobian
ebchin Feb 13, 2026
4f9163b
assemble element contribs directly into ComputedElementData
ebchin Feb 14, 2026
3889556
add a wrapper class for element contributions
ebchin Feb 14, 2026
95a3013
apply jacobian contribution wrapper
ebchin Feb 14, 2026
4f84afd
fixed smoothing issue
Feb 23, 2026
8a9b850
more changes
Feb 23, 2026
9103511
size empty blocks correctly
ebchin Mar 12, 2026
b37c163
fix some bugs
ebchin Mar 12, 2026
16b2ca8
formatting
ebchin Mar 12, 2026
b394f71
temp workaround for axom Array issue
ebchin Mar 12, 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
3 changes: 3 additions & 0 deletions src/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set( contact_examples
common_plane_gpu.cpp
common_plane.cpp
mortar_lm_patch_test.cpp
step_1_lobatto.cpp
)


Expand Down Expand Up @@ -100,7 +101,9 @@ if ( BUILD_REDECOMP )

set( examples
mfem_mortar_lm_patch.cpp
mfem_mortar_energy_patch.cpp
mfem_common_plane.cpp
jacobian_transfer_comparison.cpp
)

foreach( example ${examples} )
Expand Down
201 changes: 201 additions & 0 deletions src/examples/jacobian_transfer_comparison.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// 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 "mfem.hpp"
#include "axom/core.hpp"
#include "axom/slic.hpp"
#include "shared/mesh/MeshBuilder.hpp"
#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"
#include "tribol/mesh/CouplingScheme.hpp"

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

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 );

// 1. Setup mesh (2 blocks)
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 );

// 2. 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();

// 3. Setup MfemJacobianData
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();

// 4. Synthesize Jacobian data for comparison
// We'll create some dummy element matrices for elements on Mesh 1 (Mortar)
int ne1 = cs->getMfemMeshData()->GetMesh1NE();
int num_nodes_per_elem = 4; // Quad faces
int num_dofs_per_elem = num_nodes_per_elem * dim;

// New format: 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 );

// Prepare MethodData for OLD path 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 );
}

// 5. Time and assemble using Old Method
auto start_old = std::chrono::high_resolution_clock::now();

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

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

// 6. Time and assemble using New Method
// We must call this on ALL ranks collectively.
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();

// 7. Verify match
// We need to extract the block from BlockOperator if we used GetMfemBlockJacobian
auto* old_hypre = dynamic_cast<mfem::HypreParMatrix*>( &xfer->GetBlock( 0, 0 ) );
auto* new_hypre = &par_J_new.get();

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

// Verify match by checking NNZ of difference
// Since we subtracted, exact match means NNZ should be 0 (or values very small)
// mfem::HypreParMatrix doesn't have an easy "max norm" without converting to SparseMatrix
// but we can check NNZ. Note that operator- might keep zero entries.
// A better way is to check the data array of the resulting matrix.

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] ) );
}
// Also check off-diagonal block
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