From 63a057c07618a2f5c5e52f9a6929048aebb006bb Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Sun, 25 Jan 2026 23:52:48 -0800 Subject: [PATCH 01/33] initial implementation --- src/tribol/CMakeLists.txt | 2 + src/tribol/utils/ParSparseMat.cpp | 147 ++++++++++++++++++++++ src/tribol/utils/ParSparseMat.hpp | 198 ++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+) create mode 100644 src/tribol/utils/ParSparseMat.cpp create mode 100644 src/tribol/utils/ParSparseMat.hpp diff --git a/src/tribol/CMakeLists.txt b/src/tribol/CMakeLists.txt index 01caae75..988f3294 100644 --- a/src/tribol/CMakeLists.txt +++ b/src/tribol/CMakeLists.txt @@ -49,6 +49,7 @@ set(tribol_headers utils/ContactPlaneOutput.hpp utils/DataManager.hpp utils/Math.hpp + utils/ParSparseMat.hpp utils/TestUtils.hpp ) @@ -82,6 +83,7 @@ set(tribol_sources utils/ContactPlaneOutput.cpp utils/Math.cpp + utils/ParSparseMat.cpp utils/TestUtils.cpp ) diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp new file mode 100644 index 00000000..6fff8088 --- /dev/null +++ b/src/tribol/utils/ParSparseMat.cpp @@ -0,0 +1,147 @@ +// 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 "tribol/utils/ParSparseMat.hpp" + +namespace tribol { + +ParSparseMat::ParSparseMat( mfem::HypreParMatrix* mat ) : m_mat( mat ) {} + +ParSparseMat::ParSparseMat( std::unique_ptr mat ) : m_mat( std::move( mat ) ) {} + +ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ) +{ + m_mat = std::make_unique( comm, glob_size, row_starts, &diag ); + diag.GetMemoryI().ClearOwnerFlags(); + diag.GetMemoryJ().ClearOwnerFlags(); + diag.GetMemoryData().ClearOwnerFlags(); + m_mat->SetOwnerFlags( -1, m_mat->OwnsOffd(), m_mat->OwnsColMap() ); +} + +ParSparseMat ParSparseMat::operator+( const ParSparseMat& other ) const +{ + mfem::HypreParMatrix* result = mfem::Add( 1.0, *m_mat, 1.0, *other.m_mat ); + return ParSparseMat( result ); +} + +ParSparseMat& ParSparseMat::operator+=( const ParSparseMat& other ) +{ + *this = *this + other; + return *this; +} + +ParSparseMat ParSparseMat::operator-( const ParSparseMat& other ) const +{ + mfem::HypreParMatrix* result = mfem::Add( 1.0, *m_mat, -1.0, *other.m_mat ); + return ParSparseMat( result ); +} + +ParSparseMat& ParSparseMat::operator-=( const ParSparseMat& other ) +{ + *this = *this - other; + return *this; +} + +ParSparseMat ParSparseMat::operator*( double s ) const +{ + mfem::HypreParMatrix* result = mfem::Add( s, *m_mat, 0.0, *m_mat ); + return ParSparseMat( result ); +} + +ParSparseMat ParSparseMat::operator*( const ParSparseMat& other ) const +{ + mfem::HypreParMatrix* result = mfem::ParMult( m_mat.get(), other.m_mat.get() ); + result->CopyRowStarts(); + result->CopyColStarts(); + return ParSparseMat( result ); +} + +ParSparseMat& ParSparseMat::operator*=( const ParSparseMat& other ) +{ + *this = *this * other; + return *this; +} + +mfem::Vector ParSparseMat::operator*( const mfem::Vector& x ) const +{ + mfem::Vector y( m_mat->Height() ); + m_mat->Mult( x, y ); + return y; +} + +ParSparseMat ParSparseMat::transpose() const { return ParSparseMat( m_mat->Transpose() ); } + +ParSparseMat ParSparseMat::square() const { return *this * *this; } + +ParSparseMat ParSparseMat::RAP( const ParSparseMat& P ) const +{ + return ParSparseMat( mfem::RAP( m_mat.get(), P.m_mat.get() ) ); +} + +ParSparseMat ParSparseMat::RAP( const ParSparseMat& R, const ParSparseMat& A, const ParSparseMat& P ) +{ + return ParSparseMat( mfem::RAP( R.m_mat.get(), A.m_mat.get(), P.m_mat.get() ) ); +} + +ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, + const mfem::Array& row_starts, double diag_val, + const mfem::Array& ordered_zero_val_rows ) +{ + int num_local_rows = 0; + if ( HYPRE_AssumedPartitionCheck() ) { + num_local_rows = row_starts[1] - row_starts[0]; + } else { + int rank; + MPI_Comm_rank( comm, &rank ); + num_local_rows = row_starts[rank + 1] - row_starts[rank]; + } + int num_zero_val_rows = ordered_zero_val_rows.Size(); + int num_nonzero_val_rows = num_local_rows - num_zero_val_rows; + mfem::Array rows( num_local_rows + 1 ); + mfem::Array cols( num_nonzero_val_rows ); + rows = 0; + int zero_row_ct = 0; + for ( int i{ 0 }; i < num_local_rows; ++i ) { + if ( zero_row_ct < num_zero_val_rows && ordered_zero_val_rows[zero_row_ct] != i ) { + ++zero_row_ct; + } else { + cols[i - zero_row_ct] = i; + } + rows[i + 1] = i + 1 - zero_row_ct; + } + rows.GetMemory().SetHostPtrOwner( false ); + cols.GetMemory().SetHostPtrOwner( false ); + mfem::Vector vals( num_nonzero_val_rows ); + vals = diag_val; + vals.GetMemory().SetHostPtrOwner( false ); + mfem::SparseMatrix inactive_diag( rows.GetData(), cols.GetData(), vals.GetData(), num_local_rows, num_local_rows, + false, false, true ); + // if the size of vals is zero, SparseMatrix creates its own memory which it owns. explicitly prevent this... + inactive_diag.SetDataOwner( false ); + // copy row_starts to a new array + mfem::Array row_starts_copy = row_starts; + auto mat = std::make_unique( comm, global_size, row_starts_copy, &inactive_diag ); + mat->CopyRowStarts(); + mat->SetOwnerFlags( -1, mat->OwnsOffd(), mat->OwnsColMap() ); + return ParSparseMat( std::move( mat ) ); +} + +void ParSparseMat::EliminateRows( const mfem::Array& rows ) { m_mat->EliminateRows( rows ); } + +ParSparseMat ParSparseMat::EliminateCols( const mfem::Array& cols ) +{ + return ParSparseMat( m_mat->EliminateCols( cols ) ); +} + +ParSparseMat operator*( double s, const ParSparseMat& mat ) { return mat * s; } + +mfem::Vector operator*( const mfem::Vector& x, const ParSparseMat& mat ) +{ + mfem::Vector y( mat.get().Width() ); + mat.get().MultTranspose( x, y ); + return y; +} + +} // namespace tribol \ No newline at end of file diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp new file mode 100644 index 00000000..aef3f4e6 --- /dev/null +++ b/src/tribol/utils/ParSparseMat.hpp @@ -0,0 +1,198 @@ +// 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) + +#ifndef SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ +#define SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ + +#include "tribol/config.hpp" +#include "mfem.hpp" + +#include +#include + +namespace tribol { + +/** + * @brief Wrapper class for mfem::HypreParMatrix to provide convenience operators + * + * This class owns a mfem::HypreParMatrix via a unique_ptr and adds support for + * algebraic operations like addition and scalar multiplication. + */ +class ParSparseMat { + public: + /** + * @brief Construct from a mfem::HypreParMatrix pointer and take ownership + * + * @param mat Pointer to the mfem HypreParMatrix + */ + explicit ParSparseMat( mfem::HypreParMatrix* mat ); + + /** + * @brief Construct from a mfem::HypreParMatrix pointer and take ownership + * + * @param mat Pointer to the mfem HypreParMatrix + */ + explicit ParSparseMat( std::unique_ptr mat ); + + /** + * @brief Construct from MPI communicator, global size, row_starts, and mfem::SparseMatrix rvalue + * + * @param comm MPI communicator + * @param glob_size Global number of rows (and columns) + * @param row_starts Global row partitioning + * @param diag Local diagonal block SparseMatrix (rvalue) + * + * @note The HypreParMatrix will take ownership of the I, J, and Data from diag. + */ + ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ); + + /// Template constructor forwarding arguments to mfem::HypreParMatrix constructor + template + explicit ParSparseMat( Args&&... args ) + : m_mat( std::make_unique( std::forward( args )... ) ) + { + } + + /// Move constructor + ParSparseMat( ParSparseMat&& other ) = default; + + /// Move assignment + ParSparseMat& operator=( ParSparseMat&& other ) = default; + + // Disable copy constructor and assignment + ParSparseMat( const ParSparseMat& ) = delete; + ParSparseMat& operator=( const ParSparseMat& ) = delete; + + /** + * @brief Access the underlying mfem::HypreParMatrix + */ + mfem::HypreParMatrix& get() { return *m_mat; } + + /** + * @brief Access the underlying mfem::HypreParMatrix (const) + */ + const mfem::HypreParMatrix& get() const { return *m_mat; } + + /** + * @brief Access underlying matrix members via arrow operator + */ + mfem::HypreParMatrix* operator->() { return m_mat.get(); } + + /** + * @brief Access underlying matrix members via arrow operator (const) + */ + const mfem::HypreParMatrix* operator->() const { return m_mat.get(); } + + /** + * @brief Access and release ownership of the HypreParMatrix pointer. The caller is now resposible for releasing the + * memory. + */ + mfem::HypreParMatrix* release() { return m_mat.release(); } + + /** + * @brief Matrix addition: returns A + B + */ + ParSparseMat operator+( const ParSparseMat& other ) const; + + /** + * @brief Matrix in-place addition: A += B + */ + ParSparseMat& operator+=( const ParSparseMat& other ); + + /** + * @brief Matrix subtraction: returns A - B + */ + ParSparseMat operator-( const ParSparseMat& other ) const; + + /** + * @brief Matrix in-place subtraction: A -= B + */ + ParSparseMat& operator-=( const ParSparseMat& other ); + + /** + * @brief Matrix scalar multiplication: returns s * A + */ + ParSparseMat operator*( double s ) const; + + /** + * @brief Matrix multiplication: returns A * B + */ + ParSparseMat operator*( const ParSparseMat& other ) const; + + /** + * @brief Matrix in-place multiplication: A *= B + */ + ParSparseMat& operator*=( const ParSparseMat& other ); + + /** + * @brief Matrix-vector multiplication: returns y = A * x + */ + mfem::Vector operator*( const mfem::Vector& x ) const; + + /** + * @brief Returns the transpose of the matrix + */ + ParSparseMat transpose() const; + + /** + * @brief Returns the square of the matrix (A * A) + */ + ParSparseMat square() const; + + /** + * @brief Returns P^T * A * P + */ + ParSparseMat RAP( const ParSparseMat& P ) const; + + /** + * @brief Returns R * A * P + */ + static ParSparseMat RAP( const ParSparseMat& R, const ParSparseMat& A, const ParSparseMat& P ); + + /** + * @brief Returns a diagonal matrix with the given diagonal value + * + * @param comm MPI communicator + * @param global_size Global size of the matrix (rows and columns) + * @param row_starts Row partitioning (global offsets) + * @param diag_val Value for the diagonal entries + * @param ordered_zero_val_rows Sorted array of local row indices that should be zero (inactive). Defaults to empty. + * @return ParSparseMat The constructed diagonal matrix + */ + static ParSparseMat diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, + const mfem::Array& row_starts, double diag_val, + const mfem::Array& ordered_zero_val_rows = mfem::Array() ); + + /** + * @brief Eliminates the rows from the matrix + * + * @param rows Array of rows to eliminate + */ + void EliminateRows( const mfem::Array& rows ); + + /** + * @brief Eliminates the columns from the matrix + * + * @param cols Array of columns to eliminate + */ + ParSparseMat EliminateCols( const mfem::Array& cols ); + + /** + * @brief Scalar-Matrix multiplication: returns s * A + */ + friend ParSparseMat operator*( double s, const ParSparseMat& mat ); + + /** + * @brief Vector-Matrix multiplication: returns y = x^T * A (computed as A^T * x) + */ + friend mfem::Vector operator*( const mfem::Vector& x, const ParSparseMat& mat ); + + private: + std::unique_ptr m_mat; +}; + +} // namespace tribol + +#endif /* SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ */ \ No newline at end of file From 78eb36547d2a81015b26dcd4aa81be001317b67c Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Sun, 25 Jan 2026 23:53:03 -0800 Subject: [PATCH 02/33] fix mfem version mismatch --- scripts/spack/configs/versions.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/spack/configs/versions.yaml b/scripts/spack/configs/versions.yaml index 7ebda5d8..4ef79642 100644 --- a/scripts/spack/configs/versions.yaml +++ b/scripts/spack/configs/versions.yaml @@ -20,7 +20,7 @@ packages: - spec: "@2.26.0" mfem: require: - - spec: "@4.8.0.1" + - spec: "@4.9.0" petsc: require: - spec: "@3.21.6" From 2022fbf686b9a14743af8bd2099d96db4b3d4a9d Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Sun, 25 Jan 2026 23:53:20 -0800 Subject: [PATCH 03/33] add test --- src/tests/CMakeLists.txt | 45 ++++ src/tests/tribol_par_sparse_mat.cpp | 373 ++++++++++++++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 src/tests/tribol_par_sparse_mat.cpp diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 5b0a90ac..0b253c12 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -71,6 +71,51 @@ foreach( test ${tribol_tests} ) endforeach() +#------------------------------------------------------------------------------ +# Add MPI tests +#------------------------------------------------------------------------------ +if ( TRIBOL_USE_MPI ) + + set( mpi_tests + tribol_par_sparse_mat.cpp + ) + + set(mpi_test_depends tribol gtest) + + foreach( test ${mpi_tests} ) + + get_filename_component( test_name ${test} NAME_WE ) + + blt_add_executable( + NAME ${test_name}_test + SOURCES ${test} + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON ${mpi_test_depends} ${tribol_device_depends} + FOLDER tribol/tests ) + + blt_add_test( NAME ${test_name} + COMMAND ${test_name}_test + NUM_MPI_TASKS 2 ) + + if (ENABLE_CUDA) + set_target_properties(${test_name}_test PROPERTIES CUDA_SEPARABLE_COMPILATION On) + endif() + + if (ENABLE_HIP) + target_compile_options(${test_name}_test PRIVATE -fgpu-rdc) + target_compile_options(${test_name}_test PRIVATE + $<$: + -ggdb + > + ) + target_link_options(${test_name}_test PRIVATE -fgpu-rdc) + target_link_options(${test_name}_test PRIVATE --hip-link) + endif() + + endforeach() + +endif() + #------------------------------------------------------------------------------ # Add redecomp tests #------------------------------------------------------------------------------ diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/tribol_par_sparse_mat.cpp new file mode 100644 index 00000000..ac4c5c4c --- /dev/null +++ b/src/tests/tribol_par_sparse_mat.cpp @@ -0,0 +1,373 @@ +// 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/utils/ParSparseMat.hpp" +#include "tribol/config.hpp" +#include "mfem.hpp" + +#ifdef TRIBOL_USE_MPI +#include +#endif + +class ParSparseMatTest : public ::testing::Test { + protected: + mfem::Array GetRowStarts( MPI_Comm comm, HYPRE_BigInt global_size ) + { + int rank, num_procs; + MPI_Comm_rank( comm, &rank ); + MPI_Comm_size( comm, &num_procs ); + + int local_size = global_size / num_procs; + int remainder = global_size % num_procs; + if ( rank < remainder ) { + local_size++; + } + + mfem::Array row_starts( num_procs + 1 ); + std::vector local_sizes( num_procs ); + MPI_Allgather( &local_size, 1, MPI_INT, local_sizes.data(), 1, MPI_INT, comm ); + row_starts[0] = 0; + for ( int i = 0; i < num_procs; ++i ) { + row_starts[i + 1] = row_starts[i] + local_sizes[i]; + } + if ( HYPRE_AssumedPartitionCheck() ) { + auto total_dofs = row_starts[num_procs]; + row_starts.SetSize( 3 ); + row_starts[0] = row_starts[rank]; + row_starts[1] = row_starts[rank + 1]; + row_starts[2] = total_dofs; + } + + return row_starts; + } +}; + +// Test Construction +TEST_F( ParSparseMatTest, Construction ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + int num_procs; + MPI_Comm_size( MPI_COMM_WORLD, &num_procs ); + constexpr int size = 10; + int local_size = size / num_procs + ( rank < ( size % num_procs ) ? 1 : 0 ); + if ( rank == 0 ) std::cout << "Testing Construction..." << std::endl; + + auto row_starts_array = GetRowStarts( MPI_COMM_WORLD, size ); + + // 1. From mfem::HypreParMatrix* + mfem::HypreParMatrix* m1 = + tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 1.0 ).release(); + tribol::ParSparseMat psm1( m1 ); + EXPECT_EQ( psm1.get().Height(), local_size ); + + // 2. From unique_ptr + auto m2 = std::unique_ptr( + tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 2.0 ).release() ); + tribol::ParSparseMat psm2( std::move( m2 ) ); + EXPECT_EQ( psm2.get().Height(), local_size ); + + // 3. From SparseMatrix rvalue + mfem::SparseMatrix diag( local_size ); + for ( int i = 0; i < local_size; ++i ) diag.Set( i, i, 3.0 ); + diag.Finalize(); + + tribol::ParSparseMat psm3( MPI_COMM_WORLD, (HYPRE_BigInt)size, row_starts_array.GetData(), std::move( diag ) ); + EXPECT_EQ( psm3.get().Height(), local_size ); + + mfem::Vector x( local_size ), y( local_size ); + x = 1.0; + psm3.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); +} + +// Test Addition +TEST_F( ParSparseMatTest, Addition ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Addition..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + tribol::ParSparseMat B = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + + // A + B + tribol::ParSparseMat C = A + B; + mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + x = 1.0; + C.get().Mult( x, y ); + // Result should be (2+3)*1 = 5 + EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); + EXPECT_NEAR( y.Min(), 5.0, 1e-12 ); + + // A += B + A += B; + A.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); +} + +// Test Subtraction +TEST_F( ParSparseMatTest, Subtraction ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Subtraction..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 5.0 ); + tribol::ParSparseMat B = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + + // A - B + tribol::ParSparseMat C = A - B; + mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + x = 1.0; + C.get().Mult( x, y ); + // Result should be (5-2)*1 = 3 + EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); + + // A -= B + A -= B; + A.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); +} + +// Test Scalar Multiplication +TEST_F( ParSparseMatTest, ScalarMult ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Scalar Multiplication..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + + // A * s + tribol::ParSparseMat B = A * 3.0; + mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + x = 1.0; + B.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 6.0, 1e-12 ); + + // s * A + tribol::ParSparseMat C = 4.0 * A; + C.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 8.0, 1e-12 ); +} + +// Test Matrix Multiplication +TEST_F( ParSparseMatTest, MatrixMult ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Matrix Multiplication..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + tribol::ParSparseMat B = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + + // A * B + tribol::ParSparseMat C = A * B; + mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + x = 1.0; + C.get().Mult( x, y ); + // Result should be (2*3)*1 = 6 + EXPECT_NEAR( y.Max(), 6.0, 1e-12 ); + + // A *= B + A *= B; + A.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 6.0, 1e-12 ); +} + +// Test Matrix-Vector Multiplication +TEST_F( ParSparseMatTest, MatVecMult ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Matrix-Vector Multiplication..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + mfem::Vector x( A.get().Width() ); + x = 1.0; + + // y = A * x + mfem::Vector y = A * x; + EXPECT_NEAR( y.Max(), 2.0, 1e-12 ); +} + +// Test Vector-Matrix Multiplication +TEST_F( ParSparseMatTest, VecMatMult ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Vector-Matrix Multiplication..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + mfem::Vector x( A.get().Height() ); + x = 1.0; + + // y = x^T * A + mfem::Vector y = x * A; + EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); + EXPECT_NEAR( y.Min(), 3.0, 1e-12 ); +} + +// Test Elimination +TEST_F( ParSparseMatTest, Elimination ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Elimination..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + + // Eliminate row 0 (globally) + // Determine if I own row 0 + mfem::Array rows_to_elim; + if ( row_starts[rank] == 0 ) { + rows_to_elim.Append( 0 ); + } + A.EliminateRows( rows_to_elim ); + + // Check if row 0 is identity (or zero with diagonal 1) + // Diagonal matrix means we can just check multiplication + mfem::Vector x( A.get().Height() ), y( A.get().Height() ); + x = 1.0; + y = A * x; // y = A * x + + // if rank owns row 0, the result for that row should be 1.0 * x[0] = 1.0 (since diag is 1.0) + // other rows should be 3.0 + + // local row 0 on rank 0 is global row 0 + if ( rank == 0 ) { + EXPECT_NEAR( y[0], 0.0, 1e-12 ); + for ( int i = 1; i < y.Size(); ++i ) { + EXPECT_NEAR( y[i], 3.0, 1e-12 ); + } + } else { + for ( int i = 0; i < y.Size(); ++i ) { + EXPECT_NEAR( y[i], 3.0, 1e-12 ); + } + } + + A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + int num_procs; + MPI_Comm_size( MPI_COMM_WORLD, &num_procs ); + + // Eliminate last local col + auto last_local_col = A.get().Width() - 1; + mfem::Array cols_to_elim( { last_local_col } ); + + tribol::ParSparseMat Ae = A.EliminateCols( cols_to_elim ); + + // Now check A * e_last = 0 + // Create vector with 1 at last_local_col, 0 elsewhere + x = 0.0; + x[last_local_col] = 1.0; + y = A * x; + EXPECT_NEAR( y[last_local_col], 0.0, 1e-12 ); + + // Check Ae * e_last = original value + mfem::Vector ye = Ae * x; + double expected_val = 3.0; + + EXPECT_NEAR( ye[last_local_col], expected_val, 1e-12 ); +} + +// Test Transpose and Square +TEST_F( ParSparseMatTest, TransposeSquare ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Transpose and Square..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + + // Transpose (Diagonal matrix is symmetric) + tribol::ParSparseMat At = A.transpose(); + mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + x = 1.0; + At.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 2.0, 1e-12 ); + + // Square + tribol::ParSparseMat A2 = A.square(); + A2.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); +} + +// Test RAP +TEST_F( ParSparseMatTest, RAP ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing RAP..." << std::endl; + + // Use Identity for P to simplify testing: P^T * A * P = I * A * I = A + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 5.0 ); + tribol::ParSparseMat P = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 1.0 ); + tribol::ParSparseMat R = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 1.0 ); + + // RAP(P) + tribol::ParSparseMat Res1 = A.RAP( P ); + mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + x = 1.0; + Res1.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); + + // RAP(R, A, P) + tribol::ParSparseMat Res2 = tribol::ParSparseMat::RAP( R, A, P ); + Res2.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); +} + +// Test Accessors +TEST_F( ParSparseMatTest, Accessors ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + int num_procs; + MPI_Comm_size( MPI_COMM_WORLD, &num_procs ); + constexpr int size = 10; + int local_size = size / num_procs + ( rank < ( size % num_procs ) ? 1 : 0 ); + if ( rank == 0 ) std::cout << "Testing Accessors..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, size ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts, 1.0 ); + + // get() + EXPECT_EQ( A.get().Height(), local_size ); + + // operator-> + EXPECT_EQ( A->Height(), local_size ); +} + +//------------------------------------------------------------------------------ +#include "axom/slic/core/SimpleLogger.hpp" + +int main( int argc, char* argv[] ) +{ + int result = 0; + + MPI_Init( &argc, &argv ); + + ::testing::InitGoogleTest( &argc, argv ); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + MPI_Finalize(); + + return result; +} \ No newline at end of file From 4d7c5a37ab8e4dde59baf8e102c0070d802d8d41 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Mon, 26 Jan 2026 10:34:58 -0800 Subject: [PATCH 04/33] add view --- src/tests/tribol_par_sparse_mat.cpp | 23 +++ src/tribol/utils/ParSparseMat.cpp | 190 +++++++++++++++--------- src/tribol/utils/ParSparseMat.hpp | 215 +++++++++++++++++----------- 3 files changed, 278 insertions(+), 150 deletions(-) diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/tribol_par_sparse_mat.cpp index ac4c5c4c..bea50eec 100644 --- a/src/tests/tribol_par_sparse_mat.cpp +++ b/src/tests/tribol_par_sparse_mat.cpp @@ -85,6 +85,29 @@ TEST_F( ParSparseMatTest, Construction ) EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); } +// Test View +TEST_F( ParSparseMatTest, View ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing View..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + + // Construct View + tribol::ParSparseMatView view( &A.get() ); + + EXPECT_EQ( view.get().Height(), A.get().Height() ); + + // Operate on View + tribol::ParSparseMat B = view * 2.0; + mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + x = 1.0; + B.get().Mult( x, y ); + EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); +} + // Test Addition TEST_F( ParSparseMatTest, Addition ) { diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index 6fff8088..f5e842a8 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -7,113 +7,177 @@ namespace tribol { -ParSparseMat::ParSparseMat( mfem::HypreParMatrix* mat ) : m_mat( mat ) {} +// ParSparseMatView implementations -ParSparseMat::ParSparseMat( std::unique_ptr mat ) : m_mat( std::move( mat ) ) {} - -ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ) +ParSparseMat operator+( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - m_mat = std::make_unique( comm, glob_size, row_starts, &diag ); - diag.GetMemoryI().ClearOwnerFlags(); - diag.GetMemoryJ().ClearOwnerFlags(); - diag.GetMemoryData().ClearOwnerFlags(); - m_mat->SetOwnerFlags( -1, m_mat->OwnsOffd(), m_mat->OwnsColMap() ); + mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.m_mat, 1.0, *rhs.m_mat ); + return ParSparseMat( result ); } -ParSparseMat ParSparseMat::operator+( const ParSparseMat& other ) const +ParSparseMat operator-( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - mfem::HypreParMatrix* result = mfem::Add( 1.0, *m_mat, 1.0, *other.m_mat ); + mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.m_mat, -1.0, *rhs.m_mat ); return ParSparseMat( result ); } -ParSparseMat& ParSparseMat::operator+=( const ParSparseMat& other ) +ParSparseMat ParSparseMatView::operator*( double s ) const { - *this = *this + other; - return *this; + mfem::HypreParMatrix* result = mfem::Add( s, *m_mat, 0.0, *m_mat ); + return ParSparseMat( result ); } -ParSparseMat ParSparseMat::operator-( const ParSparseMat& other ) const +ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - mfem::HypreParMatrix* result = mfem::Add( 1.0, *m_mat, -1.0, *other.m_mat ); + mfem::HypreParMatrix* result = mfem::ParMult( lhs.m_mat, rhs.m_mat ); + result->CopyRowStarts(); + result->CopyColStarts(); return ParSparseMat( result ); } -ParSparseMat& ParSparseMat::operator-=( const ParSparseMat& other ) +mfem::Vector ParSparseMatView::operator*( const mfem::Vector& x ) const { - *this = *this - other; - return *this; + mfem::Vector y( m_mat->Height() ); + m_mat->Mult( x, y ); + return y; } -ParSparseMat ParSparseMat::operator*( double s ) const +ParSparseMat ParSparseMatView::transpose() const { return ParSparseMat( m_mat->Transpose() ); } + +ParSparseMat ParSparseMatView::square() const { return *this * *this; } + +ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& P ) const { - mfem::HypreParMatrix* result = mfem::Add( s, *m_mat, 0.0, *m_mat ); - return ParSparseMat( result ); + return ParSparseMat( mfem::RAP( m_mat, P.m_mat ) ); } -ParSparseMat ParSparseMat::operator*( const ParSparseMat& other ) const +ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& A, const ParSparseMatView& P ) { - mfem::HypreParMatrix* result = mfem::ParMult( m_mat.get(), other.m_mat.get() ); - result->CopyRowStarts(); - result->CopyColStarts(); - return ParSparseMat( result ); + return ParSparseMat( mfem::RAP( A.m_mat, P.m_mat ) ); } -ParSparseMat& ParSparseMat::operator*=( const ParSparseMat& other ) +ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& R, const ParSparseMatView& A, const ParSparseMatView& P ) { - *this = *this * other; - return *this; + return ParSparseMat( mfem::RAP( R.m_mat, A.m_mat, P.m_mat ) ); } -mfem::Vector ParSparseMat::operator*( const mfem::Vector& x ) const +void ParSparseMatView::EliminateRows( const mfem::Array& rows ) { m_mat->EliminateRows( rows ); } + +ParSparseMat ParSparseMatView::EliminateCols( const mfem::Array& cols ) { - mfem::Vector y( m_mat->Height() ); - m_mat->Mult( x, y ); + return ParSparseMat( m_mat->EliminateCols( cols ) ); +} + +ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s; } + +mfem::Vector operator*( const mfem::Vector& x, const ParSparseMatView& mat ) +{ + mfem::Vector y( mat.m_mat->Width() ); + mat.m_mat->MultTranspose( x, y ); return y; } -ParSparseMat ParSparseMat::transpose() const { return ParSparseMat( m_mat->Transpose() ); } +// ParSparseMat implementations + +ParSparseMat::ParSparseMat( mfem::HypreParMatrix* mat ) : ParSparseMatView( mat ), m_owned_mat( mat ) {} + +ParSparseMat::ParSparseMat( std::unique_ptr mat ) + : ParSparseMatView( mat.get() ), m_owned_mat( std::move( mat ) ) +{ +} + +ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, + mfem::SparseMatrix&& diag ) + : ParSparseMatView( nullptr ) +{ + m_owned_mat = std::make_unique( comm, glob_size, row_starts, &diag ); + m_mat = m_owned_mat.get(); + diag.GetMemoryI().ClearOwnerFlags(); + diag.GetMemoryJ().ClearOwnerFlags(); + diag.GetMemoryData().ClearOwnerFlags(); + m_owned_mat->SetOwnerFlags( -1, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); +} -ParSparseMat ParSparseMat::square() const { return *this * *this; } +ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept + : ParSparseMatView( other.m_owned_mat.get() ), m_owned_mat( std::move( other.m_owned_mat ) ) +{ + other.m_mat = nullptr; +} + +ParSparseMat& ParSparseMat::operator=( ParSparseMat&& other ) noexcept +{ + if ( this != &other ) { + m_owned_mat = std::move( other.m_owned_mat ); + m_mat = m_owned_mat.get(); + other.m_mat = nullptr; + } + return *this; +} + +mfem::HypreParMatrix* ParSparseMat::release() +{ + m_mat = nullptr; + return m_owned_mat.release(); +} + +ParSparseMat& ParSparseMat::operator+=( const ParSparseMatView& other ) +{ + *this = *this + other; + return *this; +} -ParSparseMat ParSparseMat::RAP( const ParSparseMat& P ) const +ParSparseMat& ParSparseMat::operator-=( const ParSparseMatView& other ) { - return ParSparseMat( mfem::RAP( m_mat.get(), P.m_mat.get() ) ); + *this = *this - other; + return *this; } -ParSparseMat ParSparseMat::RAP( const ParSparseMat& R, const ParSparseMat& A, const ParSparseMat& P ) +ParSparseMat& ParSparseMat::operator*=( const ParSparseMatView& other ) { - return ParSparseMat( mfem::RAP( R.m_mat.get(), A.m_mat.get(), P.m_mat.get() ) ); + *this = *this * other; + return *this; } ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, const mfem::Array& row_starts, double diag_val, - const mfem::Array& ordered_zero_val_rows ) + const mfem::Array& ordered_rows, bool skip_rows ) { int num_local_rows = 0; if ( HYPRE_AssumedPartitionCheck() ) { - num_local_rows = row_starts[1] - row_starts[0]; + num_local_rows = static_cast( row_starts[1] - row_starts[0] ); } else { int rank; MPI_Comm_rank( comm, &rank ); - num_local_rows = row_starts[rank + 1] - row_starts[rank]; + num_local_rows = static_cast( row_starts[rank + 1] - row_starts[rank] ); } - int num_zero_val_rows = ordered_zero_val_rows.Size(); - int num_nonzero_val_rows = num_local_rows - num_zero_val_rows; + + int num_ordered_rows = ordered_rows.Size(); + int num_diag_entries = skip_rows ? ( num_local_rows - num_ordered_rows ) : num_ordered_rows; + mfem::Array rows( num_local_rows + 1 ); - mfem::Array cols( num_nonzero_val_rows ); - rows = 0; - int zero_row_ct = 0; + mfem::Array cols( num_diag_entries ); + rows[0] = 0; + + int diag_entry_ct = 0; + int ordered_idx = 0; for ( int i{ 0 }; i < num_local_rows; ++i ) { - if ( zero_row_ct < num_zero_val_rows && ordered_zero_val_rows[zero_row_ct] != i ) { - ++zero_row_ct; - } else { - cols[i - zero_row_ct] = i; + bool is_ordered = ( ordered_idx < num_ordered_rows && ordered_rows[ordered_idx] == i ); + bool add_entry = skip_rows ? !is_ordered : is_ordered; + + if ( add_entry ) { + cols[diag_entry_ct] = i; + ++diag_entry_ct; + } + rows[i + 1] = diag_entry_ct; + + if ( is_ordered ) { + ++ordered_idx; } - rows[i + 1] = i + 1 - zero_row_ct; } + rows.GetMemory().SetHostPtrOwner( false ); cols.GetMemory().SetHostPtrOwner( false ); - mfem::Vector vals( num_nonzero_val_rows ); + mfem::Vector vals( num_diag_entries ); vals = diag_val; vals.GetMemory().SetHostPtrOwner( false ); mfem::SparseMatrix inactive_diag( rows.GetData(), cols.GetData(), vals.GetData(), num_local_rows, num_local_rows, @@ -128,20 +192,14 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si return ParSparseMat( std::move( mat ) ); } -void ParSparseMat::EliminateRows( const mfem::Array& rows ) { m_mat->EliminateRows( rows ); } - -ParSparseMat ParSparseMat::EliminateCols( const mfem::Array& cols ) +ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, HYPRE_BigInt* row_starts, + double diag_val, const mfem::Array& ordered_rows, bool skip_rows ) { - return ParSparseMat( m_mat->EliminateCols( cols ) ); -} - -ParSparseMat operator*( double s, const ParSparseMat& mat ) { return mat * s; } - -mfem::Vector operator*( const mfem::Vector& x, const ParSparseMat& mat ) -{ - mfem::Vector y( mat.get().Width() ); - mat.get().MultTranspose( x, y ); - return y; + int num_procs; + MPI_Comm_size( comm, &num_procs ); + int n_row_starts = HYPRE_AssumedPartitionCheck() ? 3 : num_procs + 1; + mfem::Array row_starts_array( row_starts, n_row_starts ); + return diagonalMatrix( comm, global_size, row_starts_array, diag_val, ordered_rows, skip_rows ); } } // namespace tribol \ No newline at end of file diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp index aef3f4e6..ecc8a6cb 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/tribol/utils/ParSparseMat.hpp @@ -14,56 +14,24 @@ namespace tribol { +class ParSparseMat; + /** - * @brief Wrapper class for mfem::HypreParMatrix to provide convenience operators + * @brief Non-owning view of a mfem::HypreParMatrix * - * This class owns a mfem::HypreParMatrix via a unique_ptr and adds support for - * algebraic operations like addition and scalar multiplication. + * This class holds a raw pointer to a mfem::HypreParMatrix and provides algebraic operations. + * It does not manage the lifetime of the matrix. */ -class ParSparseMat { +class ParSparseMatView { public: /** - * @brief Construct from a mfem::HypreParMatrix pointer and take ownership + * @brief Construct from a mfem::HypreParMatrix pointer * * @param mat Pointer to the mfem HypreParMatrix */ - explicit ParSparseMat( mfem::HypreParMatrix* mat ); + ParSparseMatView( mfem::HypreParMatrix* mat ) : m_mat( mat ) {} - /** - * @brief Construct from a mfem::HypreParMatrix pointer and take ownership - * - * @param mat Pointer to the mfem HypreParMatrix - */ - explicit ParSparseMat( std::unique_ptr mat ); - - /** - * @brief Construct from MPI communicator, global size, row_starts, and mfem::SparseMatrix rvalue - * - * @param comm MPI communicator - * @param glob_size Global number of rows (and columns) - * @param row_starts Global row partitioning - * @param diag Local diagonal block SparseMatrix (rvalue) - * - * @note The HypreParMatrix will take ownership of the I, J, and Data from diag. - */ - ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ); - - /// Template constructor forwarding arguments to mfem::HypreParMatrix constructor - template - explicit ParSparseMat( Args&&... args ) - : m_mat( std::make_unique( std::forward( args )... ) ) - { - } - - /// Move constructor - ParSparseMat( ParSparseMat&& other ) = default; - - /// Move assignment - ParSparseMat& operator=( ParSparseMat&& other ) = default; - - // Disable copy constructor and assignment - ParSparseMat( const ParSparseMat& ) = delete; - ParSparseMat& operator=( const ParSparseMat& ) = delete; + virtual ~ParSparseMatView() = default; /** * @brief Access the underlying mfem::HypreParMatrix @@ -78,38 +46,22 @@ class ParSparseMat { /** * @brief Access underlying matrix members via arrow operator */ - mfem::HypreParMatrix* operator->() { return m_mat.get(); } + mfem::HypreParMatrix* operator->() { return m_mat; } /** * @brief Access underlying matrix members via arrow operator (const) */ - const mfem::HypreParMatrix* operator->() const { return m_mat.get(); } - - /** - * @brief Access and release ownership of the HypreParMatrix pointer. The caller is now resposible for releasing the - * memory. - */ - mfem::HypreParMatrix* release() { return m_mat.release(); } + const mfem::HypreParMatrix* operator->() const { return m_mat; } /** * @brief Matrix addition: returns A + B */ - ParSparseMat operator+( const ParSparseMat& other ) const; - - /** - * @brief Matrix in-place addition: A += B - */ - ParSparseMat& operator+=( const ParSparseMat& other ); + friend ParSparseMat operator+( const ParSparseMatView& lhs, const ParSparseMatView& rhs ); /** * @brief Matrix subtraction: returns A - B */ - ParSparseMat operator-( const ParSparseMat& other ) const; - - /** - * @brief Matrix in-place subtraction: A -= B - */ - ParSparseMat& operator-=( const ParSparseMat& other ); + friend ParSparseMat operator-( const ParSparseMatView& lhs, const ParSparseMatView& rhs ); /** * @brief Matrix scalar multiplication: returns s * A @@ -119,12 +71,7 @@ class ParSparseMat { /** * @brief Matrix multiplication: returns A * B */ - ParSparseMat operator*( const ParSparseMat& other ) const; - - /** - * @brief Matrix in-place multiplication: A *= B - */ - ParSparseMat& operator*=( const ParSparseMat& other ); + friend ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs ); /** * @brief Matrix-vector multiplication: returns y = A * x @@ -144,26 +91,17 @@ class ParSparseMat { /** * @brief Returns P^T * A * P */ - ParSparseMat RAP( const ParSparseMat& P ) const; + ParSparseMat RAP( const ParSparseMatView& P ) const; /** - * @brief Returns R * A * P + * @brief Returns P^T * A * P */ - static ParSparseMat RAP( const ParSparseMat& R, const ParSparseMat& A, const ParSparseMat& P ); + static ParSparseMat RAP( const ParSparseMatView& A, const ParSparseMatView& P ); /** - * @brief Returns a diagonal matrix with the given diagonal value - * - * @param comm MPI communicator - * @param global_size Global size of the matrix (rows and columns) - * @param row_starts Row partitioning (global offsets) - * @param diag_val Value for the diagonal entries - * @param ordered_zero_val_rows Sorted array of local row indices that should be zero (inactive). Defaults to empty. - * @return ParSparseMat The constructed diagonal matrix + * @brief Returns R * A * P */ - static ParSparseMat diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, - const mfem::Array& row_starts, double diag_val, - const mfem::Array& ordered_zero_val_rows = mfem::Array() ); + static ParSparseMat RAP( const ParSparseMatView& R, const ParSparseMatView& A, const ParSparseMatView& P ); /** * @brief Eliminates the rows from the matrix @@ -182,15 +120,124 @@ class ParSparseMat { /** * @brief Scalar-Matrix multiplication: returns s * A */ - friend ParSparseMat operator*( double s, const ParSparseMat& mat ); + friend ParSparseMat operator*( double s, const ParSparseMatView& mat ); /** * @brief Vector-Matrix multiplication: returns y = x^T * A (computed as A^T * x) */ - friend mfem::Vector operator*( const mfem::Vector& x, const ParSparseMat& mat ); + friend mfem::Vector operator*( const mfem::Vector& x, const ParSparseMatView& mat ); + + protected: + mfem::HypreParMatrix* m_mat; +}; + +/** + * @brief Wrapper class for mfem::HypreParMatrix to provide convenience operators + * + * This class owns a mfem::HypreParMatrix via a unique_ptr and adds support for + * algebraic operations like addition and scalar multiplication. + */ +class ParSparseMat : public ParSparseMatView { + public: + /** + * @brief Construct from a mfem::HypreParMatrix pointer and take ownership + * + * @param mat Pointer to the mfem HypreParMatrix + */ + explicit ParSparseMat( mfem::HypreParMatrix* mat ); + + /** + * @brief Construct from a mfem::HypreParMatrix pointer and take ownership + * + * @param mat Pointer to the mfem HypreParMatrix + */ + explicit ParSparseMat( std::unique_ptr mat ); + + /** + * @brief Construct from MPI communicator, global size, row_starts, and mfem::SparseMatrix rvalue + * + * @param comm MPI communicator + * @param glob_size Global number of rows (and columns) + * @param row_starts Global row partitioning + * @param diag Local diagonal block SparseMatrix (rvalue) + * + * @note The HypreParMatrix will take ownership of the I, J, and Data from diag. + */ + ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ); + + /// Template constructor forwarding arguments to mfem::HypreParMatrix constructor + template + explicit ParSparseMat( Args&&... args ) + : ParSparseMatView( nullptr ), + m_owned_mat( std::make_unique( std::forward( args )... ) ) + { + m_mat = m_owned_mat.get(); + } + + /// Move constructor + ParSparseMat( ParSparseMat&& other ) noexcept; + + /// Move assignment + ParSparseMat& operator=( ParSparseMat&& other ) noexcept; + + // Disable copy constructor and assignment + ParSparseMat( const ParSparseMat& ) = delete; + ParSparseMat& operator=( const ParSparseMat& ) = delete; + + /** + * @brief Access and release ownership of the HypreParMatrix pointer. The caller is now resposible for releasing the + * memory. + */ + mfem::HypreParMatrix* release(); + + /** + * @brief Matrix in-place addition: A += B + */ + ParSparseMat& operator+=( const ParSparseMatView& other ); + + /** + * @brief Matrix in-place subtraction: A -= B + */ + ParSparseMat& operator-=( const ParSparseMatView& other ); + + /** + * @brief Matrix in-place multiplication: A *= B + */ + ParSparseMat& operator*=( const ParSparseMatView& other ); + + /** + * @brief Returns a diagonal matrix with the given diagonal value + * + * @param comm MPI communicator + * @param global_size Global size of the matrix (rows and columns) + * @param row_starts Row partitioning (global offsets) + * @param diag_val Value for the diagonal entries + * @param ordered_rows Sorted array of local row indices. Defaults to empty. + * @param skip_rows If true (default), ordered_rows are skipped (zero entries). If false, ordered_rows are the only entries. + * @return ParSparseMat The constructed diagonal matrix + */ + static ParSparseMat diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, + const mfem::Array& row_starts, double diag_val, + const mfem::Array& ordered_rows = mfem::Array(), + bool skip_rows = true ); + + /** + * @brief Returns a diagonal matrix with the given diagonal value + * + * @param comm MPI communicator + * @param global_size Global size of the matrix (rows and columns) + * @param row_starts Row partitioning (global offsets) + * @param diag_val Value for the diagonal entries + * @param ordered_rows Sorted array of local row indices. Defaults to empty. + * @param skip_rows If true (default), ordered_rows are skipped (zero entries). If false, ordered_rows are the only entries. + * @return ParSparseMat The constructed diagonal matrix + */ + static ParSparseMat diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, HYPRE_BigInt* row_starts, + double diag_val, const mfem::Array& ordered_rows = mfem::Array(), + bool skip_rows = true ); private: - std::unique_ptr m_mat; + std::unique_ptr m_owned_mat; }; } // namespace tribol From 91c7ba04e08d9cd6afb403224ee8527f12de4378 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Mon, 26 Jan 2026 10:35:33 -0800 Subject: [PATCH 05/33] apply ParSparseMat to code --- src/tribol/interface/mfem_tribol.cpp | 19 ++-- src/tribol/mesh/MfemData.cpp | 139 +++++++++------------------ src/tribol/mesh/MfemData.hpp | 3 +- 3 files changed, 57 insertions(+), 104 deletions(-) diff --git a/src/tribol/interface/mfem_tribol.cpp b/src/tribol/interface/mfem_tribol.cpp index e8d0a842..4dd21b47 100644 --- a/src/tribol/interface/mfem_tribol.cpp +++ b/src/tribol/interface/mfem_tribol.cpp @@ -339,14 +339,17 @@ std::unique_ptr getMfemBlockJacobian( IndexT cs_id ) auto dfdx = cs->getMfemJacobianData()->GetMfemDfDxFullJacobian( *cs->getMethodData() ); auto dfdn = cs->getMfemJacobianData()->GetMfemDfDnJacobian( *cs->getDfDnMethodData() ); auto dndx = cs->getMfemJacobianData()->GetMfemDnDxJacobian( *cs->getDnDxMethodData() ); - dfdx->SetBlock( 0, 0, - mfem::ParAdd( mfem::ParMult( &static_cast( dfdn->GetBlock( 0, 0 ) ), - &static_cast( dndx->GetBlock( 0, 0 ) ) ), - &static_cast( dfdx->GetBlock( 0, 0 ) ) ) ); - dfdx->SetBlock( 1, 0, - mfem::ParAdd( mfem::ParMult( &static_cast( dfdn->GetBlock( 1, 0 ) ), - &static_cast( dndx->GetBlock( 0, 0 ) ) ), - &static_cast( dfdx->GetBlock( 1, 0 ) ) ) ); + + auto block_00 = ( ParSparseMatView( &static_cast( dfdn->GetBlock( 0, 0 ) ) ) * + &static_cast( dndx->GetBlock( 0, 0 ) ) ) + + &static_cast( dfdx->GetBlock( 0, 0 ) ); + dfdx->SetBlock( 0, 0, block_00.release() ); + + auto block_10 = ( ParSparseMatView( &static_cast( dfdn->GetBlock( 1, 0 ) ) ) * + &static_cast( dndx->GetBlock( 0, 0 ) ) ) + + &static_cast( dfdx->GetBlock( 1, 0 ) ); + dfdx->SetBlock( 1, 0, block_10.release() ); + return dfdx; } else { return cs->getMfemJacobianData()->GetMfemBlockJacobian( cs->getMethodData() ); diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 47671456..8f2988aa 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -855,7 +855,7 @@ MfemJacobianData::MfemJacobianData( const MfemMeshData& parent_data, const MfemS mfem::Vector submesh_parent_data( submesh2parent_vdof_list_.Size() ); submesh_parent_data = 1.0; // This constructor copies all of the data, so don't worry about ownership of the CSR data - submesh_parent_vdof_xfer_ = std::make_unique( + submesh_parent_vdof_xfer_ = std::make_unique( TRIBOL_COMM_WORLD, submesh_fes.GetVSize(), submesh_fes.GlobalVSize(), parent_fes.GlobalVSize(), submesh_parent_I.data(), submesh2parent_vdof_list_.GetData(), submesh_parent_data.GetData(), submesh_fes.GetDofOffsets(), parent_fes.GetDofOffsets() ); @@ -1033,42 +1033,19 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( con auto& parent_trial_fes = *parent_data_.GetParentCoords().ParFESpace(); // NOTE: we don't call MatrixTransfer::ConvertToHypreParMatrix() because the // trial space is on the parent mesh, not the submesh - auto J_full = std::make_unique( mpi.MPIComm(), submesh_fes.GetVSize(), - submesh_fes.GlobalVSize(), parent_trial_fes.GlobalVSize(), - submesh_J.GetI(), J.GetData(), submesh_J.GetData(), - submesh_fes.GetDofOffsets(), parent_trial_fes.GetDofOffsets() ); - auto J_true = std::unique_ptr( - mfem::RAP( submesh_fes.Dof_TrueDof_Matrix(), J_full.get(), parent_trial_fes.Dof_TrueDof_Matrix() ) ); - - // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs (CSR sparse matrix -> HypreParMatrix) - // I vector - mfem::Array rows( submesh_fes.GetTrueVSize() + 1 ); - rows = 0; - auto mortar_tdofs_ct = 0; - for ( int i{ 0 }; i < submesh_fes.GetTrueVSize(); ++i ) { - if ( mortar_tdofs_ct < mortar_tdof_list_.Size() && mortar_tdof_list_[mortar_tdofs_ct] == i ) { - ++mortar_tdofs_ct; - } - rows[i + 1] = mortar_tdofs_ct; - } - // J vector - mfem::Array mortar_tdofs( mortar_tdof_list_ ); - // data vector - mfem::Vector ones( mortar_tdofs_ct ); - ones = 1.0; - mfem::SparseMatrix inactive_sm( rows.GetData(), mortar_tdofs.GetData(), ones.GetData(), submesh_fes.GetTrueVSize(), - submesh_fes.GetTrueVSize(), false, false, true ); - auto inactive_hpm = std::make_unique( J_true->GetComm(), J_true->GetGlobalNumRows(), - J_true->GetRowStarts(), &inactive_sm ); - // Have the mfem::HypreParMatrix manage the data pointers - rows.GetMemory().ClearOwnerFlags(); - mortar_tdofs.GetMemory().ClearOwnerFlags(); - ones.GetMemory().ClearOwnerFlags(); - inactive_sm.GetMemoryI().ClearOwnerFlags(); - inactive_sm.GetMemoryJ().ClearOwnerFlags(); - inactive_sm.GetMemoryData().ClearOwnerFlags(); - - block_J->SetBlock( 0, 1, J_true->Transpose() ); + ParSparseMat J_full( mpi.MPIComm(), submesh_fes.GetVSize(), submesh_fes.GlobalVSize(), parent_trial_fes.GlobalVSize(), + submesh_J.GetI(), J.GetData(), submesh_J.GetData(), submesh_fes.GetDofOffsets(), + parent_trial_fes.GetDofOffsets() ); + ParSparseMatView P_submesh( submesh_fes.Dof_TrueDof_Matrix() ); + ParSparseMatView P_parent( parent_trial_fes.Dof_TrueDof_Matrix() ); + auto J_true = J_full.RAP( P_submesh, J_full, P_parent ); + + // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs + auto inactive_hpm = ParSparseMat::diagonalMatrix( + J_true.get().GetComm(), J_true.get().GetGlobalNumRows(), J_true.get().GetRowStarts(), 1.0, + mortar_tdof_list_, false ); + + block_J->SetBlock( 0, 1, J_true.transpose().release() ); block_J->SetBlock( 1, 0, J_true.release() ); block_J->SetBlock( 1, 1, inactive_hpm.release() ); @@ -1121,10 +1098,9 @@ std::unique_ptr MfemJacobianData::GetMfemDfDxFullJacobian( submesh_J.Finalize(); auto submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_00_->ConvertToHypreParMatrix( submesh_J, false ); // Matrix returned by mfem::RAP copies all existing data and owns its data - auto parent_J_hypre = - std::unique_ptr( mfem::RAP( submesh_J_hypre.get(), submesh_parent_vdof_xfer_.get() ) ); + auto parent_J = ParSparseMatView::RAP( submesh_J_hypre.get(), *submesh_parent_vdof_xfer_ ); block_J->SetBlock( - 0, 0, mfem::RAP( parent_J_hypre.get(), parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ) ); + 0, 0, parent_J.RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ).release() ); // transfer (0, 1) block (residual dof rows, lagrange multiplier dof cols) submesh_J = GetUpdateData().submesh_redecomp_xfer_01_->TransferToParallelSparse( @@ -1139,12 +1115,11 @@ std::unique_ptr MfemJacobianData::GetMfemDfDxFullJacobian( submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_01_->ConvertToHypreParMatrix( submesh_J, false ); // Matrix returned by mfem::ParMult copies row and column starts since last arg is true. All other data is copied and // owned by the new matrix. - parent_J_hypre = std::unique_ptr( - mfem::ParMult( std::unique_ptr( submesh_parent_vdof_xfer_->Transpose() ).get(), - submesh_J_hypre.get(), true ) ); + parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); block_J->SetBlock( 0, 1, - mfem::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J_hypre.get(), - submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) ); + ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, + submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) + .release() ); // transfer (1, 0) block (gap dof rows, displacement dof cols) submesh_J = GetUpdateData().submesh_redecomp_xfer_10_->TransferToParallelSparse( @@ -1159,40 +1134,19 @@ std::unique_ptr MfemJacobianData::GetMfemDfDxFullJacobian( submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_10_->ConvertToHypreParMatrix( submesh_J, false ); // Matrix returned by mfem::ParMult copies row and column starts since last arg is true. All other data is copied and // owned by the new matrix. - parent_J_hypre = std::unique_ptr( std::unique_ptr( - mfem::ParMult( submesh_J_hypre.get(), submesh_parent_vdof_xfer_.get(), true ) ) ); + parent_J = ParSparseMatView( submesh_J_hypre.get() ) * ( *submesh_parent_vdof_xfer_ ); block_J->SetBlock( 1, 0, - mfem::RAP( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix(), parent_J_hypre.get(), - parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ) ); - - // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs (CSR sparse matrix -> HypreParMatrix) - // I vector - auto& submesh_fes = submesh_data_.GetSubmeshFESpace(); - mfem::Array rows( submesh_fes.GetTrueVSize() + 1 ); - rows = 0; - auto mortar_tdofs_ct = 0; - for ( int i{ 0 }; i < submesh_fes.GetTrueVSize(); ++i ) { - if ( mortar_tdofs_ct < mortar_tdof_list_.Size() && mortar_tdof_list_[mortar_tdofs_ct] == i ) { - ++mortar_tdofs_ct; - } - rows[i + 1] = mortar_tdofs_ct; - } - // J vector - mfem::Array mortar_tdofs( mortar_tdof_list_ ); - // data vector - mfem::Vector ones( mortar_tdofs_ct ); - ones = 1.0; - mfem::SparseMatrix inactive_sm( rows.GetData(), mortar_tdofs.GetData(), ones.GetData(), submesh_fes.GetTrueVSize(), - submesh_fes.GetTrueVSize(), false, false, true ); - auto inactive_hpm = std::make_unique( TRIBOL_COMM_WORLD, submesh_fes.GlobalTrueVSize(), - submesh_fes.GetTrueDofOffsets(), &inactive_sm ); - // Have the mfem::HypreParMatrix manage the data pointers - rows.GetMemory().SetHostPtrOwner( false ); - mortar_tdofs.GetMemory().SetHostPtrOwner( false ); - ones.GetMemory().SetHostPtrOwner( false ); - inactive_sm.SetDataOwner( false ); - inactive_hpm->SetOwnerFlags( 3, 3, 1 ); - block_J->SetBlock( 1, 1, inactive_hpm.release() ); + ParSparseMat::RAP( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix(), parent_J, + parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ) + .release() ); + + // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs + auto& submesh_fes_full = submesh_data_.GetSubmeshFESpace(); + ParSparseMat inactive_hpm_full = ParSparseMat::diagonalMatrix( + TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), submesh_fes_full.GetTrueDofOffsets(), 1.0, + mortar_tdof_list_, false ); + inactive_hpm_full.get().SetOwnerFlags( 3, 3, 1 ); + block_J->SetBlock( 1, 1, inactive_hpm_full.release() ); return block_J; } @@ -1236,11 +1190,10 @@ std::unique_ptr MfemJacobianData::GetMfemDfDnJacobian( cons method_data.getBlockJ()( static_cast( BlockSpace::NONMORTAR ), static_cast( BlockSpace::NONMORTAR ) ) ); submesh_J.Finalize(); auto submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_00_->ConvertToHypreParMatrix( submesh_J, false ); - // Matrix returned by mfem::RAP copies all existing data and owns its data - auto parent_J_hypre = - std::unique_ptr( mfem::RAP( submesh_J_hypre.get(), submesh_parent_vdof_xfer_.get() ) ); - block_J->SetBlock( - 0, 0, mfem::RAP( parent_J_hypre.get(), parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ) ); + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_J->SetBlock( 0, 0, parent_J.RAP( parent_P ).release() ); // transfer (1, 0) block (gap dof rows, displacement dof cols) submesh_J = GetUpdateData().submesh_redecomp_xfer_10_->TransferToParallelSparse( @@ -1249,13 +1202,10 @@ std::unique_ptr MfemJacobianData::GetMfemDfDnJacobian( cons static_cast( BlockSpace::NONMORTAR ) ) ); submesh_J.Finalize(); submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_10_->ConvertToHypreParMatrix( submesh_J, false ); - // Matrix returned by mfem::ParMult copies row and column starts since last arg is true. All other data is copied and - // owned by the new matrix. - parent_J_hypre = std::unique_ptr( - mfem::ParMult( submesh_J_hypre.get(), submesh_parent_vdof_xfer_.get(), true ) ); - block_J->SetBlock( 1, 0, - mfem::RAP( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix(), parent_J_hypre.get(), - parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ) ); + submesh_J_view = ParSparseMatView( submesh_J_hypre.get() ); + parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); + ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); + block_J->SetBlock( 1, 0, ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release() ); return block_J; } @@ -1281,11 +1231,10 @@ std::unique_ptr MfemJacobianData::GetMfemDnDxJacobian( cons method_data.getBlockJ()( static_cast( BlockSpace::NONMORTAR ), static_cast( BlockSpace::NONMORTAR ) ) ); submesh_J.Finalize(); auto submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_00_->ConvertToHypreParMatrix( submesh_J, false ); - // Matrix returned by mfem::RAP copies all existing data and owns its data - auto parent_J_hypre = - std::unique_ptr( mfem::RAP( submesh_J_hypre.get(), submesh_parent_vdof_xfer_.get() ) ); - block_J->SetBlock( - 0, 0, mfem::RAP( parent_J_hypre.get(), parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ) ); + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_J->SetBlock( 0, 0, parent_J.RAP( parent_P ).release() ); return block_J; } diff --git a/src/tribol/mesh/MfemData.hpp b/src/tribol/mesh/MfemData.hpp index de2846d8..844298d3 100644 --- a/src/tribol/mesh/MfemData.hpp +++ b/src/tribol/mesh/MfemData.hpp @@ -22,6 +22,7 @@ #include "tribol/common/BasicTypes.hpp" #include "tribol/common/Parameters.hpp" #include "tribol/mesh/MethodCouplingData.hpp" +#include "tribol/utils/ParSparseMat.hpp" namespace tribol { @@ -1755,7 +1756,7 @@ class MfemJacobianData { /** * @brief Submesh to parent transfer operator */ - std::unique_ptr submesh_parent_vdof_xfer_; + std::unique_ptr submesh_parent_vdof_xfer_; /** * @brief List of submesh true dofs that only exist on the mortar surface From eaa2ae33305fd8533ceed7c661aaa39953c59213 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Mon, 26 Jan 2026 23:33:24 -0800 Subject: [PATCH 06/33] add general block jacobian method --- src/tribol/interface/mfem_tribol.cpp | 17 ++- src/tribol/mesh/MfemData.cpp | 173 +++++++++++++++++++++++++-- src/tribol/mesh/MfemData.hpp | 16 +++ 3 files changed, 192 insertions(+), 14 deletions(-) diff --git a/src/tribol/interface/mfem_tribol.cpp b/src/tribol/interface/mfem_tribol.cpp index 4dd21b47..f5787aa1 100644 --- a/src/tribol/interface/mfem_tribol.cpp +++ b/src/tribol/interface/mfem_tribol.cpp @@ -335,10 +335,18 @@ std::unique_ptr getMfemBlockJacobian( IndexT cs_id ) cs_id ) ); // creates a block Jacobian on the parent mesh/parent-linked boundary submesh based on the element Jacobians stored in // the coupling scheme's method data + const std::vector all_spaces{ BlockSpace::MORTAR, BlockSpace::NONMORTAR, + BlockSpace::LAGRANGE_MULTIPLIER }; + const std::vector all_blocks{ 0, 0, 1 }; if ( cs->isEnzymeEnabled() ) { - auto dfdx = cs->getMfemJacobianData()->GetMfemDfDxFullJacobian( *cs->getMethodData() ); - auto dfdn = cs->getMfemJacobianData()->GetMfemDfDnJacobian( *cs->getDfDnMethodData() ); - auto dndx = cs->getMfemJacobianData()->GetMfemDnDxJacobian( *cs->getDnDxMethodData() ); + auto dfdx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getMethodData(), all_spaces, all_spaces, + all_blocks, all_blocks ); + const std::vector nonmortar_space{ BlockSpace::NONMORTAR }; + const std::vector disp_block{ 0 }; + auto dfdn = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDfDnMethodData(), all_spaces, nonmortar_space, + all_blocks, disp_block ); + auto dndx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDnDxMethodData(), nonmortar_space, + nonmortar_space, disp_block, disp_block ); auto block_00 = ( ParSparseMatView( &static_cast( dfdn->GetBlock( 0, 0 ) ) ) * &static_cast( dndx->GetBlock( 0, 0 ) ) ) + @@ -352,7 +360,8 @@ std::unique_ptr getMfemBlockJacobian( IndexT cs_id ) return dfdx; } else { - return cs->getMfemJacobianData()->GetMfemBlockJacobian( cs->getMethodData() ); + return cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getMethodData(), all_spaces, all_spaces, all_blocks, + all_blocks ); } } diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 8f2988aa..6f9ed0ee 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -1041,9 +1041,8 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( con auto J_true = J_full.RAP( P_submesh, J_full, P_parent ); // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs - auto inactive_hpm = ParSparseMat::diagonalMatrix( - J_true.get().GetComm(), J_true.get().GetGlobalNumRows(), J_true.get().GetRowStarts(), 1.0, - mortar_tdof_list_, false ); + auto inactive_hpm = ParSparseMat::diagonalMatrix( J_true.get().GetComm(), J_true.get().GetGlobalNumRows(), + J_true.get().GetRowStarts(), 1.0, mortar_tdof_list_, false ); block_J->SetBlock( 0, 1, J_true.transpose().release() ); block_J->SetBlock( 1, 0, J_true.release() ); @@ -1099,8 +1098,8 @@ std::unique_ptr MfemJacobianData::GetMfemDfDxFullJacobian( auto submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_00_->ConvertToHypreParMatrix( submesh_J, false ); // Matrix returned by mfem::RAP copies all existing data and owns its data auto parent_J = ParSparseMatView::RAP( submesh_J_hypre.get(), *submesh_parent_vdof_xfer_ ); - block_J->SetBlock( - 0, 0, parent_J.RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ).release() ); + block_J->SetBlock( 0, 0, + parent_J.RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ).release() ); // transfer (0, 1) block (residual dof rows, lagrange multiplier dof cols) submesh_J = GetUpdateData().submesh_redecomp_xfer_01_->TransferToParallelSparse( @@ -1113,8 +1112,6 @@ std::unique_ptr MfemJacobianData::GetMfemDfDxFullJacobian( static_cast( BlockSpace::LAGRANGE_MULTIPLIER ) ) ); submesh_J.Finalize(); submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_01_->ConvertToHypreParMatrix( submesh_J, false ); - // Matrix returned by mfem::ParMult copies row and column starts since last arg is true. All other data is copied and - // owned by the new matrix. parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); block_J->SetBlock( 0, 1, ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, @@ -1142,9 +1139,9 @@ std::unique_ptr MfemJacobianData::GetMfemDfDxFullJacobian( // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs auto& submesh_fes_full = submesh_data_.GetSubmeshFESpace(); - ParSparseMat inactive_hpm_full = ParSparseMat::diagonalMatrix( - TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), submesh_fes_full.GetTrueDofOffsets(), 1.0, - mortar_tdof_list_, false ); + ParSparseMat inactive_hpm_full = + ParSparseMat::diagonalMatrix( TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), + submesh_fes_full.GetTrueDofOffsets(), 1.0, mortar_tdof_list_, false ); inactive_hpm_full.get().SetOwnerFlags( 3, 3, 1 ); block_J->SetBlock( 1, 1, inactive_hpm_full.release() ); @@ -1239,6 +1236,162 @@ std::unique_ptr MfemJacobianData::GetMfemDnDxJacobian( cons return block_J; } +std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( const MethodData& method_data, + const std::vector& row_spaces, + const std::vector& col_spaces, + const std::vector& row_blocks, + const std::vector& col_blocks ) const +{ + // Determine block structure + int max_row_block = 0; + for ( auto b : row_blocks ) { + if ( b > max_row_block ) max_row_block = b; + } + int max_col_block = 0; + for ( auto b : col_blocks ) { + if ( b > max_col_block ) max_col_block = b; + } + + const mfem::Array& row_offsets = ( max_row_block == 0 ) ? disp_offsets_ : block_offsets_; + const mfem::Array& col_offsets = ( max_col_block == 0 ) ? disp_offsets_ : block_offsets_; + + auto block_J = std::make_unique( row_offsets, col_offsets ); + block_J->owns_blocks = 1; + + // Map unique (r_blk, c_blk) -> list of (row_space, col_space) pairs + std::map, std::vector>> block_contribs; + + for ( size_t i = 0; i < row_spaces.size(); ++i ) { + for ( size_t j = 0; j < col_spaces.size(); ++j ) { + int r_blk = row_blocks[i]; + int c_blk = col_blocks[j]; + block_contribs[{ r_blk, c_blk }].push_back( { row_spaces[i], col_spaces[j] } ); + } + } + + const auto& elem_map_1 = parent_data_.GetElemMap1(); + const auto& elem_map_2 = parent_data_.GetElemMap2(); + + // Iterate over unique blocks + for ( const auto& entry : block_contribs ) { + int r_blk = entry.first.first; + int c_blk = entry.first.second; + const auto& contribs = entry.second; + + std::unique_ptr submesh_J; + + for ( const auto& pair : contribs ) { + BlockSpace rs = pair.first; + BlockSpace cs = pair.second; + + // Get block from method_data + const auto& J_block = method_data.getBlockJ()( static_cast( rs ), static_cast( cs ) ); + + // Map element IDs to redecomp IDs + const auto& row_elem_ids_tribol = method_data.getBlockJElementIds()[static_cast( rs )]; + ArrayT row_redecomp_ids; + row_redecomp_ids.reserve( row_elem_ids_tribol.size() ); + for ( auto id : row_elem_ids_tribol ) { + if ( rs == BlockSpace::MORTAR ) { + row_redecomp_ids.push_back( elem_map_1[static_cast( id )] ); + } else { + row_redecomp_ids.push_back( elem_map_2[static_cast( id )] ); + } + } + + const auto& col_elem_ids_tribol = method_data.getBlockJElementIds()[static_cast( cs )]; + ArrayT col_redecomp_ids; + col_redecomp_ids.reserve( col_elem_ids_tribol.size() ); + for ( auto id : col_elem_ids_tribol ) { + if ( cs == BlockSpace::MORTAR ) { + col_redecomp_ids.push_back( elem_map_1[static_cast( id )] ); + } else { + col_redecomp_ids.push_back( elem_map_2[static_cast( id )] ); + } + } + + // Pick transfer object + redecomp::MatrixTransfer* xfer = nullptr; + if ( r_blk == 0 && c_blk == 0 ) + xfer = GetUpdateData().submesh_redecomp_xfer_00_.get(); + else if ( r_blk == 0 && c_blk == 1 ) + xfer = GetUpdateData().submesh_redecomp_xfer_01_.get(); + else if ( r_blk == 1 && c_blk == 0 ) + xfer = GetUpdateData().submesh_redecomp_xfer_10_.get(); + else + continue; + + auto J_contrib = xfer->TransferToParallelSparse( row_redecomp_ids, col_redecomp_ids, J_block ); + if ( !submesh_J ) { + submesh_J = std::make_unique( std::move( J_contrib ) ); + } else { + ( *submesh_J ) += J_contrib; + } + } + + if ( submesh_J ) { + submesh_J->Finalize(); + + // Pick xfer again for conversion + redecomp::MatrixTransfer* xfer = nullptr; + if ( r_blk == 0 && c_blk == 0 ) + xfer = GetUpdateData().submesh_redecomp_xfer_00_.get(); + else if ( r_blk == 0 && c_blk == 1 ) + xfer = GetUpdateData().submesh_redecomp_xfer_01_.get(); + else if ( r_blk == 1 && c_blk == 0 ) + xfer = GetUpdateData().submesh_redecomp_xfer_10_.get(); + + auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( *submesh_J, false ); + + mfem::HypreParMatrix* block_mat = nullptr; + + if ( r_blk == 0 && c_blk == 0 ) { + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_mat = parent_J.RAP( parent_P ).release(); + } else if ( r_blk == 0 && c_blk == 1 ) { + auto parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); + block_mat = ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, + submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) + .release(); + } else if ( r_blk == 1 && c_blk == 0 ) { + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); + ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_mat = ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release(); + } + + block_J->SetBlock( r_blk, c_blk, block_mat ); + } + } + + // Handle Inactive DOFs for (1, 1) + bool has_11 = false; + for ( auto rb : row_blocks ) + if ( rb == 1 ) has_11 = true; + bool col_has_1 = false; + for ( auto cb : col_blocks ) + if ( cb == 1 ) col_has_1 = true; + has_11 = has_11 && col_has_1; + + if ( has_11 ) { + auto& submesh_fes_full = submesh_data_.GetSubmeshFESpace(); + ParSparseMat inactive_hpm_full = + ParSparseMat::diagonalMatrix( TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), + submesh_fes_full.GetTrueDofOffsets(), 1.0, mortar_tdof_list_, false ); + inactive_hpm_full.get().SetOwnerFlags( 3, inactive_hpm_full.get().OwnsOffd(), + inactive_hpm_full.get().OwnsColMap() ); + + if ( block_J->IsZeroBlock( 1, 1 ) ) { + block_J->SetBlock( 1, 1, inactive_hpm_full.release() ); + } + } + + return block_J; +} + MfemJacobianData::UpdateData::UpdateData( const MfemMeshData& parent_data, const MfemSubmeshData& submesh_data ) { auto dual_submesh_fes = &submesh_data.GetSubmeshFESpace(); diff --git a/src/tribol/mesh/MfemData.hpp b/src/tribol/mesh/MfemData.hpp index 844298d3..02aaaffb 100644 --- a/src/tribol/mesh/MfemData.hpp +++ b/src/tribol/mesh/MfemData.hpp @@ -1657,6 +1657,22 @@ class MfemJacobianData { */ std::unique_ptr GetMfemBlockJacobian( const MethodData* method_data ) const; + /** + * @brief Returns a Jacobian as an mfem::BlockOperator + * + * @param method_data Method data holding element Jacobians + * @param row_spaces List of BlockSpaces for the rows + * @param col_spaces List of BlockSpaces for the columns + * @param row_blocks List of row block indices corresponding to row_spaces + * @param col_blocks List of column block indices corresponding to col_spaces + * @return std::unique_ptr + */ + std::unique_ptr GetMfemBlockJacobian( const MethodData& method_data, + const std::vector& row_spaces, + const std::vector& col_spaces, + const std::vector& row_blocks, + const std::vector& col_blocks ) const; + /** * @brief Returns full, potentially non-symmetric derivative of the force w.r.t. nodal coordinates as an * mfem::BlockOperator From 5bc73343819856f58908d67de362b62333a10b4b Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Mon, 26 Jan 2026 23:48:56 -0800 Subject: [PATCH 07/33] remove old methods --- src/tribol/mesh/MfemData.cpp | 325 ----------------------------------- src/tribol/mesh/MfemData.hpp | 33 ---- 2 files changed, 358 deletions(-) diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 6f9ed0ee..52e8a57f 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -911,331 +911,6 @@ void MfemJacobianData::UpdateJacobianXfer() update_data_ = std::make_unique( parent_data_, submesh_data_ ); } -std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( const MethodData* method_data ) const -{ - // 0 = displacement DOFs, 1 = lagrange multiplier DOFs - // (0,0) block is empty (for now using SINGLE_MORTAR with approximate tangent) - // (1,1) block is a diagonal matrix with ones on the diagonal of submesh nodes without a Lagrange multiplier DOF - // (0,1) and (1,0) are symmetric (for now using SINGLE_MORTAR with approximate tangent) - const auto& elem_map_1 = parent_data_.GetElemMap1(); - const auto& elem_map_2 = parent_data_.GetElemMap2(); - // empty data structures are needed even when no meshes are on rank since TransferToParallelSparse() needs to be - // called on all ranks (even those without data) - auto mortar_elems = ArrayT( 0, 0 ); - auto nonmortar_elems = ArrayT( 0, 0 ); - auto lm_elems = ArrayT( 0, 0 ); - auto elem_J_1_ptr = std::make_unique>( 0, 0 ); - auto elem_J_2_ptr = std::make_unique>( 0, 0 ); - const ArrayT* elem_J_1 = elem_J_1_ptr.get(); - const ArrayT* elem_J_2 = elem_J_2_ptr.get(); - // this means both of the meshes exist - if ( method_data != nullptr && !elem_map_1.empty() && !elem_map_2.empty() ) { - mortar_elems = method_data->getBlockJElementIds()[static_cast( BlockSpace::MORTAR )]; - for ( auto& mortar_elem : mortar_elems ) { - mortar_elem = elem_map_1[static_cast( mortar_elem )]; - } - nonmortar_elems = method_data->getBlockJElementIds()[static_cast( BlockSpace::NONMORTAR )]; - for ( auto& nonmortar_elem : nonmortar_elems ) { - nonmortar_elem = elem_map_2[static_cast( nonmortar_elem )]; - } - lm_elems = method_data->getBlockJElementIds()[static_cast( BlockSpace::LAGRANGE_MULTIPLIER )]; - for ( auto& lm_elem : lm_elems ) { - lm_elem = elem_map_2[static_cast( lm_elem )]; - } - // get (1,0) block - elem_J_1 = &method_data->getBlockJ()( static_cast( BlockSpace::LAGRANGE_MULTIPLIER ), - static_cast( BlockSpace::MORTAR ) ); - elem_J_2 = &method_data->getBlockJ()( static_cast( BlockSpace::LAGRANGE_MULTIPLIER ), - static_cast( BlockSpace::NONMORTAR ) ); - } - // move to submesh level - auto submesh_J = - GetUpdateData().submesh_redecomp_xfer_10_->TransferToParallelSparse( lm_elems, mortar_elems, *elem_J_1 ); - submesh_J += - GetUpdateData().submesh_redecomp_xfer_10_->TransferToParallelSparse( lm_elems, nonmortar_elems, *elem_J_2 ); - submesh_J.Finalize(); - - // transform J values from submesh to (global) parent mesh - mfem::Array J( submesh_J.NumNonZeroElems() ); - // This copy is needed to convert mfem::SparseMatrix int J values to the HYPRE_BigInt values the mfem::HypreParMatrix - // constructor needs - auto* J_int = submesh_J.GetJ(); - for ( int i{ 0 }; i < J.Size(); ++i ) { - J[i] = J_int[i]; - } - auto submesh_vector_fes = parent_data_.GetSubmeshFESpace(); - auto mpi = redecomp::MPIUtility( submesh_vector_fes.GetComm() ); - auto submesh_dof_offsets = ArrayT( mpi.NRanks() + 1, mpi.NRanks() + 1 ); - // we need the dof offsets of each rank. check if mfem stores this or if we - // need to create it. - if ( HYPRE_AssumedPartitionCheck() ) { - submesh_dof_offsets[mpi.MyRank() + 1] = submesh_vector_fes.GetDofOffsets()[1]; - mpi.Allreduce( &submesh_dof_offsets, MPI_SUM ); - } else { - for ( int i{ 0 }; i < mpi.NRanks(); ++i ) { - submesh_dof_offsets[i] = submesh_vector_fes.GetDofOffsets()[i]; - } - } - // the submesh to parent vdof map only exists for vdofs on rank, so J values - // not on rank will need to be transferred to the rank that the vdof exists on - // to query the map. the steps are laid out below. - - // step 1) query J values on rank for their parent vdof and package J values - // not on rank to send - auto send_J_by_rank = redecomp::MPIArray( &mpi ); - auto J_idx = redecomp::MPIArray( &mpi ); - auto est_num_J = submesh_J.NumNonZeroElems() / mpi.NRanks(); - for ( int r{}; r < mpi.NRanks(); ++r ) { - if ( r == mpi.MyRank() ) { - send_J_by_rank[r].shrink(); - J_idx[r].shrink(); - } else { - send_J_by_rank[r].reserve( est_num_J ); - J_idx[r].reserve( est_num_J ); - } - } - for ( int j{}; j < submesh_J.NumNonZeroElems(); ++j ) { - if ( J[j] >= submesh_dof_offsets[mpi.MyRank()] && J[j] < submesh_dof_offsets[mpi.MyRank() + 1] ) { - J[j] = submesh2parent_vdof_list_[J[j] - submesh_dof_offsets[mpi.MyRank()]]; - } else { - for ( int r{}; r < mpi.NRanks(); ++r ) { - if ( J[j] >= submesh_dof_offsets[r] && J[j] < submesh_dof_offsets[r + 1] ) { - send_J_by_rank[r].push_back( J[j] - submesh_dof_offsets[r] ); - J_idx[r].push_back( j ); - break; - } - } - } - } - // step 2) sends the J values to the ranks that own them - auto recv_J_by_rank = redecomp::MPIArray( &mpi ); - recv_J_by_rank.SendRecvArrayEach( send_J_by_rank ); - // step 3) query the on-rank map to recover J values - for ( int r{}; r < mpi.NRanks(); ++r ) { - for ( auto& recv_J : recv_J_by_rank[r] ) { - recv_J = submesh2parent_vdof_list_[recv_J]; - } - } - // step 4) send the updated parent J values back and update the J vector - send_J_by_rank.SendRecvArrayEach( recv_J_by_rank ); - for ( int r{}; r < mpi.NRanks(); ++r ) { - for ( int j{}; j < send_J_by_rank[r].size(); ++j ) { - J[J_idx[r][j]] = send_J_by_rank[r][j]; - } - } - - // create block operator - auto block_J = std::make_unique( block_offsets_ ); - block_J->owns_blocks = 1; - - // fill block operator - auto& submesh_fes = submesh_data_.GetSubmeshFESpace(); - auto& parent_trial_fes = *parent_data_.GetParentCoords().ParFESpace(); - // NOTE: we don't call MatrixTransfer::ConvertToHypreParMatrix() because the - // trial space is on the parent mesh, not the submesh - ParSparseMat J_full( mpi.MPIComm(), submesh_fes.GetVSize(), submesh_fes.GlobalVSize(), parent_trial_fes.GlobalVSize(), - submesh_J.GetI(), J.GetData(), submesh_J.GetData(), submesh_fes.GetDofOffsets(), - parent_trial_fes.GetDofOffsets() ); - ParSparseMatView P_submesh( submesh_fes.Dof_TrueDof_Matrix() ); - ParSparseMatView P_parent( parent_trial_fes.Dof_TrueDof_Matrix() ); - auto J_true = J_full.RAP( P_submesh, J_full, P_parent ); - - // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs - auto inactive_hpm = ParSparseMat::diagonalMatrix( J_true.get().GetComm(), J_true.get().GetGlobalNumRows(), - J_true.get().GetRowStarts(), 1.0, mortar_tdof_list_, false ); - - block_J->SetBlock( 0, 1, J_true.transpose().release() ); - block_J->SetBlock( 1, 0, J_true.release() ); - block_J->SetBlock( 1, 1, inactive_hpm.release() ); - - return block_J; -} - -// TODO: Merge with GetMfemBlockJacobian() to avoid code duplication -std::unique_ptr MfemJacobianData::GetMfemDfDxFullJacobian( const MethodData& method_data ) const -{ - // create block operator - auto block_J = std::make_unique( block_offsets_ ); - block_J->owns_blocks = 1; - - // these are Tribol element ids - auto mortar_elems = method_data.getBlockJElementIds()[static_cast( BlockSpace::MORTAR )]; - // convert them to redecomp element ids - const auto& elem_map_1 = parent_data_.GetElemMap1(); - for ( auto& mortar_elem : mortar_elems ) { - mortar_elem = elem_map_1[static_cast( mortar_elem )]; - } - - // these are Tribol element ids - auto nonmortar_elems = method_data.getBlockJElementIds()[static_cast( BlockSpace::NONMORTAR )]; - // convert them to redecomp element ids - const auto& elem_map_2 = parent_data_.GetElemMap2(); - for ( auto& nonmortar_elem : nonmortar_elems ) { - nonmortar_elem = elem_map_2[static_cast( nonmortar_elem )]; - } - - // these are Tribol element ids - auto lm_elems = method_data.getBlockJElementIds()[static_cast( BlockSpace::LAGRANGE_MULTIPLIER )]; - // convert them to redecomp element ids - for ( auto& lm_elem : lm_elems ) { - lm_elem = elem_map_2[static_cast( lm_elem )]; - } - - // transfer (0, 0) block (residual dof rows, displacement dof cols) - auto submesh_J = GetUpdateData().submesh_redecomp_xfer_00_->TransferToParallelSparse( - mortar_elems, mortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::MORTAR ), static_cast( BlockSpace::MORTAR ) ) ); - submesh_J += GetUpdateData().submesh_redecomp_xfer_00_->TransferToParallelSparse( - mortar_elems, nonmortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::MORTAR ), static_cast( BlockSpace::NONMORTAR ) ) ); - submesh_J += GetUpdateData().submesh_redecomp_xfer_00_->TransferToParallelSparse( - nonmortar_elems, mortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::NONMORTAR ), static_cast( BlockSpace::MORTAR ) ) ); - submesh_J += GetUpdateData().submesh_redecomp_xfer_00_->TransferToParallelSparse( - nonmortar_elems, nonmortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::NONMORTAR ), static_cast( BlockSpace::NONMORTAR ) ) ); - submesh_J.Finalize(); - auto submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_00_->ConvertToHypreParMatrix( submesh_J, false ); - // Matrix returned by mfem::RAP copies all existing data and owns its data - auto parent_J = ParSparseMatView::RAP( submesh_J_hypre.get(), *submesh_parent_vdof_xfer_ ); - block_J->SetBlock( 0, 0, - parent_J.RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ).release() ); - - // transfer (0, 1) block (residual dof rows, lagrange multiplier dof cols) - submesh_J = GetUpdateData().submesh_redecomp_xfer_01_->TransferToParallelSparse( - mortar_elems, lm_elems, - method_data.getBlockJ()( static_cast( BlockSpace::MORTAR ), - static_cast( BlockSpace::LAGRANGE_MULTIPLIER ) ) ); - submesh_J += GetUpdateData().submesh_redecomp_xfer_01_->TransferToParallelSparse( - nonmortar_elems, lm_elems, - method_data.getBlockJ()( static_cast( BlockSpace::NONMORTAR ), - static_cast( BlockSpace::LAGRANGE_MULTIPLIER ) ) ); - submesh_J.Finalize(); - submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_01_->ConvertToHypreParMatrix( submesh_J, false ); - parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); - block_J->SetBlock( 0, 1, - ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, - submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) - .release() ); - - // transfer (1, 0) block (gap dof rows, displacement dof cols) - submesh_J = GetUpdateData().submesh_redecomp_xfer_10_->TransferToParallelSparse( - lm_elems, mortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::LAGRANGE_MULTIPLIER ), - static_cast( BlockSpace::MORTAR ) ) ); - submesh_J += GetUpdateData().submesh_redecomp_xfer_10_->TransferToParallelSparse( - lm_elems, nonmortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::LAGRANGE_MULTIPLIER ), - static_cast( BlockSpace::NONMORTAR ) ) ); - submesh_J.Finalize(); - submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_10_->ConvertToHypreParMatrix( submesh_J, false ); - // Matrix returned by mfem::ParMult copies row and column starts since last arg is true. All other data is copied and - // owned by the new matrix. - parent_J = ParSparseMatView( submesh_J_hypre.get() ) * ( *submesh_parent_vdof_xfer_ ); - block_J->SetBlock( 1, 0, - ParSparseMat::RAP( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix(), parent_J, - parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ) - .release() ); - - // Create ones on diagonal of eliminated mortar tdofs, i.e. inactive dofs - auto& submesh_fes_full = submesh_data_.GetSubmeshFESpace(); - ParSparseMat inactive_hpm_full = - ParSparseMat::diagonalMatrix( TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), - submesh_fes_full.GetTrueDofOffsets(), 1.0, mortar_tdof_list_, false ); - inactive_hpm_full.get().SetOwnerFlags( 3, 3, 1 ); - block_J->SetBlock( 1, 1, inactive_hpm_full.release() ); - - return block_J; -} - -// TODO: Merge with GetMfemBlockJacobian() to avoid code duplication -std::unique_ptr MfemJacobianData::GetMfemDfDnJacobian( const MethodData& method_data ) const -{ - // create block operator - auto block_J = std::make_unique( block_offsets_, disp_offsets_ ); - block_J->owns_blocks = 1; - - // these are Tribol element ids - auto mortar_elems = method_data.getBlockJElementIds()[static_cast( BlockSpace::MORTAR )]; - // convert them to redecomp element ids - const auto& elem_map_1 = parent_data_.GetElemMap1(); - for ( auto& mortar_elem : mortar_elems ) { - mortar_elem = elem_map_1[static_cast( mortar_elem )]; - } - - // these are Tribol element ids - auto nonmortar_elems = method_data.getBlockJElementIds()[static_cast( BlockSpace::NONMORTAR )]; - // convert them to redecomp element ids - const auto& elem_map_2 = parent_data_.GetElemMap2(); - for ( auto& nonmortar_elem : nonmortar_elems ) { - nonmortar_elem = elem_map_2[static_cast( nonmortar_elem )]; - } - - // these are Tribol element ids - auto lm_elems = method_data.getBlockJElementIds()[static_cast( BlockSpace::LAGRANGE_MULTIPLIER )]; - // convert them to redecomp element ids - for ( auto& lm_elem : lm_elems ) { - lm_elem = elem_map_2[static_cast( lm_elem )]; - } - - // transfer (0, 0) block (residual dof rows, displacement dof cols) - auto submesh_J = GetUpdateData().submesh_redecomp_xfer_00_->TransferToParallelSparse( - mortar_elems, nonmortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::MORTAR ), static_cast( BlockSpace::NONMORTAR ) ) ); - submesh_J += GetUpdateData().submesh_redecomp_xfer_00_->TransferToParallelSparse( - nonmortar_elems, nonmortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::NONMORTAR ), static_cast( BlockSpace::NONMORTAR ) ) ); - submesh_J.Finalize(); - auto submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_00_->ConvertToHypreParMatrix( submesh_J, false ); - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); - block_J->SetBlock( 0, 0, parent_J.RAP( parent_P ).release() ); - - // transfer (1, 0) block (gap dof rows, displacement dof cols) - submesh_J = GetUpdateData().submesh_redecomp_xfer_10_->TransferToParallelSparse( - lm_elems, nonmortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::LAGRANGE_MULTIPLIER ), - static_cast( BlockSpace::NONMORTAR ) ) ); - submesh_J.Finalize(); - submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_10_->ConvertToHypreParMatrix( submesh_J, false ); - submesh_J_view = ParSparseMatView( submesh_J_hypre.get() ); - parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); - ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); - block_J->SetBlock( 1, 0, ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release() ); - - return block_J; -} - -// TODO: Merge with GetMfemBlockJacobian() to avoid code duplication -std::unique_ptr MfemJacobianData::GetMfemDnDxJacobian( const MethodData& method_data ) const -{ - // create block operator - auto block_J = std::make_unique( disp_offsets_, disp_offsets_ ); - block_J->owns_blocks = 1; - - // these are Tribol element ids - auto nonmortar_elems = method_data.getBlockJElementIds()[static_cast( BlockSpace::NONMORTAR )]; - // convert them to redecomp element ids - const auto& elem_map_2 = parent_data_.GetElemMap2(); - for ( auto& nonmortar_elem : nonmortar_elems ) { - nonmortar_elem = elem_map_2[static_cast( nonmortar_elem )]; - } - - // transfer (0, 0) block (residual dof rows, displacement dof cols) - auto submesh_J = GetUpdateData().submesh_redecomp_xfer_00_->TransferToParallelSparse( - nonmortar_elems, nonmortar_elems, - method_data.getBlockJ()( static_cast( BlockSpace::NONMORTAR ), static_cast( BlockSpace::NONMORTAR ) ) ); - submesh_J.Finalize(); - auto submesh_J_hypre = GetUpdateData().submesh_redecomp_xfer_00_->ConvertToHypreParMatrix( submesh_J, false ); - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); - block_J->SetBlock( 0, 0, parent_J.RAP( parent_P ).release() ); - - return block_J; -} - std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( const MethodData& method_data, const std::vector& row_spaces, const std::vector& col_spaces, diff --git a/src/tribol/mesh/MfemData.hpp b/src/tribol/mesh/MfemData.hpp index 02aaaffb..72b3d27a 100644 --- a/src/tribol/mesh/MfemData.hpp +++ b/src/tribol/mesh/MfemData.hpp @@ -1649,14 +1649,6 @@ class MfemJacobianData { */ void UpdateJacobianXfer(); - /** - * @brief Returns symmetric, off-diagonal Jacobian contributions as an mfem::BlockOperator - * - * @param method_data Method data holding element Jacobians - * @return std::unique_ptr - */ - std::unique_ptr GetMfemBlockJacobian( const MethodData* method_data ) const; - /** * @brief Returns a Jacobian as an mfem::BlockOperator * @@ -1673,31 +1665,6 @@ class MfemJacobianData { const std::vector& row_blocks, const std::vector& col_blocks ) const; - /** - * @brief Returns full, potentially non-symmetric derivative of the force w.r.t. nodal coordinates as an - * mfem::BlockOperator - * - * @param method_data Method data holding element Jacobians - * @return std::unique_ptr - */ - std::unique_ptr GetMfemDfDxFullJacobian( const MethodData& method_data ) const; - - /** - * @brief Returns the derivative of the force w.r.t. the normal direction as an mfem::BlockOperator - * - * @param method_data Method data holding element Jacobians - * @return std::unique_ptr - */ - std::unique_ptr GetMfemDfDnJacobian( const MethodData& method_data ) const; - - /** - * @brief Returns the derivative of the normal direction w.r.t. the nodal coordinates as an mfem::BlockOperator - * - * @param method_data Method data holding element Jacobians - * @return std::unique_ptr - */ - std::unique_ptr GetMfemDnDxJacobian( const MethodData& method_data ) const; - private: /** * @brief Creates and stores data that changes when the redecomp mesh is From 56f1b5689bc994ecf5acfef25f383d98016849d7 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Mon, 26 Jan 2026 23:55:40 -0800 Subject: [PATCH 08/33] cleanup --- src/tests/tribol_par_sparse_mat.cpp | 2 +- src/tribol/mesh/MfemData.cpp | 2 -- src/tribol/utils/ParSparseMat.cpp | 9 +++++---- src/tribol/utils/ParSparseMat.hpp | 6 ++++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/tribol_par_sparse_mat.cpp index bea50eec..62fd41ef 100644 --- a/src/tests/tribol_par_sparse_mat.cpp +++ b/src/tests/tribol_par_sparse_mat.cpp @@ -393,4 +393,4 @@ int main( int argc, char* argv[] ) MPI_Finalize(); return result; -} \ No newline at end of file +} diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 52e8a57f..3503ad65 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -1056,8 +1056,6 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( con ParSparseMat inactive_hpm_full = ParSparseMat::diagonalMatrix( TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), submesh_fes_full.GetTrueDofOffsets(), 1.0, mortar_tdof_list_, false ); - inactive_hpm_full.get().SetOwnerFlags( 3, inactive_hpm_full.get().OwnsOffd(), - inactive_hpm_full.get().OwnsColMap() ); if ( block_J->IsZeroBlock( 1, 1 ) ) { block_J->SetBlock( 1, 1, inactive_hpm_full.release() ); diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index f5e842a8..9ba8ea40 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -86,8 +86,7 @@ ParSparseMat::ParSparseMat( std::unique_ptr mat ) { } -ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, - mfem::SparseMatrix&& diag ) +ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ) : ParSparseMatView( nullptr ) { m_owned_mat = std::make_unique( comm, glob_size, row_starts, &diag ); @@ -95,7 +94,8 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* diag.GetMemoryI().ClearOwnerFlags(); diag.GetMemoryJ().ClearOwnerFlags(); diag.GetMemoryData().ClearOwnerFlags(); - m_owned_mat->SetOwnerFlags( -1, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); + auto hypre_owned_arrays = -1; + m_owned_mat->SetOwnerFlags( hypre_owned_arrays, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); } ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept @@ -188,7 +188,8 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si mfem::Array row_starts_copy = row_starts; auto mat = std::make_unique( comm, global_size, row_starts_copy, &inactive_diag ); mat->CopyRowStarts(); - mat->SetOwnerFlags( -1, mat->OwnsOffd(), mat->OwnsColMap() ); + auto hypre_owned_arrays = -1; + mat->SetOwnerFlags( hypre_owned_arrays, mat->OwnsOffd(), mat->OwnsColMap() ); return ParSparseMat( std::move( mat ) ); } diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp index ecc8a6cb..8ac1c557 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/tribol/utils/ParSparseMat.hpp @@ -213,7 +213,8 @@ class ParSparseMat : public ParSparseMatView { * @param row_starts Row partitioning (global offsets) * @param diag_val Value for the diagonal entries * @param ordered_rows Sorted array of local row indices. Defaults to empty. - * @param skip_rows If true (default), ordered_rows are skipped (zero entries). If false, ordered_rows are the only entries. + * @param skip_rows If true (default), ordered_rows are skipped (zero entries). If false, ordered_rows are the only + * entries. * @return ParSparseMat The constructed diagonal matrix */ static ParSparseMat diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, @@ -229,7 +230,8 @@ class ParSparseMat : public ParSparseMatView { * @param row_starts Row partitioning (global offsets) * @param diag_val Value for the diagonal entries * @param ordered_rows Sorted array of local row indices. Defaults to empty. - * @param skip_rows If true (default), ordered_rows are skipped (zero entries). If false, ordered_rows are the only entries. + * @param skip_rows If true (default), ordered_rows are skipped (zero entries). If false, ordered_rows are the only + * entries. * @return ParSparseMat The constructed diagonal matrix */ static ParSparseMat diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, HYPRE_BigInt* row_starts, From 9c4580640ac5cf3eeae93e5f2e7a7819bad45225 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Tue, 27 Jan 2026 09:21:07 -0800 Subject: [PATCH 09/33] set ownership to mfem --- src/tribol/utils/ParSparseMat.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index 9ba8ea40..27bb6fa7 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -94,8 +94,8 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* diag.GetMemoryI().ClearOwnerFlags(); diag.GetMemoryJ().ClearOwnerFlags(); diag.GetMemoryData().ClearOwnerFlags(); - auto hypre_owned_arrays = -1; - m_owned_mat->SetOwnerFlags( hypre_owned_arrays, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); + auto mfem_owned_arrays = 3; + m_owned_mat->SetOwnerFlags( mfem_owned_arrays, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); } ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept @@ -188,8 +188,8 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si mfem::Array row_starts_copy = row_starts; auto mat = std::make_unique( comm, global_size, row_starts_copy, &inactive_diag ); mat->CopyRowStarts(); - auto hypre_owned_arrays = -1; - mat->SetOwnerFlags( hypre_owned_arrays, mat->OwnsOffd(), mat->OwnsColMap() ); + auto mfem_owned_arrays = 3; + mat->SetOwnerFlags( mfem_owned_arrays, mat->OwnsOffd(), mat->OwnsColMap() ); return ParSparseMat( std::move( mat ) ); } From f9272b8332fc34a04254c716728d5b2a0b64fc1f Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Tue, 27 Jan 2026 10:11:17 -0800 Subject: [PATCH 10/33] simplify getmfemblockjacobian --- src/tribol/mesh/MfemData.cpp | 163 +++++++++++++++-------------------- src/tribol/mesh/MfemData.hpp | 25 ++---- 2 files changed, 79 insertions(+), 109 deletions(-) diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 3503ad65..eb9b1424 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -911,20 +911,18 @@ void MfemJacobianData::UpdateJacobianXfer() update_data_ = std::make_unique( parent_data_, submesh_data_ ); } -std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( const MethodData& method_data, - const std::vector& row_spaces, - const std::vector& col_spaces, - const std::vector& row_blocks, - const std::vector& col_blocks ) const +std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( + const MethodData& method_data, const std::vector>& row_info, + const std::vector>& col_info ) const { // Determine block structure int max_row_block = 0; - for ( auto b : row_blocks ) { - if ( b > max_row_block ) max_row_block = b; + for ( auto info : row_info ) { + if ( info.first > max_row_block ) max_row_block = info.first; } int max_col_block = 0; - for ( auto b : col_blocks ) { - if ( b > max_col_block ) max_col_block = b; + for ( auto info : col_info ) { + if ( info.first > max_col_block ) max_col_block = info.first; } const mfem::Array& row_offsets = ( max_row_block == 0 ) ? disp_offsets_ : block_offsets_; @@ -933,31 +931,21 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( con auto block_J = std::make_unique( row_offsets, col_offsets ); block_J->owns_blocks = 1; - // Map unique (r_blk, c_blk) -> list of (row_space, col_space) pairs - std::map, std::vector>> block_contribs; - - for ( size_t i = 0; i < row_spaces.size(); ++i ) { - for ( size_t j = 0; j < col_spaces.size(); ++j ) { - int r_blk = row_blocks[i]; - int c_blk = col_blocks[j]; - block_contribs[{ r_blk, c_blk }].push_back( { row_spaces[i], col_spaces[j] } ); - } + std::vector>> submesh_matrices( max_row_block + 1 ); + for ( auto& row : submesh_matrices ) { + row.resize( max_col_block + 1 ); } - const auto& elem_map_1 = parent_data_.GetElemMap1(); - const auto& elem_map_2 = parent_data_.GetElemMap2(); - - // Iterate over unique blocks - for ( const auto& entry : block_contribs ) { - int r_blk = entry.first.first; - int c_blk = entry.first.second; - const auto& contribs = entry.second; + const std::vector*> elem_map_by_space{ &parent_data_.GetElemMap1(), &parent_data_.GetElemMap2() }; - std::unique_ptr submesh_J; + // Iterate over blocks + for ( const auto& r_pair : row_info ) { + int r_blk = r_pair.first; + BlockSpace rs = r_pair.second; - for ( const auto& pair : contribs ) { - BlockSpace rs = pair.first; - BlockSpace cs = pair.second; + for ( const auto& c_pair : col_info ) { + int c_blk = c_pair.first; + BlockSpace cs = c_pair.second; // Get block from method_data const auto& J_block = method_data.getBlockJ()( static_cast( rs ), static_cast( cs ) ); @@ -967,88 +955,78 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( con ArrayT row_redecomp_ids; row_redecomp_ids.reserve( row_elem_ids_tribol.size() ); for ( auto id : row_elem_ids_tribol ) { - if ( rs == BlockSpace::MORTAR ) { - row_redecomp_ids.push_back( elem_map_1[static_cast( id )] ); - } else { - row_redecomp_ids.push_back( elem_map_2[static_cast( id )] ); - } + row_redecomp_ids.push_back( ( *elem_map_by_space[static_cast( rs )] )[static_cast( id )] ); } const auto& col_elem_ids_tribol = method_data.getBlockJElementIds()[static_cast( cs )]; ArrayT col_redecomp_ids; col_redecomp_ids.reserve( col_elem_ids_tribol.size() ); for ( auto id : col_elem_ids_tribol ) { - if ( cs == BlockSpace::MORTAR ) { - col_redecomp_ids.push_back( elem_map_1[static_cast( id )] ); - } else { - col_redecomp_ids.push_back( elem_map_2[static_cast( id )] ); - } + col_redecomp_ids.push_back( ( *elem_map_by_space[static_cast( rs )] )[static_cast( id )] ); } // Pick transfer object redecomp::MatrixTransfer* xfer = nullptr; - if ( r_blk == 0 && c_blk == 0 ) - xfer = GetUpdateData().submesh_redecomp_xfer_00_.get(); - else if ( r_blk == 0 && c_blk == 1 ) - xfer = GetUpdateData().submesh_redecomp_xfer_01_.get(); - else if ( r_blk == 1 && c_blk == 0 ) - xfer = GetUpdateData().submesh_redecomp_xfer_10_.get(); - else + if ( r_blk < GetUpdateData().submesh_redecomp_xfer_.shape()[0] && + c_blk < GetUpdateData().submesh_redecomp_xfer_.shape()[1] ) { + xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); + } + + if ( !xfer ) { continue; + } auto J_contrib = xfer->TransferToParallelSparse( row_redecomp_ids, col_redecomp_ids, J_block ); - if ( !submesh_J ) { - submesh_J = std::make_unique( std::move( J_contrib ) ); + if ( !submesh_matrices[r_blk][c_blk] ) { + submesh_matrices[r_blk][c_blk] = std::make_unique( std::move( J_contrib ) ); } else { - ( *submesh_J ) += J_contrib; + ( *submesh_matrices[r_blk][c_blk] ) += J_contrib; } } + } - if ( submesh_J ) { - submesh_J->Finalize(); + for ( int r_blk = 0; r_blk <= max_row_block; ++r_blk ) { + for ( int c_blk = 0; c_blk <= max_col_block; ++c_blk ) { + if ( submesh_matrices[r_blk][c_blk] ) { + submesh_matrices[r_blk][c_blk]->Finalize(); + + // Pick xfer again for conversion + redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); + + auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( *submesh_matrices[r_blk][c_blk], false ); + + mfem::HypreParMatrix* block_mat = nullptr; + + if ( r_blk == 0 && c_blk == 0 ) { + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_mat = parent_J.RAP( parent_P ).release(); + } else if ( r_blk == 0 && c_blk == 1 ) { + auto parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); + block_mat = ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, + submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) + .release(); + } else if ( r_blk == 1 && c_blk == 0 ) { + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); + ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_mat = ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release(); + } - // Pick xfer again for conversion - redecomp::MatrixTransfer* xfer = nullptr; - if ( r_blk == 0 && c_blk == 0 ) - xfer = GetUpdateData().submesh_redecomp_xfer_00_.get(); - else if ( r_blk == 0 && c_blk == 1 ) - xfer = GetUpdateData().submesh_redecomp_xfer_01_.get(); - else if ( r_blk == 1 && c_blk == 0 ) - xfer = GetUpdateData().submesh_redecomp_xfer_10_.get(); - - auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( *submesh_J, false ); - - mfem::HypreParMatrix* block_mat = nullptr; - - if ( r_blk == 0 && c_blk == 0 ) { - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); - block_mat = parent_J.RAP( parent_P ).release(); - } else if ( r_blk == 0 && c_blk == 1 ) { - auto parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); - block_mat = ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, - submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) - .release(); - } else if ( r_blk == 1 && c_blk == 0 ) { - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); - ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); - block_mat = ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release(); + block_J->SetBlock( r_blk, c_blk, block_mat ); } - - block_J->SetBlock( r_blk, c_blk, block_mat ); } } // Handle Inactive DOFs for (1, 1) bool has_11 = false; - for ( auto rb : row_blocks ) - if ( rb == 1 ) has_11 = true; + for ( auto rb : row_info ) + if ( rb.first == 1 ) has_11 = true; bool col_has_1 = false; - for ( auto cb : col_blocks ) - if ( cb == 1 ) col_has_1 = true; + for ( auto cb : col_info ) + if ( cb.first == 1 ) col_has_1 = true; has_11 = has_11 && col_has_1; if ( has_11 ) { @@ -1066,6 +1044,7 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( con } MfemJacobianData::UpdateData::UpdateData( const MfemMeshData& parent_data, const MfemSubmeshData& submesh_data ) + : submesh_redecomp_xfer_( 2, 2 ) { auto dual_submesh_fes = &submesh_data.GetSubmeshFESpace(); auto primal_submesh_fes = &parent_data.GetSubmeshFESpace(); @@ -1074,13 +1053,13 @@ MfemJacobianData::UpdateData::UpdateData( const MfemMeshData& parent_data, const primal_submesh_fes = parent_data.GetLORMeshFESpace(); } // create a matrix transfer operator for moving data from redecomp to the submesh - submesh_redecomp_xfer_00_ = std::make_unique( + submesh_redecomp_xfer_( 0, 0 ) = std::make_unique( *primal_submesh_fes, *primal_submesh_fes, *parent_data.GetRedecompResponse().FESpace(), *parent_data.GetRedecompResponse().FESpace() ); - submesh_redecomp_xfer_01_ = std::make_unique( *primal_submesh_fes, *dual_submesh_fes, - *parent_data.GetRedecompResponse().FESpace(), - *submesh_data.GetRedecompGap().FESpace() ); - submesh_redecomp_xfer_10_ = std::make_unique( + submesh_redecomp_xfer_( 0, 1 ) = std::make_unique( + *primal_submesh_fes, *dual_submesh_fes, *parent_data.GetRedecompResponse().FESpace(), + *submesh_data.GetRedecompGap().FESpace() ); + submesh_redecomp_xfer_( 1, 0 ) = std::make_unique( *dual_submesh_fes, *primal_submesh_fes, *submesh_data.GetRedecompGap().FESpace(), *parent_data.GetRedecompResponse().FESpace() ); } diff --git a/src/tribol/mesh/MfemData.hpp b/src/tribol/mesh/MfemData.hpp index 72b3d27a..3fec7f7a 100644 --- a/src/tribol/mesh/MfemData.hpp +++ b/src/tribol/mesh/MfemData.hpp @@ -12,6 +12,7 @@ #ifdef BUILD_REDECOMP #include +#include #include #include "mfem.hpp" @@ -1659,11 +1660,9 @@ class MfemJacobianData { * @param col_blocks List of column block indices corresponding to col_spaces * @return std::unique_ptr */ - std::unique_ptr GetMfemBlockJacobian( const MethodData& method_data, - const std::vector& row_spaces, - const std::vector& col_spaces, - const std::vector& row_blocks, - const std::vector& col_blocks ) const; + std::unique_ptr GetMfemBlockJacobian( + const MethodData& method_data, const std::vector>& row_info, + const std::vector>& col_info ) const; private: /** @@ -1680,19 +1679,11 @@ class MfemJacobianData { UpdateData( const MfemMeshData& parent_data, const MfemSubmeshData& submesh_data ); /** - * @brief Redecomp to parent-linked boundary submesh transfer operator, (displacement, displacement) block - */ - std::unique_ptr submesh_redecomp_xfer_00_; - - /** - * @brief Redecomp to parent-linked boundary submesh transfer operator, (displacement, pressure) block - */ - std::unique_ptr submesh_redecomp_xfer_01_; - - /** - * @brief Redecomp to parent-linked boundary submesh transfer operator, (pressure, displacement) block + * @brief Redecomp to parent-linked boundary submesh transfer operators + * + * @note Indexed by (row_block, col_block) */ - std::unique_ptr submesh_redecomp_xfer_10_; + Array2D> submesh_redecomp_xfer_; }; /** From acda49b5ba6522c6c67dc63ee2faf5029ee6eeb3 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Tue, 27 Jan 2026 16:26:58 -0800 Subject: [PATCH 11/33] reintroduce map for iterating over active spaces --- src/tribol/interface/mfem_tribol.cpp | 23 +++-- src/tribol/mesh/MfemData.cpp | 124 ++++++++++++++------------- src/tribol/mesh/MfemData.hpp | 9 +- 3 files changed, 80 insertions(+), 76 deletions(-) diff --git a/src/tribol/interface/mfem_tribol.cpp b/src/tribol/interface/mfem_tribol.cpp index f5787aa1..95663a8d 100644 --- a/src/tribol/interface/mfem_tribol.cpp +++ b/src/tribol/interface/mfem_tribol.cpp @@ -335,18 +335,16 @@ std::unique_ptr getMfemBlockJacobian( IndexT cs_id ) cs_id ) ); // creates a block Jacobian on the parent mesh/parent-linked boundary submesh based on the element Jacobians stored in // the coupling scheme's method data - const std::vector all_spaces{ BlockSpace::MORTAR, BlockSpace::NONMORTAR, - BlockSpace::LAGRANGE_MULTIPLIER }; - const std::vector all_blocks{ 0, 0, 1 }; + const std::vector> all_info{ { 0, BlockSpace::MORTAR }, + { 0, BlockSpace::NONMORTAR }, + { 1, BlockSpace::LAGRANGE_MULTIPLIER } }; if ( cs->isEnzymeEnabled() ) { - auto dfdx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getMethodData(), all_spaces, all_spaces, - all_blocks, all_blocks ); - const std::vector nonmortar_space{ BlockSpace::NONMORTAR }; - const std::vector disp_block{ 0 }; - auto dfdn = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDfDnMethodData(), all_spaces, nonmortar_space, - all_blocks, disp_block ); - auto dndx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDnDxMethodData(), nonmortar_space, - nonmortar_space, disp_block, disp_block ); + auto dfdx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getMethodData(), all_info, all_info ); + const std::vector> nonmortar_info{ { 0, BlockSpace::NONMORTAR } }; + auto dfdn = + cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDfDnMethodData(), all_info, nonmortar_info ); + auto dndx = + cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDnDxMethodData(), nonmortar_info, nonmortar_info ); auto block_00 = ( ParSparseMatView( &static_cast( dfdn->GetBlock( 0, 0 ) ) ) * &static_cast( dndx->GetBlock( 0, 0 ) ) ) + @@ -360,8 +358,7 @@ std::unique_ptr getMfemBlockJacobian( IndexT cs_id ) return dfdx; } else { - return cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getMethodData(), all_spaces, all_spaces, all_blocks, - all_blocks ); + return cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getMethodData(), all_info, all_info ); } } diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index eb9b1424..34182446 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -9,6 +9,8 @@ #ifdef BUILD_REDECOMP +#include + #include "axom/slic.hpp" #include "shared/infrastructure/Profiling.hpp" @@ -925,27 +927,40 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( if ( info.first > max_col_block ) max_col_block = info.first; } + SLIC_ERROR_ROOT_IF( max_row_block > GetUpdateData().submesh_redecomp_xfer_.shape()[0] || + max_col_block > GetUpdateData().submesh_redecomp_xfer_.shape()[1], + axom::fmt::format( "No transfer object for row {0} and col {1}", max_row_block, max_col_block ) ); + const mfem::Array& row_offsets = ( max_row_block == 0 ) ? disp_offsets_ : block_offsets_; const mfem::Array& col_offsets = ( max_col_block == 0 ) ? disp_offsets_ : block_offsets_; auto block_J = std::make_unique( row_offsets, col_offsets ); block_J->owns_blocks = 1; - std::vector>> submesh_matrices( max_row_block + 1 ); - for ( auto& row : submesh_matrices ) { - row.resize( max_col_block + 1 ); + // Map unique (r_blk, c_blk) -> list of (row_space, col_space) pairs + std::map, std::vector>> block_contribs; + + for ( const auto& r_pair : row_info ) { + for ( const auto& c_pair : col_info ) { + block_contribs[{ r_pair.first, c_pair.first }].push_back( { r_pair.second, c_pair.second } ); + } } - const std::vector*> elem_map_by_space{ &parent_data_.GetElemMap1(), &parent_data_.GetElemMap2() }; + // Maps BlockSpaces (MORTAR, NONMORTAR, LAGRANGE_MULTIPLIER) to a tribol element map + const std::vector*> elem_map_by_space{ &parent_data_.GetElemMap1(), &parent_data_.GetElemMap2(), + &parent_data_.GetElemMap2() }; - // Iterate over blocks - for ( const auto& r_pair : row_info ) { - int r_blk = r_pair.first; - BlockSpace rs = r_pair.second; + // Iterate over unique blocks + for ( const auto& entry : block_contribs ) { + int r_blk = entry.first.first; + int c_blk = entry.first.second; + const auto& contribs = entry.second; - for ( const auto& c_pair : col_info ) { - int c_blk = c_pair.first; - BlockSpace cs = c_pair.second; + std::unique_ptr submesh_J; + + for ( const auto& pair : contribs ) { + BlockSpace rs = pair.first; + BlockSpace cs = pair.second; // Get block from method_data const auto& J_block = method_data.getBlockJ()( static_cast( rs ), static_cast( cs ) ); @@ -962,61 +977,52 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( ArrayT col_redecomp_ids; col_redecomp_ids.reserve( col_elem_ids_tribol.size() ); for ( auto id : col_elem_ids_tribol ) { - col_redecomp_ids.push_back( ( *elem_map_by_space[static_cast( rs )] )[static_cast( id )] ); + col_redecomp_ids.push_back( ( *elem_map_by_space[static_cast( cs )] )[static_cast( id )] ); } // Pick transfer object - redecomp::MatrixTransfer* xfer = nullptr; - if ( r_blk < GetUpdateData().submesh_redecomp_xfer_.shape()[0] && - c_blk < GetUpdateData().submesh_redecomp_xfer_.shape()[1] ) { - xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); - } - - if ( !xfer ) { - continue; - } - - auto J_contrib = xfer->TransferToParallelSparse( row_redecomp_ids, col_redecomp_ids, J_block ); - if ( !submesh_matrices[r_blk][c_blk] ) { - submesh_matrices[r_blk][c_blk] = std::make_unique( std::move( J_contrib ) ); - } else { - ( *submesh_matrices[r_blk][c_blk] ) += J_contrib; + redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); + + // No transfer object for LAGRANGE_MULTIPLER, LAGRANGE_MULTIPLIER block + if ( xfer != nullptr ) { + auto J_contrib = xfer->TransferToParallelSparse( row_redecomp_ids, col_redecomp_ids, J_block ); + if ( !submesh_J ) { + submesh_J = std::make_unique( std::move( J_contrib ) ); + } else { + ( *submesh_J ) += J_contrib; + } } } - } - - for ( int r_blk = 0; r_blk <= max_row_block; ++r_blk ) { - for ( int c_blk = 0; c_blk <= max_col_block; ++c_blk ) { - if ( submesh_matrices[r_blk][c_blk] ) { - submesh_matrices[r_blk][c_blk]->Finalize(); - - // Pick xfer again for conversion - redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); - - auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( *submesh_matrices[r_blk][c_blk], false ); - - mfem::HypreParMatrix* block_mat = nullptr; - - if ( r_blk == 0 && c_blk == 0 ) { - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); - block_mat = parent_J.RAP( parent_P ).release(); - } else if ( r_blk == 0 && c_blk == 1 ) { - auto parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); - block_mat = ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, - submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) - .release(); - } else if ( r_blk == 1 && c_blk == 0 ) { - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); - ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); - block_mat = ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release(); - } - block_J->SetBlock( r_blk, c_blk, block_mat ); + if ( submesh_J ) { + submesh_J->Finalize(); + + // Pick xfer again for conversion + redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); + + auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( *submesh_J, false ); + + mfem::HypreParMatrix* block_mat = nullptr; + + if ( r_blk == 0 && c_blk == 0 ) { + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_mat = parent_J.RAP( parent_P ).release(); + } else if ( r_blk == 0 && c_blk == 1 ) { + auto parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); + block_mat = ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, + submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) + .release(); + } else if ( r_blk == 1 && c_blk == 0 ) { + ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); + auto parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); + ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); + ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_mat = ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release(); } + + block_J->SetBlock( r_blk, c_blk, block_mat ); } } diff --git a/src/tribol/mesh/MfemData.hpp b/src/tribol/mesh/MfemData.hpp index 3fec7f7a..17798cce 100644 --- a/src/tribol/mesh/MfemData.hpp +++ b/src/tribol/mesh/MfemData.hpp @@ -1654,10 +1654,11 @@ class MfemJacobianData { * @brief Returns a Jacobian as an mfem::BlockOperator * * @param method_data Method data holding element Jacobians - * @param row_spaces List of BlockSpaces for the rows - * @param col_spaces List of BlockSpaces for the columns - * @param row_blocks List of row block indices corresponding to row_spaces - * @param col_blocks List of column block indices corresponding to col_spaces + * @param row_info List of {block_row_index, BlockSpace} pairs. Since a single block row in the output matrix might + * aggregate DOFs from multiple Tribol spaces (e.g. Mortar and NonMortar spaces might both map to the Displacement + * block 0), this vector defines the mapping from each Tribol space to its corresponding block row index. + * @param col_info List of {block_col_index, BlockSpace} pairs. Similar to row_info, this defines the mapping from + * each Tribol space to its corresponding block column index. * @return std::unique_ptr */ std::unique_ptr GetMfemBlockJacobian( From 51782b33a04793df278dc939e4cc5804554a05ac Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Tue, 27 Jan 2026 16:46:53 -0800 Subject: [PATCH 12/33] eof newline --- src/tribol/utils/ParSparseMat.cpp | 2 +- src/tribol/utils/ParSparseMat.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index 27bb6fa7..6f862663 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -203,4 +203,4 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si return diagonalMatrix( comm, global_size, row_starts_array, diag_val, ordered_rows, skip_rows ); } -} // namespace tribol \ No newline at end of file +} // namespace tribol diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp index 8ac1c557..08384279 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/tribol/utils/ParSparseMat.hpp @@ -244,4 +244,4 @@ class ParSparseMat : public ParSparseMatView { } // namespace tribol -#endif /* SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ */ \ No newline at end of file +#endif /* SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ */ From d683295215b12cc4388cc97f26b5b3c362f61936 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Fri, 6 Feb 2026 10:26:31 -0800 Subject: [PATCH 13/33] testing changes --- src/tests/tribol_par_sparse_mat.cpp | 10 ++-- src/tribol/utils/ParSparseMat.cpp | 82 +++++++++++++++++++---------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/tribol_par_sparse_mat.cpp index 62fd41ef..05f18610 100644 --- a/src/tests/tribol_par_sparse_mat.cpp +++ b/src/tests/tribol_par_sparse_mat.cpp @@ -3,16 +3,18 @@ // // SPDX-License-Identifier: (MIT) -#include - -#include "tribol/utils/ParSparseMat.hpp" #include "tribol/config.hpp" -#include "mfem.hpp" + +#include #ifdef TRIBOL_USE_MPI #include #endif +#include "mfem.hpp" + +#include "tribol/utils/ParSparseMat.hpp" + class ParSparseMatTest : public ::testing::Test { protected: mfem::Array GetRowStarts( MPI_Comm comm, HYPRE_BigInt global_size ) diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index 6f862663..ab6c5f07 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -151,46 +151,72 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si num_local_rows = static_cast( row_starts[rank + 1] - row_starts[rank] ); } - int num_ordered_rows = ordered_rows.Size(); - int num_diag_entries = skip_rows ? ( num_local_rows - num_ordered_rows ) : num_ordered_rows; - - mfem::Array rows( num_local_rows + 1 ); - mfem::Array cols( num_diag_entries ); - rows[0] = 0; + // NOTE: mfem::HypreParMatrix(MPI_Comm, HYPRE_BigInt, HYPRE_BigInt*, SparseMatrix*) does not take ownership + // of the provided CSR arrays (see MFEM docs). To avoid dangling pointers and allocator mismatches, build + // the ParCSR data using the HypreParMatrix constructor that takes ownership of raw arrays allocated with + // new[]. + + const int num_ordered_rows = ordered_rows.Size(); + if ( num_local_rows < 0 ) { + num_local_rows = 0; + } - int diag_entry_ct = 0; + // Count selected diagonal entries in a first pass (do not rely on ordered_rows being unique/in-range). + HYPRE_Int num_diag_entries = 0; int ordered_idx = 0; - for ( int i{ 0 }; i < num_local_rows; ++i ) { - bool is_ordered = ( ordered_idx < num_ordered_rows && ordered_rows[ordered_idx] == i ); - bool add_entry = skip_rows ? !is_ordered : is_ordered; - + for ( int i = 0; i < num_local_rows; ++i ) { + while ( ordered_idx < num_ordered_rows && ordered_rows[ordered_idx] < i ) { + ++ordered_idx; + } + const bool is_in_list = ( ordered_idx < num_ordered_rows && ordered_rows[ordered_idx] == i ); + const bool add_entry = skip_rows ? !is_in_list : is_in_list; if ( add_entry ) { - cols[diag_entry_ct] = i; - ++diag_entry_ct; + ++num_diag_entries; } - rows[i + 1] = diag_entry_ct; + } + + auto* diag_i = new HYPRE_Int[num_local_rows + 1]; + auto* diag_j = ( num_diag_entries > 0 ) ? new HYPRE_Int[num_diag_entries] : nullptr; + auto* diag_data = ( num_diag_entries > 0 ) ? new mfem::real_t[num_diag_entries] : nullptr; - if ( is_ordered ) { + // No off-diagonal entries for a purely diagonal matrix. + auto* offd_i = new HYPRE_Int[num_local_rows + 1]; + auto* offd_j = static_cast( nullptr ); + auto* offd_data = static_cast( nullptr ); + auto* offd_col_map = static_cast( nullptr ); + + diag_i[0] = 0; + for ( int i = 0; i < num_local_rows + 1; ++i ) { + offd_i[i] = 0; + } + + HYPRE_Int diag_entry_ct = 0; + ordered_idx = 0; + for ( int i = 0; i < num_local_rows; ++i ) { + while ( ordered_idx < num_ordered_rows && ordered_rows[ordered_idx] < i ) { ++ordered_idx; } + const bool is_in_list = ( ordered_idx < num_ordered_rows && ordered_rows[ordered_idx] == i ); + const bool add_entry = skip_rows ? !is_in_list : is_in_list; + + if ( add_entry ) { + diag_j[diag_entry_ct] = static_cast( i ); + diag_data[diag_entry_ct] = diag_val; + ++diag_entry_ct; + } + diag_i[i + 1] = diag_entry_ct; } - rows.GetMemory().SetHostPtrOwner( false ); - cols.GetMemory().SetHostPtrOwner( false ); - mfem::Vector vals( num_diag_entries ); - vals = diag_val; - vals.GetMemory().SetHostPtrOwner( false ); - mfem::SparseMatrix inactive_diag( rows.GetData(), cols.GetData(), vals.GetData(), num_local_rows, num_local_rows, - false, false, true ); - // if the size of vals is zero, SparseMatrix creates its own memory which it owns. explicitly prevent this... - inactive_diag.SetDataOwner( false ); // copy row_starts to a new array mfem::Array row_starts_copy = row_starts; - auto mat = std::make_unique( comm, global_size, row_starts_copy, &inactive_diag ); - mat->CopyRowStarts(); + auto diag_hpm = std::make_unique( comm, global_size, global_size, row_starts_copy.GetData(), + row_starts_copy.GetData(), diag_i, diag_j, diag_data, offd_i, + offd_j, offd_data, 0, offd_col_map, true ); + diag_hpm->CopyRowStarts(); + diag_hpm->CopyColStarts(); auto mfem_owned_arrays = 3; - mat->SetOwnerFlags( mfem_owned_arrays, mat->OwnsOffd(), mat->OwnsColMap() ); - return ParSparseMat( std::move( mat ) ); + diag_hpm->SetOwnerFlags( mfem_owned_arrays, diag_hpm->OwnsOffd(), diag_hpm->OwnsColMap() ); + return ParSparseMat( std::move( diag_hpm ) ); } ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_size, HYPRE_BigInt* row_starts, From d2d9ccb86b332e0f06f1cabf55674b0a90555dc0 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Fri, 6 Feb 2026 22:25:58 -0800 Subject: [PATCH 14/33] small bugfix, update comment --- src/tests/tribol_par_sparse_mat.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/tribol_par_sparse_mat.cpp index 05f18610..cbcab749 100644 --- a/src/tests/tribol_par_sparse_mat.cpp +++ b/src/tests/tribol_par_sparse_mat.cpp @@ -257,7 +257,8 @@ TEST_F( ParSparseMatTest, Elimination ) // Eliminate row 0 (globally) // Determine if I own row 0 mfem::Array rows_to_elim; - if ( row_starts[rank] == 0 ) { + int row_starts_idx = HYPRE_AssumedPartitionCheck() ? 0 : rank; + if ( row_starts[row_starts_idx] == 0 ) { rows_to_elim.Append( 0 ); } A.EliminateRows( rows_to_elim ); @@ -268,7 +269,7 @@ TEST_F( ParSparseMatTest, Elimination ) x = 1.0; y = A * x; // y = A * x - // if rank owns row 0, the result for that row should be 1.0 * x[0] = 1.0 (since diag is 1.0) + // if rank owns row 0, the result for that row should be 0.0 * x[0] = 0.0 (since diag is 0.0) // other rows should be 3.0 // local row 0 on rank 0 is global row 0 From 8e067b5696fe4cc8408c7e41c7f233bf69ecfa2e Mon Sep 17 00:00:00 2001 From: EB Chin Date: Fri, 6 Feb 2026 22:26:37 -0800 Subject: [PATCH 15/33] set hypre mem location so GPU builds with host data work --- src/tribol/utils/ParSparseMat.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index ab6c5f07..d30f00e9 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -4,6 +4,7 @@ // SPDX-License-Identifier: (MIT) #include "tribol/utils/ParSparseMat.hpp" +#include namespace tribol { @@ -89,6 +90,10 @@ ParSparseMat::ParSparseMat( std::unique_ptr mat ) ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ) : ParSparseMatView( nullptr ) { + // ParSparseMat is host only now. Make sure CSR data is copied on host in the constructor. + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation(&old_hypre_mem_location); + HYPRE_SetMemoryLocation(HYPRE_MEMORY_HOST); m_owned_mat = std::make_unique( comm, glob_size, row_starts, &diag ); m_mat = m_owned_mat.get(); diag.GetMemoryI().ClearOwnerFlags(); @@ -96,6 +101,8 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* diag.GetMemoryData().ClearOwnerFlags(); auto mfem_owned_arrays = 3; m_owned_mat->SetOwnerFlags( mfem_owned_arrays, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); + // Return hypre's memory location to what it was before + HYPRE_SetMemoryLocation(old_hypre_mem_location); } ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept From fbc00aa17dd92715d2c3668b9d23c143bf783768 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Fri, 6 Feb 2026 22:29:23 -0800 Subject: [PATCH 16/33] formatting --- src/tribol/interface/mfem_tribol.cpp | 8 +++----- src/tribol/utils/ParSparseMat.cpp | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/tribol/interface/mfem_tribol.cpp b/src/tribol/interface/mfem_tribol.cpp index 95663a8d..9f29bcd9 100644 --- a/src/tribol/interface/mfem_tribol.cpp +++ b/src/tribol/interface/mfem_tribol.cpp @@ -335,14 +335,12 @@ std::unique_ptr getMfemBlockJacobian( IndexT cs_id ) cs_id ) ); // creates a block Jacobian on the parent mesh/parent-linked boundary submesh based on the element Jacobians stored in // the coupling scheme's method data - const std::vector> all_info{ { 0, BlockSpace::MORTAR }, - { 0, BlockSpace::NONMORTAR }, - { 1, BlockSpace::LAGRANGE_MULTIPLIER } }; + const std::vector> all_info{ + { 0, BlockSpace::MORTAR }, { 0, BlockSpace::NONMORTAR }, { 1, BlockSpace::LAGRANGE_MULTIPLIER } }; if ( cs->isEnzymeEnabled() ) { auto dfdx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getMethodData(), all_info, all_info ); const std::vector> nonmortar_info{ { 0, BlockSpace::NONMORTAR } }; - auto dfdn = - cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDfDnMethodData(), all_info, nonmortar_info ); + auto dfdn = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDfDnMethodData(), all_info, nonmortar_info ); auto dndx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDnDxMethodData(), nonmortar_info, nonmortar_info ); diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index d30f00e9..a356d6d1 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -92,8 +92,8 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* { // ParSparseMat is host only now. Make sure CSR data is copied on host in the constructor. HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation(&old_hypre_mem_location); - HYPRE_SetMemoryLocation(HYPRE_MEMORY_HOST); + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); m_owned_mat = std::make_unique( comm, glob_size, row_starts, &diag ); m_mat = m_owned_mat.get(); diag.GetMemoryI().ClearOwnerFlags(); @@ -102,7 +102,7 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* auto mfem_owned_arrays = 3; m_owned_mat->SetOwnerFlags( mfem_owned_arrays, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); // Return hypre's memory location to what it was before - HYPRE_SetMemoryLocation(old_hypre_mem_location); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); } ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept From 99952cff7570433f383f1d7b47ed1a4a1a087ada Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Fri, 6 Feb 2026 23:43:35 -0800 Subject: [PATCH 17/33] implement ParVector and tests --- src/tests/CMakeLists.txt | 1 + src/tests/tribol_par_sparse_mat.cpp | 88 ++++---- src/tests/tribol_par_vector.cpp | 305 ++++++++++++++++++++++++++++ src/tribol/CMakeLists.txt | 2 + src/tribol/utils/ParSparseMat.cpp | 12 +- src/tribol/utils/ParSparseMat.hpp | 15 +- src/tribol/utils/ParVector.cpp | 137 +++++++++++++ src/tribol/utils/ParVector.hpp | 202 ++++++++++++++++++ 8 files changed, 713 insertions(+), 49 deletions(-) create mode 100644 src/tests/tribol_par_vector.cpp create mode 100644 src/tribol/utils/ParVector.cpp create mode 100644 src/tribol/utils/ParVector.hpp diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 86424763..1c1f8777 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -71,6 +71,7 @@ if ( TRIBOL_USE_MPI ) set( mpi_tests tribol_par_sparse_mat.cpp + tribol_par_vector.cpp ) set(mpi_test_depends tribol gtest) diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/tribol_par_sparse_mat.cpp index cbcab749..cac3cf88 100644 --- a/src/tests/tribol_par_sparse_mat.cpp +++ b/src/tests/tribol_par_sparse_mat.cpp @@ -13,6 +13,7 @@ #include "mfem.hpp" +#include "tribol/utils/ParVector.hpp" #include "tribol/utils/ParSparseMat.hpp" class ParSparseMatTest : public ::testing::Test { @@ -65,13 +66,13 @@ TEST_F( ParSparseMatTest, Construction ) mfem::HypreParMatrix* m1 = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 1.0 ).release(); tribol::ParSparseMat psm1( m1 ); - EXPECT_EQ( psm1.get().Height(), local_size ); + EXPECT_EQ( psm1.Height(), local_size ); // 2. From unique_ptr auto m2 = std::unique_ptr( tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 2.0 ).release() ); tribol::ParSparseMat psm2( std::move( m2 ) ); - EXPECT_EQ( psm2.get().Height(), local_size ); + EXPECT_EQ( psm2.Height(), local_size ); // 3. From SparseMatrix rvalue mfem::SparseMatrix diag( local_size ); @@ -79,11 +80,11 @@ TEST_F( ParSparseMatTest, Construction ) diag.Finalize(); tribol::ParSparseMat psm3( MPI_COMM_WORLD, (HYPRE_BigInt)size, row_starts_array.GetData(), std::move( diag ) ); - EXPECT_EQ( psm3.get().Height(), local_size ); + EXPECT_EQ( psm3.Height(), local_size ); mfem::Vector x( local_size ), y( local_size ); x = 1.0; - psm3.get().Mult( x, y ); + psm3->Mult( x, y ); EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); } @@ -100,13 +101,13 @@ TEST_F( ParSparseMatTest, View ) // Construct View tribol::ParSparseMatView view( &A.get() ); - EXPECT_EQ( view.get().Height(), A.get().Height() ); + EXPECT_EQ( view.Height(), A.Height() ); // Operate on View tribol::ParSparseMat B = view * 2.0; - mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; - B.get().Mult( x, y ); + B->Mult( x, y ); EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); } @@ -123,16 +124,16 @@ TEST_F( ParSparseMatTest, Addition ) // A + B tribol::ParSparseMat C = A + B; - mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; - C.get().Mult( x, y ); + C->Mult( x, y ); // Result should be (2+3)*1 = 5 EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); EXPECT_NEAR( y.Min(), 5.0, 1e-12 ); // A += B A += B; - A.get().Mult( x, y ); + A->Mult( x, y ); EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); } @@ -149,15 +150,15 @@ TEST_F( ParSparseMatTest, Subtraction ) // A - B tribol::ParSparseMat C = A - B; - mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; - C.get().Mult( x, y ); + C->Mult( x, y ); // Result should be (5-2)*1 = 3 EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); // A -= B A -= B; - A.get().Mult( x, y ); + A->Mult( x, y ); EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); } @@ -173,14 +174,14 @@ TEST_F( ParSparseMatTest, ScalarMult ) // A * s tribol::ParSparseMat B = A * 3.0; - mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; - B.get().Mult( x, y ); + B->Mult( x, y ); EXPECT_NEAR( y.Max(), 6.0, 1e-12 ); // s * A tribol::ParSparseMat C = 4.0 * A; - C.get().Mult( x, y ); + C->Mult( x, y ); EXPECT_NEAR( y.Max(), 8.0, 1e-12 ); } @@ -197,15 +198,15 @@ TEST_F( ParSparseMatTest, MatrixMult ) // A * B tribol::ParSparseMat C = A * B; - mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; - C.get().Mult( x, y ); + C->Mult( x, y ); // Result should be (2*3)*1 = 6 EXPECT_NEAR( y.Max(), 6.0, 1e-12 ); // A *= B A *= B; - A.get().Mult( x, y ); + A->Mult( x, y ); EXPECT_NEAR( y.Max(), 6.0, 1e-12 ); } @@ -218,11 +219,12 @@ TEST_F( ParSparseMatTest, MatVecMult ) auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); - mfem::Vector x( A.get().Width() ); - x = 1.0; + mfem::HypreParVector x_hypre( A.get(), 1 ); + x_hypre = 1.0; + tribol::ParVectorView x( &x_hypre ); // y = A * x - mfem::Vector y = A * x; + tribol::ParVector y = A * x; EXPECT_NEAR( y.Max(), 2.0, 1e-12 ); } @@ -235,11 +237,12 @@ TEST_F( ParSparseMatTest, VecMatMult ) auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); - mfem::Vector x( A.get().Height() ); - x = 1.0; + mfem::HypreParVector x_hypre( A.get(), 0 ); + x_hypre = 1.0; + tribol::ParVectorView x( &x_hypre ); // y = x^T * A - mfem::Vector y = x * A; + tribol::ParVector y = x * A; EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); EXPECT_NEAR( y.Min(), 3.0, 1e-12 ); } @@ -265,9 +268,10 @@ TEST_F( ParSparseMatTest, Elimination ) // Check if row 0 is identity (or zero with diagonal 1) // Diagonal matrix means we can just check multiplication - mfem::Vector x( A.get().Height() ), y( A.get().Height() ); - x = 1.0; - y = A * x; // y = A * x + mfem::HypreParVector x_hypre( A.get(), 1 ); + x_hypre = 1.0; + tribol::ParVectorView x( &x_hypre ); + tribol::ParVector y = A * x; // y = A * x // if rank owns row 0, the result for that row should be 0.0 * x[0] = 0.0 (since diag is 0.0) // other rows should be 3.0 @@ -289,20 +293,22 @@ TEST_F( ParSparseMatTest, Elimination ) MPI_Comm_size( MPI_COMM_WORLD, &num_procs ); // Eliminate last local col - auto last_local_col = A.get().Width() - 1; + auto last_local_col = A.Width() - 1; mfem::Array cols_to_elim( { last_local_col } ); tribol::ParSparseMat Ae = A.EliminateCols( cols_to_elim ); // Now check A * e_last = 0 // Create vector with 1 at last_local_col, 0 elsewhere - x = 0.0; - x[last_local_col] = 1.0; - y = A * x; - EXPECT_NEAR( y[last_local_col], 0.0, 1e-12 ); + mfem::HypreParVector x_hypre_last( A.get(), 1 ); + x_hypre_last = 0.0; + x_hypre_last[last_local_col] = 1.0; + tribol::ParVectorView x_last( &x_hypre_last ); + tribol::ParVector y_last = A * x_last; + EXPECT_NEAR( y_last[last_local_col], 0.0, 1e-12 ); // Check Ae * e_last = original value - mfem::Vector ye = Ae * x; + tribol::ParVector ye = Ae * x_last; double expected_val = 3.0; EXPECT_NEAR( ye[last_local_col], expected_val, 1e-12 ); @@ -320,14 +326,14 @@ TEST_F( ParSparseMatTest, TransposeSquare ) // Transpose (Diagonal matrix is symmetric) tribol::ParSparseMat At = A.transpose(); - mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; - At.get().Mult( x, y ); + At->Mult( x, y ); EXPECT_NEAR( y.Max(), 2.0, 1e-12 ); // Square tribol::ParSparseMat A2 = A.square(); - A2.get().Mult( x, y ); + A2->Mult( x, y ); EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); } @@ -346,14 +352,14 @@ TEST_F( ParSparseMatTest, RAP ) // RAP(P) tribol::ParSparseMat Res1 = A.RAP( P ); - mfem::Vector x( A.get().Width() ), y( A.get().Height() ); + mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; - Res1.get().Mult( x, y ); + Res1->Mult( x, y ); EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); // RAP(R, A, P) tribol::ParSparseMat Res2 = tribol::ParSparseMat::RAP( R, A, P ); - Res2.get().Mult( x, y ); + Res2->Mult( x, y ); EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); } @@ -372,7 +378,7 @@ TEST_F( ParSparseMatTest, Accessors ) tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts, 1.0 ); // get() - EXPECT_EQ( A.get().Height(), local_size ); + EXPECT_EQ( A.Height(), local_size ); // operator-> EXPECT_EQ( A->Height(), local_size ); diff --git a/src/tests/tribol_par_vector.cpp b/src/tests/tribol_par_vector.cpp new file mode 100644 index 00000000..8c1d43a2 --- /dev/null +++ b/src/tests/tribol_par_vector.cpp @@ -0,0 +1,305 @@ +// 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 "tribol/config.hpp" + +#include + +#ifdef TRIBOL_USE_MPI +#include +#endif + +#include "mfem.hpp" + +#include "tribol/utils/ParVector.hpp" +#include "tribol/utils/ParSparseMat.hpp" + +class ParVectorTest : public ::testing::Test { + protected: + mfem::Array GetRowStarts( MPI_Comm comm, HYPRE_BigInt global_size ) + { + int rank, num_procs; + MPI_Comm_rank( comm, &rank ); + MPI_Comm_size( comm, &num_procs ); + + int local_size = global_size / num_procs; + int remainder = global_size % num_procs; + if ( rank < remainder ) { + local_size++; + } + + mfem::Array row_starts( num_procs + 1 ); + std::vector local_sizes( num_procs ); + MPI_Allgather( &local_size, 1, MPI_INT, local_sizes.data(), 1, MPI_INT, comm ); + row_starts[0] = 0; + for ( int i = 0; i < num_procs; ++i ) { + row_starts[i + 1] = row_starts[i] + local_sizes[i]; + } + if ( HYPRE_AssumedPartitionCheck() ) { + auto total_dofs = row_starts[num_procs]; + row_starts.SetSize( 3 ); + row_starts[0] = row_starts[rank]; + row_starts[1] = row_starts[rank + 1]; + row_starts[2] = total_dofs; + } + + return row_starts; + } +}; + +// Test Construction +TEST_F( ParVectorTest, Construction ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + int num_procs; + MPI_Comm_size( MPI_COMM_WORLD, &num_procs ); + constexpr int size = 10; + int local_size = size / num_procs + ( rank < ( size % num_procs ) ? 1 : 0 ); + if ( rank == 0 ) std::cout << "Testing Construction..." << std::endl; + + auto row_starts_array = GetRowStarts( MPI_COMM_WORLD, size ); + + // 1. From mfem::HypreParVector* + mfem::HypreParVector* v1 = new mfem::HypreParVector( MPI_COMM_WORLD, size, row_starts_array.GetData() ); + tribol::ParVector pv1( v1 ); + EXPECT_EQ( pv1.Size(), local_size ); + + // 2. From unique_ptr + auto v2 = std::make_unique( MPI_COMM_WORLD, size, row_starts_array.GetData() ); + tribol::ParVector pv2( std::move( v2 ) ); + EXPECT_EQ( pv2.Size(), local_size ); + + // 3. Template constructor + tribol::ParVector pv3( MPI_COMM_WORLD, size, row_starts_array.GetData() ); + EXPECT_EQ( pv3.Size(), local_size ); +} + +// Test View +TEST_F( ParVectorTest, View ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing View..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v.Fill( 1.0 ); + + // Construct View + tribol::ParVectorView view( &v.get() ); + + EXPECT_EQ( view.Size(), v.Size() ); + EXPECT_NEAR( view.Max(), 1.0, 1e-12 ); + + // Operate on View + tribol::ParVector v2 = view * 2.0; + EXPECT_NEAR( v2.Max(), 2.0, 1e-12 ); +} + +// Test Accessors +TEST_F( ParVectorTest, Accessors ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Accessors..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v.Fill( 0.0 ); + if ( v.Size() > 0 ) { + v[0] = 5.0; + EXPECT_NEAR( v[0], 5.0, 1e-12 ); + EXPECT_NEAR( ( *v.operator->() )[0], 5.0, 1e-12 ); + } + + v.Fill( 3.0 ); + EXPECT_NEAR( v.Max(), 3.0, 1e-12 ); + EXPECT_NEAR( v.Min(), 3.0, 1e-12 ); +} + +// Test Addition and Subtraction +TEST_F( ParVectorTest, AddSub ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Addition and Subtraction..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + tribol::ParVector v2( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v1.Fill( 2.0 ); + v2.Fill( 3.0 ); + + // v1 + v2 + tribol::ParVector v3 = v1 + v2; + EXPECT_NEAR( v3.Max(), 5.0, 1e-12 ); + + // v1 += v2 + v1 += v2; + EXPECT_NEAR( v1.Max(), 5.0, 1e-12 ); + + // v1 - v2 + tribol::ParVector v4 = v1 - v2; + EXPECT_NEAR( v4.Max(), 2.0, 1e-12 ); + + // v1 -= v2 + v1 -= v2; + EXPECT_NEAR( v1.Max(), 2.0, 1e-12 ); +} + +// Test Scalar Multiplication +TEST_F( ParVectorTest, ScalarMult ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Scalar Multiplication..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v.Fill( 2.0 ); + + // v * s + tribol::ParVector v2 = v * 3.0; + EXPECT_NEAR( v2.Max(), 6.0, 1e-12 ); + + // s * v + tribol::ParVector v3 = 4.0 * v; + EXPECT_NEAR( v3.Max(), 8.0, 1e-12 ); + + // v *= s + v *= 5.0; + EXPECT_NEAR( v.Max(), 10.0, 1e-12 ); +} + +// Test Component-wise Multiplication and Division +TEST_F( ParVectorTest, ComponentWise ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Component-wise operations..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + tribol::ParVector v2( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v1.Fill( 2.0 ); + v2.Fill( 4.0 ); + + // multiply + tribol::ParVector v3 = v1.multiply( v2 ); + EXPECT_NEAR( v3.Max(), 8.0, 1e-12 ); + + // multiply in-place + v1.multiply( v2 ); + EXPECT_NEAR( v1.Max(), 8.0, 1e-12 ); + + // divide + tribol::ParVector v4 = v1.divide( v2 ); + EXPECT_NEAR( v4.Max(), 2.0, 1e-12 ); + + // divide in-place + v1.divide( v2 ); + EXPECT_NEAR( v1.Max(), 2.0, 1e-12 ); +} + +// Test Move and Release +TEST_F( ParVectorTest, MoveAndRelease ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Move and Release..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v1.Fill( 7.0 ); + + // Move constructor + tribol::ParVector v2( std::move( v1 ) ); + EXPECT_NEAR( v2.Max(), 7.0, 1e-12 ); + EXPECT_EQ( v1.operator->(), nullptr ); + + // Move assignment + tribol::ParVector v3( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v3 = std::move( v2 ); + EXPECT_NEAR( v3.Max(), 7.0, 1e-12 ); + EXPECT_EQ( v2.operator->(), nullptr ); + + // Release + mfem::HypreParVector* raw = v3.release(); + EXPECT_NE( raw, nullptr ); + EXPECT_NEAR( raw->Max(), 7.0, 1e-12 ); + delete raw; +} + +// Test Fill +TEST_F( ParVectorTest, Fill ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Fill..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + + v.Fill( 1.0 ); + EXPECT_NEAR( v.Max(), 1.0, 1e-12 ); + EXPECT_NEAR( v.Min(), 1.0, 1e-12 ); + + v.Fill( 2.5 ); + EXPECT_NEAR( v.Max(), 2.5, 1e-12 ); + EXPECT_NEAR( v.Min(), 2.5, 1e-12 ); +} + +// Test Copy +TEST_F( ParVectorTest, Copy ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Copy..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v1.Fill( 3.0 ); + + // Copy constructor + tribol::ParVector v2( v1 ); + EXPECT_NEAR( v2.Max(), 3.0, 1e-12 ); + + // Verify it's a deep copy + v1.Fill( 4.0 ); + EXPECT_NEAR( v1.Max(), 4.0, 1e-12 ); + EXPECT_NEAR( v2.Max(), 3.0, 1e-12 ); + + // Copy assignment + tribol::ParVector v3( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v3.Fill( 5.0 ); + v3 = v2; + EXPECT_NEAR( v3.Max(), 3.0, 1e-12 ); + + // Verify deep copy for assignment + v2.Fill( 6.0 ); + EXPECT_NEAR( v2.Max(), 6.0, 1e-12 ); + EXPECT_NEAR( v3.Max(), 3.0, 1e-12 ); +} + +//------------------------------------------------------------------------------ +#include "axom/slic/core/SimpleLogger.hpp" + +int main( int argc, char* argv[] ) +{ + int result = 0; + + MPI_Init( &argc, &argv ); + + ::testing::InitGoogleTest( &argc, argv ); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + MPI_Finalize(); + + return result; +} diff --git a/src/tribol/CMakeLists.txt b/src/tribol/CMakeLists.txt index ae5a2896..6b753287 100644 --- a/src/tribol/CMakeLists.txt +++ b/src/tribol/CMakeLists.txt @@ -49,6 +49,7 @@ set(tribol_headers utils/ContactPlaneOutput.hpp utils/DataManager.hpp utils/Math.hpp + utils/ParVector.hpp utils/ParSparseMat.hpp utils/TestUtils.hpp ) @@ -81,6 +82,7 @@ set(tribol_sources utils/ContactPlaneOutput.cpp utils/Math.cpp + utils/ParVector.cpp utils/ParSparseMat.cpp utils/TestUtils.cpp ) diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index a356d6d1..7ae70e76 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -36,10 +36,10 @@ ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs return ParSparseMat( result ); } -mfem::Vector ParSparseMatView::operator*( const mfem::Vector& x ) const +ParVector ParSparseMatView::operator*( const ParVectorView& x ) const { - mfem::Vector y( m_mat->Height() ); - m_mat->Mult( x, y ); + ParVector y( *m_mat ); + m_mat->Mult( const_cast( x.get() ), y.get() ); return y; } @@ -71,10 +71,10 @@ ParSparseMat ParSparseMatView::EliminateCols( const mfem::Array& cols ) ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s; } -mfem::Vector operator*( const mfem::Vector& x, const ParSparseMatView& mat ) +ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ) { - mfem::Vector y( mat.m_mat->Width() ); - mat.m_mat->MultTranspose( x, y ); + ParVector y( *mat.m_mat, 1 ); + mat.m_mat->MultTranspose( const_cast( x.get() ), y.get() ); return y; } diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp index 08384279..efeacc70 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/tribol/utils/ParSparseMat.hpp @@ -8,6 +8,7 @@ #include "tribol/config.hpp" #include "mfem.hpp" +#include "tribol/utils/ParVector.hpp" #include #include @@ -53,6 +54,16 @@ class ParSparseMatView { */ const mfem::HypreParMatrix* operator->() const { return m_mat; } + /** + * @brief Returns the number of local rows + */ + int Height() const { return m_mat->Height(); } + + /** + * @brief Returns the number of local columns + */ + int Width() const { return m_mat->Width(); } + /** * @brief Matrix addition: returns A + B */ @@ -76,7 +87,7 @@ class ParSparseMatView { /** * @brief Matrix-vector multiplication: returns y = A * x */ - mfem::Vector operator*( const mfem::Vector& x ) const; + ParVector operator*( const ParVectorView& x ) const; /** * @brief Returns the transpose of the matrix @@ -125,7 +136,7 @@ class ParSparseMatView { /** * @brief Vector-Matrix multiplication: returns y = x^T * A (computed as A^T * x) */ - friend mfem::Vector operator*( const mfem::Vector& x, const ParSparseMatView& mat ); + friend ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ); protected: mfem::HypreParMatrix* m_mat; diff --git a/src/tribol/utils/ParVector.cpp b/src/tribol/utils/ParVector.cpp new file mode 100644 index 00000000..3e029d27 --- /dev/null +++ b/src/tribol/utils/ParVector.cpp @@ -0,0 +1,137 @@ +// 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 "tribol/utils/ParVector.hpp" + +#include "axom/slic.hpp" + +namespace tribol { + +ParVector::ParVector( mfem::HypreParVector* vec ) : ParVectorView( vec ), m_owned_vec( vec ) {} + +ParVector::ParVector( std::unique_ptr vec ) + : ParVectorView( vec.get() ), m_owned_vec( std::move( vec ) ) +{ +} + +ParVector::ParVector( ParVector&& other ) noexcept + : ParVectorView( other.m_owned_vec.get() ), m_owned_vec( std::move( other.m_owned_vec ) ) +{ + other.m_vec = nullptr; +} + +ParVector& ParVector::operator=( ParVector&& other ) noexcept +{ + if ( this != &other ) { + m_owned_vec = std::move( other.m_owned_vec ); + m_vec = m_owned_vec.get(); + other.m_vec = nullptr; + } + return *this; +} + +ParVector::ParVector( const ParVector& other ) + : ParVectorView( nullptr ), m_owned_vec( std::make_unique( *other.m_vec ) ) +{ + m_vec = m_owned_vec.get(); +} + +ParVector& ParVector::operator=( const ParVector& other ) +{ + if ( this != &other ) { + m_owned_vec = std::make_unique( *other.m_vec ); + m_vec = m_owned_vec.get(); + } + return *this; +} + +mfem::HypreParVector* ParVector::release() +{ + m_vec = nullptr; + return m_owned_vec.release(); +} + +ParVector operator+( const ParVectorView& lhs, const ParVectorView& rhs ) +{ + ParVector result( new mfem::HypreParVector( lhs.get() ) ); + result.get().Add( 1.0, rhs.get() ); + return result; +} + +ParVector operator-( const ParVectorView& lhs, const ParVectorView& rhs ) +{ + ParVector result( new mfem::HypreParVector( lhs.get() ) ); + result.get().Add( -1.0, rhs.get() ); + return result; +} + +ParVector ParVectorView::operator*( double s ) const +{ + ParVector result( new mfem::HypreParVector( *m_vec ) ); + result.get() *= s; + return result; +} + +ParVector operator*( double s, const ParVectorView& vec ) { return vec * s; } + +ParVector& ParVector::operator+=( const ParVectorView& other ) +{ + m_vec->Add( 1.0, other.get() ); + return *this; +} + +ParVector& ParVector::operator-=( const ParVectorView& other ) +{ + m_vec->Add( -1.0, other.get() ); + return *this; +} + +ParVector& ParVector::operator*=( double s ) +{ + *m_vec *= s; + return *this; +} + +ParVector ParVectorView::multiply( const ParVectorView& other ) const +{ + ParVector result( new mfem::HypreParVector( *m_vec ) ); + result.multiply( other ); + return result; +} + +ParVector ParVectorView::divide( const ParVectorView& other ) const +{ + ParVector result( new mfem::HypreParVector( *m_vec ) ); + result.divide( other ); + return result; +} + +ParVector& ParVector::multiply( const ParVectorView& other ) +{ + SLIC_ASSERT( m_vec->Size() == other.get().Size() ); + int n = m_vec->Size(); + if ( n > 0 ) { + bool use_device = m_vec->UseDevice() || other.get().UseDevice(); + auto d_vec = m_vec->ReadWrite( use_device ); + auto d_other = other.get().Read( use_device ); + mfem::forall_switch( use_device, n, [=] MFEM_DEVICE( int i ) { d_vec[i] *= d_other[i]; } ); + } + return *this; +} + +ParVector& ParVector::divide( const ParVectorView& other ) +{ + SLIC_ASSERT( m_vec->Size() == other.get().Size() ); + int n = m_vec->Size(); + if ( n > 0 ) { + bool use_device = m_vec->UseDevice() || other.get().UseDevice(); + auto d_vec = m_vec->ReadWrite( use_device ); + auto d_other = other.get().Read( use_device ); + mfem::forall_switch( use_device, n, [=] MFEM_DEVICE( int i ) { d_vec[i] /= d_other[i]; } ); + } + return *this; +} + +} // namespace tribol diff --git a/src/tribol/utils/ParVector.hpp b/src/tribol/utils/ParVector.hpp new file mode 100644 index 00000000..07776506 --- /dev/null +++ b/src/tribol/utils/ParVector.hpp @@ -0,0 +1,202 @@ +// 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) + +#ifndef SRC_TRIBOL_UTILS_PARVECTOR_HPP_ +#define SRC_TRIBOL_UTILS_PARVECTOR_HPP_ + +#include "tribol/config.hpp" +#include "mfem.hpp" + +#include +#include + +namespace tribol { + +class ParVector; + +/** + * @brief Non-owning view of a mfem::HypreParVector + * + * This class holds a raw pointer to a mfem::HypreParVector and provides algebraic operations. + * It does not manage the lifetime of the vector. + */ +class ParVectorView { + public: + /** + * @brief Construct from a mfem::HypreParVector pointer + * + * @param vec Pointer to the mfem HypreParVector + */ + ParVectorView( mfem::HypreParVector* vec ) : m_vec( vec ) {} + + virtual ~ParVectorView() = default; + + /** + * @brief Access the underlying mfem::HypreParVector + */ + mfem::HypreParVector& get() { return *m_vec; } + + /** + * @brief Access the underlying mfem::HypreParVector (const) + */ + const mfem::HypreParVector& get() const { return *m_vec; } + + /** + * @brief Access underlying vector members via arrow operator + */ + mfem::HypreParVector* operator->() { return m_vec; } + + /** + * @brief Access underlying vector members via arrow operator (const) + */ + const mfem::HypreParVector* operator->() const { return m_vec; } + + /** + * @brief Access local vector entry + */ + mfem::real_t& operator[]( int i ) { return ( *m_vec )[i]; } + + /** + * @brief Access local vector entry (const) + */ + const mfem::real_t& operator[]( int i ) const { return ( *m_vec )[i]; } + + /** + * @brief Sets all entries of the vector to the given value + * + * @param val Value to set + */ + void Fill( double val ) { *m_vec = val; } + + /** + * @brief Returns the local size of the vector + */ + int Size() const { return m_vec->Size(); } + + /** + * @brief Returns the maximum value in the vector + */ + mfem::real_t Max() const { return m_vec->Max(); } + + /** + * @brief Returns the minimum value in the vector + */ + mfem::real_t Min() const { return m_vec->Min(); } + + /** + * @brief Component-wise multiplication: returns z[i] = x[i] * y[i] + */ + ParVector multiply( const ParVectorView& other ) const; + + /** + * @brief Component-wise division: returns z[i] = x[i] / y[i] + */ + ParVector divide( const ParVectorView& other ) const; + + /** + * @brief Vector addition: returns x + y + */ + friend ParVector operator+( const ParVectorView& lhs, const ParVectorView& rhs ); + + /** + * @brief Vector subtraction: returns x - y + */ + friend ParVector operator-( const ParVectorView& lhs, const ParVectorView& rhs ); + + /** + * @brief Vector scalar multiplication: returns s * x + */ + ParVector operator*( double s ) const; + + /** + * @brief Scalar-Vector multiplication: returns s * x + */ + friend ParVector operator*( double s, const ParVectorView& vec ); + + protected: + mfem::HypreParVector* m_vec; +}; + +/** + * @brief Wrapper class for mfem::HypreParVector to provide convenience operators + * + * This class owns a mfem::HypreParVector via a unique_ptr and adds support for + * algebraic operations. + */ +class ParVector : public ParVectorView { + public: + /** + * @brief Construct from a mfem::HypreParVector pointer and take ownership + * + * @param vec Pointer to the mfem HypreParVector + */ + explicit ParVector( mfem::HypreParVector* vec ); + + /** + * @brief Construct from a mfem::HypreParVector pointer and take ownership + * + * @param vec Pointer to the mfem HypreParVector + */ + explicit ParVector( std::unique_ptr vec ); + + /// Template constructor forwarding arguments to mfem::HypreParVector constructor + template + explicit ParVector( Args&&... args ) + : ParVectorView( nullptr ), + m_owned_vec( std::make_unique( std::forward( args )... ) ) + { + m_vec = m_owned_vec.get(); + } + + /// Move constructor + ParVector( ParVector&& other ) noexcept; + + /// Move assignment + ParVector& operator=( ParVector&& other ) noexcept; + + /// Copy constructor + ParVector( const ParVector& other ); + + /// Copy assignment + ParVector& operator=( const ParVector& other ); + + /** + * @brief Access and release ownership of the HypreParVector pointer. The caller is now responsible for releasing the + * memory. + */ + mfem::HypreParVector* release(); + + /** + * @brief Vector in-place addition: x += y + */ + ParVector& operator+=( const ParVectorView& other ); + + /** + * @brief Vector in-place subtraction: x -= y + */ + ParVector& operator-=( const ParVectorView& other ); + + /** + * @brief Vector in-place multiplication: x *= s + */ + ParVector& operator*=( double s ); + + /** + * @brief Component-wise in-place multiplication: x[i] *= y[i] + */ + ParVector& multiply( const ParVectorView& other ); + + /** + * @brief Component-wise in-place division: x[i] /= y[i] + */ + ParVector& divide( const ParVectorView& other ); + + private: + std::unique_ptr m_owned_vec; +}; + +} // namespace tribol + +#endif /* SRC_TRIBOL_UTILS_PARVECTOR_HPP_ */ \ No newline at end of file From 01dbb4c50220287fb94cf5561978439045615a61 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Sat, 7 Feb 2026 07:47:21 -0800 Subject: [PATCH 18/33] fix in place multiplication and division --- src/tests/tribol_par_vector.cpp | 4 ++-- src/tribol/utils/ParVector.cpp | 10 +++++----- src/tribol/utils/ParVector.hpp | 7 +++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/tests/tribol_par_vector.cpp b/src/tests/tribol_par_vector.cpp index 8c1d43a2..9ca74ca1 100644 --- a/src/tests/tribol_par_vector.cpp +++ b/src/tests/tribol_par_vector.cpp @@ -192,7 +192,7 @@ TEST_F( ParVectorTest, ComponentWise ) EXPECT_NEAR( v3.Max(), 8.0, 1e-12 ); // multiply in-place - v1.multiply( v2 ); + v1.multiplyInPlace( v2 ); EXPECT_NEAR( v1.Max(), 8.0, 1e-12 ); // divide @@ -200,7 +200,7 @@ TEST_F( ParVectorTest, ComponentWise ) EXPECT_NEAR( v4.Max(), 2.0, 1e-12 ); // divide in-place - v1.divide( v2 ); + v1.divideInPlace( v2 ); EXPECT_NEAR( v1.Max(), 2.0, 1e-12 ); } diff --git a/src/tribol/utils/ParVector.cpp b/src/tribol/utils/ParVector.cpp index 3e029d27..f4c50a04 100644 --- a/src/tribol/utils/ParVector.cpp +++ b/src/tribol/utils/ParVector.cpp @@ -97,18 +97,18 @@ ParVector& ParVector::operator*=( double s ) ParVector ParVectorView::multiply( const ParVectorView& other ) const { ParVector result( new mfem::HypreParVector( *m_vec ) ); - result.multiply( other ); + result.multiplyInPlace( other ); return result; } ParVector ParVectorView::divide( const ParVectorView& other ) const { ParVector result( new mfem::HypreParVector( *m_vec ) ); - result.divide( other ); + result.divideInPlace( other ); return result; } -ParVector& ParVector::multiply( const ParVectorView& other ) +ParVector& ParVector::multiplyInPlace( const ParVectorView& other ) { SLIC_ASSERT( m_vec->Size() == other.get().Size() ); int n = m_vec->Size(); @@ -121,7 +121,7 @@ ParVector& ParVector::multiply( const ParVectorView& other ) return *this; } -ParVector& ParVector::divide( const ParVectorView& other ) +ParVector& ParVector::divideInPlace( const ParVectorView& other ) { SLIC_ASSERT( m_vec->Size() == other.get().Size() ); int n = m_vec->Size(); @@ -134,4 +134,4 @@ ParVector& ParVector::divide( const ParVectorView& other ) return *this; } -} // namespace tribol +} // namespace tribol \ No newline at end of file diff --git a/src/tribol/utils/ParVector.hpp b/src/tribol/utils/ParVector.hpp index 07776506..48545f60 100644 --- a/src/tribol/utils/ParVector.hpp +++ b/src/tribol/utils/ParVector.hpp @@ -159,6 +159,9 @@ class ParVector : public ParVectorView { /// Copy constructor ParVector( const ParVector& other ); + /// Copy constructor (non-const) + ParVector( ParVector& other ) : ParVector( static_cast( other ) ) {} + /// Copy assignment ParVector& operator=( const ParVector& other ); @@ -186,12 +189,12 @@ class ParVector : public ParVectorView { /** * @brief Component-wise in-place multiplication: x[i] *= y[i] */ - ParVector& multiply( const ParVectorView& other ); + ParVector& multiplyInPlace( const ParVectorView& other ); /** * @brief Component-wise in-place division: x[i] /= y[i] */ - ParVector& divide( const ParVectorView& other ); + ParVector& divideInPlace( const ParVectorView& other ); private: std::unique_ptr m_owned_vec; From 4db7a7b2a58ead3758d2e23a4a8cb3ced0cb21c9 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Sat, 7 Feb 2026 08:16:06 -0800 Subject: [PATCH 19/33] naming cleanup and formatting --- src/tribol/utils/ParSparseMat.cpp | 52 ++++++++++++++-------------- src/tribol/utils/ParSparseMat.hpp | 22 ++++++------ src/tribol/utils/ParVector.cpp | 56 +++++++++++++++---------------- src/tribol/utils/ParVector.hpp | 35 ++++++++++--------- 4 files changed, 82 insertions(+), 83 deletions(-) diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index 7ae70e76..d2669014 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -12,25 +12,25 @@ namespace tribol { ParSparseMat operator+( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.m_mat, 1.0, *rhs.m_mat ); + mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.mat_, 1.0, *rhs.mat_ ); return ParSparseMat( result ); } ParSparseMat operator-( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.m_mat, -1.0, *rhs.m_mat ); + mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.mat_, -1.0, *rhs.mat_ ); return ParSparseMat( result ); } ParSparseMat ParSparseMatView::operator*( double s ) const { - mfem::HypreParMatrix* result = mfem::Add( s, *m_mat, 0.0, *m_mat ); + mfem::HypreParMatrix* result = mfem::Add( s, *mat_, 0.0, *mat_ ); return ParSparseMat( result ); } ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - mfem::HypreParMatrix* result = mfem::ParMult( lhs.m_mat, rhs.m_mat ); + mfem::HypreParMatrix* result = mfem::ParMult( lhs.mat_, rhs.mat_ ); result->CopyRowStarts(); result->CopyColStarts(); return ParSparseMat( result ); @@ -38,52 +38,52 @@ ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs ParVector ParSparseMatView::operator*( const ParVectorView& x ) const { - ParVector y( *m_mat ); - m_mat->Mult( const_cast( x.get() ), y.get() ); + ParVector y( *mat_ ); + mat_->Mult( const_cast( x.get() ), y.get() ); return y; } -ParSparseMat ParSparseMatView::transpose() const { return ParSparseMat( m_mat->Transpose() ); } +ParSparseMat ParSparseMatView::transpose() const { return ParSparseMat( mat_->Transpose() ); } ParSparseMat ParSparseMatView::square() const { return *this * *this; } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& P ) const { - return ParSparseMat( mfem::RAP( m_mat, P.m_mat ) ); + return ParSparseMat( mfem::RAP( mat_, P.mat_ ) ); } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& A, const ParSparseMatView& P ) { - return ParSparseMat( mfem::RAP( A.m_mat, P.m_mat ) ); + return ParSparseMat( mfem::RAP( A.mat_, P.mat_ ) ); } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& R, const ParSparseMatView& A, const ParSparseMatView& P ) { - return ParSparseMat( mfem::RAP( R.m_mat, A.m_mat, P.m_mat ) ); + return ParSparseMat( mfem::RAP( R.mat_, A.mat_, P.mat_ ) ); } -void ParSparseMatView::EliminateRows( const mfem::Array& rows ) { m_mat->EliminateRows( rows ); } +void ParSparseMatView::EliminateRows( const mfem::Array& rows ) { mat_->EliminateRows( rows ); } ParSparseMat ParSparseMatView::EliminateCols( const mfem::Array& cols ) { - return ParSparseMat( m_mat->EliminateCols( cols ) ); + return ParSparseMat( mat_->EliminateCols( cols ) ); } ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s; } ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ) { - ParVector y( *mat.m_mat, 1 ); - mat.m_mat->MultTranspose( const_cast( x.get() ), y.get() ); + ParVector y( *mat.mat_, 1 ); + mat.mat_->MultTranspose( const_cast( x.get() ), y.get() ); return y; } // ParSparseMat implementations -ParSparseMat::ParSparseMat( mfem::HypreParMatrix* mat ) : ParSparseMatView( mat ), m_owned_mat( mat ) {} +ParSparseMat::ParSparseMat( mfem::HypreParMatrix* mat ) : ParSparseMatView( mat ), owned_mat_( mat ) {} ParSparseMat::ParSparseMat( std::unique_ptr mat ) - : ParSparseMatView( mat.get() ), m_owned_mat( std::move( mat ) ) + : ParSparseMatView( mat.get() ), owned_mat_( std::move( mat ) ) { } @@ -94,37 +94,37 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* HYPRE_MemoryLocation old_hypre_mem_location; HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - m_owned_mat = std::make_unique( comm, glob_size, row_starts, &diag ); - m_mat = m_owned_mat.get(); + owned_mat_ = std::make_unique( comm, glob_size, row_starts, &diag ); + mat_ = owned_mat_.get(); diag.GetMemoryI().ClearOwnerFlags(); diag.GetMemoryJ().ClearOwnerFlags(); diag.GetMemoryData().ClearOwnerFlags(); auto mfem_owned_arrays = 3; - m_owned_mat->SetOwnerFlags( mfem_owned_arrays, m_owned_mat->OwnsOffd(), m_owned_mat->OwnsColMap() ); + owned_mat_->SetOwnerFlags( mfem_owned_arrays, owned_mat_->OwnsOffd(), owned_mat_->OwnsColMap() ); // Return hypre's memory location to what it was before HYPRE_SetMemoryLocation( old_hypre_mem_location ); } ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept - : ParSparseMatView( other.m_owned_mat.get() ), m_owned_mat( std::move( other.m_owned_mat ) ) + : ParSparseMatView( other.owned_mat_.get() ), owned_mat_( std::move( other.owned_mat_ ) ) { - other.m_mat = nullptr; + other.mat_ = nullptr; } ParSparseMat& ParSparseMat::operator=( ParSparseMat&& other ) noexcept { if ( this != &other ) { - m_owned_mat = std::move( other.m_owned_mat ); - m_mat = m_owned_mat.get(); - other.m_mat = nullptr; + owned_mat_ = std::move( other.owned_mat_ ); + mat_ = owned_mat_.get(); + other.mat_ = nullptr; } return *this; } mfem::HypreParMatrix* ParSparseMat::release() { - m_mat = nullptr; - return m_owned_mat.release(); + mat_ = nullptr; + return owned_mat_.release(); } ParSparseMat& ParSparseMat::operator+=( const ParSparseMatView& other ) diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp index efeacc70..9f33967d 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/tribol/utils/ParSparseMat.hpp @@ -30,39 +30,39 @@ class ParSparseMatView { * * @param mat Pointer to the mfem HypreParMatrix */ - ParSparseMatView( mfem::HypreParMatrix* mat ) : m_mat( mat ) {} + ParSparseMatView( mfem::HypreParMatrix* mat ) : mat_( mat ) {} virtual ~ParSparseMatView() = default; /** * @brief Access the underlying mfem::HypreParMatrix */ - mfem::HypreParMatrix& get() { return *m_mat; } + mfem::HypreParMatrix& get() { return *mat_; } /** * @brief Access the underlying mfem::HypreParMatrix (const) */ - const mfem::HypreParMatrix& get() const { return *m_mat; } + const mfem::HypreParMatrix& get() const { return *mat_; } /** * @brief Access underlying matrix members via arrow operator */ - mfem::HypreParMatrix* operator->() { return m_mat; } + mfem::HypreParMatrix* operator->() { return mat_; } /** * @brief Access underlying matrix members via arrow operator (const) */ - const mfem::HypreParMatrix* operator->() const { return m_mat; } + const mfem::HypreParMatrix* operator->() const { return mat_; } /** * @brief Returns the number of local rows */ - int Height() const { return m_mat->Height(); } + int Height() const { return mat_->Height(); } /** * @brief Returns the number of local columns */ - int Width() const { return m_mat->Width(); } + int Width() const { return mat_->Width(); } /** * @brief Matrix addition: returns A + B @@ -139,7 +139,7 @@ class ParSparseMatView { friend ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ); protected: - mfem::HypreParMatrix* m_mat; + mfem::HypreParMatrix* mat_; }; /** @@ -180,9 +180,9 @@ class ParSparseMat : public ParSparseMatView { template explicit ParSparseMat( Args&&... args ) : ParSparseMatView( nullptr ), - m_owned_mat( std::make_unique( std::forward( args )... ) ) + owned_mat_( std::make_unique( std::forward( args )... ) ) { - m_mat = m_owned_mat.get(); + mat_ = owned_mat_.get(); } /// Move constructor @@ -250,7 +250,7 @@ class ParSparseMat : public ParSparseMatView { bool skip_rows = true ); private: - std::unique_ptr m_owned_mat; + std::unique_ptr owned_mat_; }; } // namespace tribol diff --git a/src/tribol/utils/ParVector.cpp b/src/tribol/utils/ParVector.cpp index f4c50a04..d0cf862b 100644 --- a/src/tribol/utils/ParVector.cpp +++ b/src/tribol/utils/ParVector.cpp @@ -9,48 +9,48 @@ namespace tribol { -ParVector::ParVector( mfem::HypreParVector* vec ) : ParVectorView( vec ), m_owned_vec( vec ) {} +ParVector::ParVector( mfem::HypreParVector* vec ) : ParVectorView( vec ), owned_vec_( vec ) {} ParVector::ParVector( std::unique_ptr vec ) - : ParVectorView( vec.get() ), m_owned_vec( std::move( vec ) ) + : ParVectorView( vec.get() ), owned_vec_( std::move( vec ) ) { } ParVector::ParVector( ParVector&& other ) noexcept - : ParVectorView( other.m_owned_vec.get() ), m_owned_vec( std::move( other.m_owned_vec ) ) + : ParVectorView( other.owned_vec_.get() ), owned_vec_( std::move( other.owned_vec_ ) ) { - other.m_vec = nullptr; + other.vec_ = nullptr; } ParVector& ParVector::operator=( ParVector&& other ) noexcept { if ( this != &other ) { - m_owned_vec = std::move( other.m_owned_vec ); - m_vec = m_owned_vec.get(); - other.m_vec = nullptr; + owned_vec_ = std::move( other.owned_vec_ ); + vec_ = owned_vec_.get(); + other.vec_ = nullptr; } return *this; } ParVector::ParVector( const ParVector& other ) - : ParVectorView( nullptr ), m_owned_vec( std::make_unique( *other.m_vec ) ) + : ParVectorView( nullptr ), owned_vec_( std::make_unique( *other.vec_ ) ) { - m_vec = m_owned_vec.get(); + vec_ = owned_vec_.get(); } ParVector& ParVector::operator=( const ParVector& other ) { if ( this != &other ) { - m_owned_vec = std::make_unique( *other.m_vec ); - m_vec = m_owned_vec.get(); + owned_vec_ = std::make_unique( *other.vec_ ); + vec_ = owned_vec_.get(); } return *this; } mfem::HypreParVector* ParVector::release() { - m_vec = nullptr; - return m_owned_vec.release(); + vec_ = nullptr; + return owned_vec_.release(); } ParVector operator+( const ParVectorView& lhs, const ParVectorView& rhs ) @@ -69,7 +69,7 @@ ParVector operator-( const ParVectorView& lhs, const ParVectorView& rhs ) ParVector ParVectorView::operator*( double s ) const { - ParVector result( new mfem::HypreParVector( *m_vec ) ); + ParVector result( new mfem::HypreParVector( *vec_ ) ); result.get() *= s; return result; } @@ -78,43 +78,43 @@ ParVector operator*( double s, const ParVectorView& vec ) { return vec * s; } ParVector& ParVector::operator+=( const ParVectorView& other ) { - m_vec->Add( 1.0, other.get() ); + vec_->Add( 1.0, other.get() ); return *this; } ParVector& ParVector::operator-=( const ParVectorView& other ) { - m_vec->Add( -1.0, other.get() ); + vec_->Add( -1.0, other.get() ); return *this; } ParVector& ParVector::operator*=( double s ) { - *m_vec *= s; + *vec_ *= s; return *this; } ParVector ParVectorView::multiply( const ParVectorView& other ) const { - ParVector result( new mfem::HypreParVector( *m_vec ) ); + ParVector result( new mfem::HypreParVector( *vec_ ) ); result.multiplyInPlace( other ); return result; } ParVector ParVectorView::divide( const ParVectorView& other ) const { - ParVector result( new mfem::HypreParVector( *m_vec ) ); + ParVector result( new mfem::HypreParVector( *vec_ ) ); result.divideInPlace( other ); return result; } ParVector& ParVector::multiplyInPlace( const ParVectorView& other ) { - SLIC_ASSERT( m_vec->Size() == other.get().Size() ); - int n = m_vec->Size(); + SLIC_ASSERT( vec_->Size() == other.get().Size() ); + int n = vec_->Size(); if ( n > 0 ) { - bool use_device = m_vec->UseDevice() || other.get().UseDevice(); - auto d_vec = m_vec->ReadWrite( use_device ); + bool use_device = vec_->UseDevice() || other.get().UseDevice(); + auto d_vec = vec_->ReadWrite( use_device ); auto d_other = other.get().Read( use_device ); mfem::forall_switch( use_device, n, [=] MFEM_DEVICE( int i ) { d_vec[i] *= d_other[i]; } ); } @@ -123,15 +123,15 @@ ParVector& ParVector::multiplyInPlace( const ParVectorView& other ) ParVector& ParVector::divideInPlace( const ParVectorView& other ) { - SLIC_ASSERT( m_vec->Size() == other.get().Size() ); - int n = m_vec->Size(); + SLIC_ASSERT( vec_->Size() == other.get().Size() ); + int n = vec_->Size(); if ( n > 0 ) { - bool use_device = m_vec->UseDevice() || other.get().UseDevice(); - auto d_vec = m_vec->ReadWrite( use_device ); + bool use_device = vec_->UseDevice() || other.get().UseDevice(); + auto d_vec = vec_->ReadWrite( use_device ); auto d_other = other.get().Read( use_device ); mfem::forall_switch( use_device, n, [=] MFEM_DEVICE( int i ) { d_vec[i] /= d_other[i]; } ); } return *this; } -} // namespace tribol \ No newline at end of file +} // namespace tribol diff --git a/src/tribol/utils/ParVector.hpp b/src/tribol/utils/ParVector.hpp index 48545f60..84cd3a67 100644 --- a/src/tribol/utils/ParVector.hpp +++ b/src/tribol/utils/ParVector.hpp @@ -29,61 +29,61 @@ class ParVectorView { * * @param vec Pointer to the mfem HypreParVector */ - ParVectorView( mfem::HypreParVector* vec ) : m_vec( vec ) {} + ParVectorView( mfem::HypreParVector* vec ) : vec_( vec ) {} virtual ~ParVectorView() = default; /** * @brief Access the underlying mfem::HypreParVector */ - mfem::HypreParVector& get() { return *m_vec; } + mfem::HypreParVector& get() { return *vec_; } /** * @brief Access the underlying mfem::HypreParVector (const) */ - const mfem::HypreParVector& get() const { return *m_vec; } + const mfem::HypreParVector& get() const { return *vec_; } /** * @brief Access underlying vector members via arrow operator */ - mfem::HypreParVector* operator->() { return m_vec; } + mfem::HypreParVector* operator->() { return vec_; } /** * @brief Access underlying vector members via arrow operator (const) */ - const mfem::HypreParVector* operator->() const { return m_vec; } + const mfem::HypreParVector* operator->() const { return vec_; } /** * @brief Access local vector entry */ - mfem::real_t& operator[]( int i ) { return ( *m_vec )[i]; } + mfem::real_t& operator[]( int i ) { return ( *vec_ )[i]; } /** * @brief Access local vector entry (const) */ - const mfem::real_t& operator[]( int i ) const { return ( *m_vec )[i]; } + const mfem::real_t& operator[]( int i ) const { return ( *vec_ )[i]; } /** * @brief Sets all entries of the vector to the given value * * @param val Value to set */ - void Fill( double val ) { *m_vec = val; } + void Fill( double val ) { *vec_ = val; } /** * @brief Returns the local size of the vector */ - int Size() const { return m_vec->Size(); } + int Size() const { return vec_->Size(); } /** * @brief Returns the maximum value in the vector */ - mfem::real_t Max() const { return m_vec->Max(); } + mfem::real_t Max() const { return vec_->Max(); } /** * @brief Returns the minimum value in the vector */ - mfem::real_t Min() const { return m_vec->Min(); } + mfem::real_t Min() const { return vec_->Min(); } /** * @brief Component-wise multiplication: returns z[i] = x[i] * y[i] @@ -116,7 +116,7 @@ class ParVectorView { friend ParVector operator*( double s, const ParVectorView& vec ); protected: - mfem::HypreParVector* m_vec; + mfem::HypreParVector* vec_; }; /** @@ -144,10 +144,9 @@ class ParVector : public ParVectorView { /// Template constructor forwarding arguments to mfem::HypreParVector constructor template explicit ParVector( Args&&... args ) - : ParVectorView( nullptr ), - m_owned_vec( std::make_unique( std::forward( args )... ) ) + : ParVectorView( nullptr ), owned_vec_( std::make_unique( std::forward( args )... ) ) { - m_vec = m_owned_vec.get(); + vec_ = owned_vec_.get(); } /// Move constructor @@ -159,7 +158,7 @@ class ParVector : public ParVectorView { /// Copy constructor ParVector( const ParVector& other ); - /// Copy constructor (non-const) + /// Copy constructor (non-const; prevents the template constructor from trying to match this case) ParVector( ParVector& other ) : ParVector( static_cast( other ) ) {} /// Copy assignment @@ -197,9 +196,9 @@ class ParVector : public ParVectorView { ParVector& divideInPlace( const ParVectorView& other ); private: - std::unique_ptr m_owned_vec; + std::unique_ptr owned_vec_; }; } // namespace tribol -#endif /* SRC_TRIBOL_UTILS_PARVECTOR_HPP_ */ \ No newline at end of file +#endif /* SRC_TRIBOL_UTILS_PARVECTOR_HPP_ */ From 992c180b48daf9acea6696a23d8c8b473a3fca40 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Sat, 7 Feb 2026 17:27:10 -0800 Subject: [PATCH 20/33] fix host device decorations --- src/tribol/utils/ParVector.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tribol/utils/ParVector.cpp b/src/tribol/utils/ParVector.cpp index d0cf862b..3f548046 100644 --- a/src/tribol/utils/ParVector.cpp +++ b/src/tribol/utils/ParVector.cpp @@ -7,6 +7,8 @@ #include "axom/slic.hpp" +#include "tribol/common/BasicTypes.hpp" + namespace tribol { ParVector::ParVector( mfem::HypreParVector* vec ) : ParVectorView( vec ), owned_vec_( vec ) {} @@ -116,7 +118,7 @@ ParVector& ParVector::multiplyInPlace( const ParVectorView& other ) bool use_device = vec_->UseDevice() || other.get().UseDevice(); auto d_vec = vec_->ReadWrite( use_device ); auto d_other = other.get().Read( use_device ); - mfem::forall_switch( use_device, n, [=] MFEM_DEVICE( int i ) { d_vec[i] *= d_other[i]; } ); + mfem::forall_switch( use_device, n, [=] TRIBOL_HOST_DEVICE( int i ) { d_vec[i] *= d_other[i]; } ); } return *this; } @@ -129,7 +131,7 @@ ParVector& ParVector::divideInPlace( const ParVectorView& other ) bool use_device = vec_->UseDevice() || other.get().UseDevice(); auto d_vec = vec_->ReadWrite( use_device ); auto d_other = other.get().Read( use_device ); - mfem::forall_switch( use_device, n, [=] MFEM_DEVICE( int i ) { d_vec[i] /= d_other[i]; } ); + mfem::forall_switch( use_device, n, [=] TRIBOL_HOST_DEVICE( int i ) { d_vec[i] /= d_other[i]; } ); } return *this; } From 803e63061c8da8343807723d8a42ad9f2d8e12bf Mon Sep 17 00:00:00 2001 From: EB Chin Date: Mon, 9 Feb 2026 17:07:51 -0800 Subject: [PATCH 21/33] prevent unwanted device allocations --- src/tests/tribol_par_sparse_mat.cpp | 43 +++++++------- src/tests/tribol_par_vector.cpp | 7 +++ src/tribol/utils/ParSparseMat.cpp | 90 ++++++++++++++++++++++------- src/tribol/utils/ParSparseMat.hpp | 24 +++++--- src/tribol/utils/ParVector.cpp | 31 +++++++--- src/tribol/utils/ParVector.hpp | 12 +++- 6 files changed, 149 insertions(+), 58 deletions(-) diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/tribol_par_sparse_mat.cpp index cac3cf88..73657694 100644 --- a/src/tests/tribol_par_sparse_mat.cpp +++ b/src/tests/tribol_par_sparse_mat.cpp @@ -105,9 +105,9 @@ TEST_F( ParSparseMatTest, View ) // Operate on View tribol::ParSparseMat B = view * 2.0; - mfem::Vector x( A.Width() ), y( A.Height() ); - x = 1.0; - B->Mult( x, y ); + tribol::ParVector x( B.get() ); + x.Fill( 1.0 ); + auto y = B * x; EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); } @@ -219,7 +219,11 @@ TEST_F( ParSparseMatTest, MatVecMult ) auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mfem::HypreParVector x_hypre( A.get(), 1 ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); x_hypre = 1.0; tribol::ParVectorView x( &x_hypre ); @@ -237,9 +241,8 @@ TEST_F( ParSparseMatTest, VecMatMult ) auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); - mfem::HypreParVector x_hypre( A.get(), 0 ); - x_hypre = 1.0; - tribol::ParVectorView x( &x_hypre ); + tribol::ParVector x( A.get(), 0 ); + x.Fill( 1.0 ); // y = x^T * A tribol::ParVector y = x * A; @@ -268,9 +271,8 @@ TEST_F( ParSparseMatTest, Elimination ) // Check if row 0 is identity (or zero with diagonal 1) // Diagonal matrix means we can just check multiplication - mfem::HypreParVector x_hypre( A.get(), 1 ); - x_hypre = 1.0; - tribol::ParVectorView x( &x_hypre ); + tribol::ParVector x( A.get(), 1 ); + x.Fill( 1.0 ); tribol::ParVector y = A * x; // y = A * x // if rank owns row 0, the result for that row should be 0.0 * x[0] = 0.0 (since diag is 0.0) @@ -300,10 +302,9 @@ TEST_F( ParSparseMatTest, Elimination ) // Now check A * e_last = 0 // Create vector with 1 at last_local_col, 0 elsewhere - mfem::HypreParVector x_hypre_last( A.get(), 1 ); - x_hypre_last = 0.0; - x_hypre_last[last_local_col] = 1.0; - tribol::ParVectorView x_last( &x_hypre_last ); + tribol::ParVector x_last( A.get(), 1 ); + x_last.Fill( 0.0 ); + x_last[last_local_col] = 1.0; tribol::ParVector y_last = A * x_last; EXPECT_NEAR( y_last[last_local_col], 0.0, 1e-12 ); @@ -326,14 +327,14 @@ TEST_F( ParSparseMatTest, TransposeSquare ) // Transpose (Diagonal matrix is symmetric) tribol::ParSparseMat At = A.transpose(); - mfem::Vector x( A.Width() ), y( A.Height() ); - x = 1.0; - At->Mult( x, y ); + tribol::ParVector x( At.get(), 0 ); + x.Fill( 1.0 ); + auto y = At * x; EXPECT_NEAR( y.Max(), 2.0, 1e-12 ); // Square tribol::ParSparseMat A2 = A.square(); - A2->Mult( x, y ); + y = A2 * x; EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); } @@ -352,14 +353,14 @@ TEST_F( ParSparseMatTest, RAP ) // RAP(P) tribol::ParSparseMat Res1 = A.RAP( P ); - mfem::Vector x( A.Width() ), y( A.Height() ); - x = 1.0; - Res1->Mult( x, y ); + tribol::ParVector x( A.get(), 0 ); + x.Fill( 1.0 ); + auto y = Res1 * x; EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); // RAP(R, A, P) tribol::ParSparseMat Res2 = tribol::ParSparseMat::RAP( R, A, P ); - Res2->Mult( x, y ); + y = Res2 * x; EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); } diff --git a/src/tests/tribol_par_vector.cpp b/src/tests/tribol_par_vector.cpp index 9ca74ca1..10f8365a 100644 --- a/src/tests/tribol_par_vector.cpp +++ b/src/tests/tribol_par_vector.cpp @@ -63,12 +63,19 @@ TEST_F( ParVectorTest, Construction ) auto row_starts_array = GetRowStarts( MPI_COMM_WORLD, size ); // 1. From mfem::HypreParVector* + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mfem::HypreParVector* v1 = new mfem::HypreParVector( MPI_COMM_WORLD, size, row_starts_array.GetData() ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); tribol::ParVector pv1( v1 ); EXPECT_EQ( pv1.Size(), local_size ); // 2. From unique_ptr + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); auto v2 = std::make_unique( MPI_COMM_WORLD, size, row_starts_array.GetData() ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); tribol::ParVector pv2( std::move( v2 ) ); EXPECT_EQ( pv2.Size(), local_size ); diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index d2669014..70fc110b 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -4,42 +4,59 @@ // SPDX-License-Identifier: (MIT) #include "tribol/utils/ParSparseMat.hpp" + #include +#include <_hypre_parcsr_mv.h> + +#include "axom/slic.hpp" namespace tribol { // ParSparseMatView implementations -ParSparseMat operator+( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) +ParSparseMatView::ParSparseMatView( mfem::HypreParMatrix* mat ) : mat_( mat ) { - mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.mat_, 1.0, *rhs.mat_ ); - return ParSparseMat( result ); + SLIC_ERROR_ROOT_IF( + mat != nullptr && hypre_ParCSRMatrixMemoryLocation( mat->operator hypre_ParCSRMatrix*() ) != HYPRE_MEMORY_HOST, + "ParSparseMatView currently requires host data." ); } -ParSparseMat operator-( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) +ParSparseMat operator+( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - mfem::HypreParMatrix* result = mfem::Add( 1.0, *lhs.mat_, -1.0, *rhs.mat_ ); - return ParSparseMat( result ); + return ParSparseMatView::add( 1.0, lhs, 1.0, rhs ); } -ParSparseMat ParSparseMatView::operator*( double s ) const +ParSparseMat operator-( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - mfem::HypreParMatrix* result = mfem::Add( s, *mat_, 0.0, *mat_ ); - return ParSparseMat( result ); + return ParSparseMatView::add( 1.0, lhs, -1.0, rhs ); } +ParSparseMat ParSparseMatView::operator*( double s ) const { return add( s, *this, 0.0, *this ); } + ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { + // Prevent mfem::ParMult from creating device arrays + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mfem::HypreParMatrix* result = mfem::ParMult( lhs.mat_, rhs.mat_ ); result->CopyRowStarts(); result->CopyColStarts(); + constexpr auto hypre_owned_host_arrays = -1; + result->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); return ParSparseMat( result ); } ParVector ParSparseMatView::operator*( const ParVectorView& x ) const { ParVector y( *mat_ ); + // Prevent HypreParMatrix::Mult from changing memory from host to device + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mat_->Mult( const_cast( x.get() ), y.get() ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); return y; } @@ -62,11 +79,24 @@ ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& R, const ParSparseMa return ParSparseMat( mfem::RAP( R.mat_, A.mat_, P.mat_ ) ); } -void ParSparseMatView::EliminateRows( const mfem::Array& rows ) { mat_->EliminateRows( rows ); } +void ParSparseMatView::EliminateRows( const mfem::Array& rows ) +{ + // Prevent HypreParMatrix::EliminateRows from changing memory from host to device + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + mat_->EliminateRows( rows ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); +} ParSparseMat ParSparseMatView::EliminateCols( const mfem::Array& cols ) { + // Prevent HypreParMatrix::EliminateRows from changing memory from host to device + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); return ParSparseMat( mat_->EliminateCols( cols ) ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); } ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s; } @@ -74,10 +104,28 @@ ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ) { ParVector y( *mat.mat_, 1 ); + // Prevent HypreParMatrix::MultTranspose from changing memory from host to device + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mat.mat_->MultTranspose( const_cast( x.get() ), y.get() ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); return y; } +ParSparseMat ParSparseMatView::add( RealT alpha, const ParSparseMatView& A, RealT beta, const ParSparseMatView& B ) +{ + // Prevent HypreParMatrix::Add from returning data on device + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + mfem::HypreParMatrix* result = mfem::Add( alpha, A.get(), beta, B.get() ); + constexpr auto hypre_owned_host_arrays = -1; + result->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); + return ParSparseMat( result ); +} + // ParSparseMat implementations ParSparseMat::ParSparseMat( mfem::HypreParMatrix* mat ) : ParSparseMatView( mat ), owned_mat_( mat ) {} @@ -90,7 +138,7 @@ ParSparseMat::ParSparseMat( std::unique_ptr mat ) ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ) : ParSparseMatView( nullptr ) { - // ParSparseMat is host only now. Make sure CSR data is copied on host in the constructor. + // ParSparseMat works with host data for now. Make sure CSR data is copied on host in the constructor. HYPRE_MemoryLocation old_hypre_mem_location; HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); @@ -99,8 +147,9 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* diag.GetMemoryI().ClearOwnerFlags(); diag.GetMemoryJ().ClearOwnerFlags(); diag.GetMemoryData().ClearOwnerFlags(); - auto mfem_owned_arrays = 3; - owned_mat_->SetOwnerFlags( mfem_owned_arrays, owned_mat_->OwnsOffd(), owned_mat_->OwnsColMap() ); + constexpr auto mfem_owned_host_arrays = 3; + constexpr auto hypre_owned_host_arrays = -1; + owned_mat_->SetOwnerFlags( mfem_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); // Return hypre's memory location to what it was before HYPRE_SetMemoryLocation( old_hypre_mem_location ); } @@ -158,11 +207,6 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si num_local_rows = static_cast( row_starts[rank + 1] - row_starts[rank] ); } - // NOTE: mfem::HypreParMatrix(MPI_Comm, HYPRE_BigInt, HYPRE_BigInt*, SparseMatrix*) does not take ownership - // of the provided CSR arrays (see MFEM docs). To avoid dangling pointers and allocator mismatches, build - // the ParCSR data using the HypreParMatrix constructor that takes ownership of raw arrays allocated with - // new[]. - const int num_ordered_rows = ordered_rows.Size(); if ( num_local_rows < 0 ) { num_local_rows = 0; @@ -216,13 +260,19 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si // copy row_starts to a new array mfem::Array row_starts_copy = row_starts; + // ParSparseMat is host only for now + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); auto diag_hpm = std::make_unique( comm, global_size, global_size, row_starts_copy.GetData(), row_starts_copy.GetData(), diag_i, diag_j, diag_data, offd_i, offd_j, offd_data, 0, offd_col_map, true ); + // Return hypre's memory location to what it was before + HYPRE_SetMemoryLocation( old_hypre_mem_location ); diag_hpm->CopyRowStarts(); diag_hpm->CopyColStarts(); - auto mfem_owned_arrays = 3; - diag_hpm->SetOwnerFlags( mfem_owned_arrays, diag_hpm->OwnsOffd(), diag_hpm->OwnsColMap() ); + constexpr auto mfem_owned_host_arrays = 3; + diag_hpm->SetOwnerFlags( mfem_owned_host_arrays, mfem_owned_host_arrays, diag_hpm->OwnsColMap() ); return ParSparseMat( std::move( diag_hpm ) ); } diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp index 9f33967d..bf813c09 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/tribol/utils/ParSparseMat.hpp @@ -6,12 +6,15 @@ #ifndef SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ #define SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ +#include +#include + #include "tribol/config.hpp" + #include "mfem.hpp" -#include "tribol/utils/ParVector.hpp" -#include -#include +#include "tribol/common/BasicTypes.hpp" +#include "tribol/utils/ParVector.hpp" namespace tribol { @@ -30,7 +33,7 @@ class ParSparseMatView { * * @param mat Pointer to the mfem HypreParMatrix */ - ParSparseMatView( mfem::HypreParMatrix* mat ) : mat_( mat ) {} + ParSparseMatView( mfem::HypreParMatrix* mat ); virtual ~ParSparseMatView() = default; @@ -139,6 +142,8 @@ class ParSparseMatView { friend ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ); protected: + static ParSparseMat add( RealT alpha, const ParSparseMatView& A, RealT beta, const ParSparseMatView& B ); + mfem::HypreParMatrix* mat_; }; @@ -178,10 +183,15 @@ class ParSparseMat : public ParSparseMatView { /// Template constructor forwarding arguments to mfem::HypreParMatrix constructor template - explicit ParSparseMat( Args&&... args ) - : ParSparseMatView( nullptr ), - owned_mat_( std::make_unique( std::forward( args )... ) ) + explicit ParSparseMat( Args&&... args ) : ParSparseMatView( nullptr ), owned_mat_( nullptr ) { + // ParSparseMat is host-only for now. + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + owned_mat_ = std::make_unique( std::forward( args )... ); + // Return hypre's memory location to what it was before + HYPRE_SetMemoryLocation( old_hypre_mem_location ); mat_ = owned_mat_.get(); } diff --git a/src/tribol/utils/ParVector.cpp b/src/tribol/utils/ParVector.cpp index 3f548046..ee2a7f2d 100644 --- a/src/tribol/utils/ParVector.cpp +++ b/src/tribol/utils/ParVector.cpp @@ -5,12 +5,21 @@ #include "tribol/utils/ParVector.hpp" +#include <_hypre_parcsr_mv.h> + #include "axom/slic.hpp" #include "tribol/common/BasicTypes.hpp" namespace tribol { +ParVectorView::ParVectorView( mfem::HypreParVector* vec ) : vec_( vec ) +{ + SLIC_ERROR_ROOT_IF( + vec != nullptr && hypre_ParVectorMemoryLocation( vec->operator hypre_ParVector*() ) != HYPRE_MEMORY_HOST, + "ParVectorView currently requires host data." ); +} + ParVector::ParVector( mfem::HypreParVector* vec ) : ParVectorView( vec ), owned_vec_( vec ) {} ParVector::ParVector( std::unique_ptr vec ) @@ -34,16 +43,24 @@ ParVector& ParVector::operator=( ParVector&& other ) noexcept return *this; } -ParVector::ParVector( const ParVector& other ) - : ParVectorView( nullptr ), owned_vec_( std::make_unique( *other.vec_ ) ) +ParVector::ParVector( const ParVector& other ) : ParVectorView( nullptr ), owned_vec_( nullptr ) { + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + owned_vec_ = std::make_unique( *other.vec_ ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); vec_ = owned_vec_.get(); } ParVector& ParVector::operator=( const ParVector& other ) { if ( this != &other ) { + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); owned_vec_ = std::make_unique( *other.vec_ ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); vec_ = owned_vec_.get(); } return *this; @@ -57,21 +74,21 @@ mfem::HypreParVector* ParVector::release() ParVector operator+( const ParVectorView& lhs, const ParVectorView& rhs ) { - ParVector result( new mfem::HypreParVector( lhs.get() ) ); + ParVector result( lhs.get() ); result.get().Add( 1.0, rhs.get() ); return result; } ParVector operator-( const ParVectorView& lhs, const ParVectorView& rhs ) { - ParVector result( new mfem::HypreParVector( lhs.get() ) ); + ParVector result( lhs.get() ); result.get().Add( -1.0, rhs.get() ); return result; } ParVector ParVectorView::operator*( double s ) const { - ParVector result( new mfem::HypreParVector( *vec_ ) ); + ParVector result( *vec_ ); result.get() *= s; return result; } @@ -98,14 +115,14 @@ ParVector& ParVector::operator*=( double s ) ParVector ParVectorView::multiply( const ParVectorView& other ) const { - ParVector result( new mfem::HypreParVector( *vec_ ) ); + ParVector result( *vec_ ); result.multiplyInPlace( other ); return result; } ParVector ParVectorView::divide( const ParVectorView& other ) const { - ParVector result( new mfem::HypreParVector( *vec_ ) ); + ParVector result( *vec_ ); result.divideInPlace( other ); return result; } diff --git a/src/tribol/utils/ParVector.hpp b/src/tribol/utils/ParVector.hpp index 84cd3a67..27f8ed8f 100644 --- a/src/tribol/utils/ParVector.hpp +++ b/src/tribol/utils/ParVector.hpp @@ -29,7 +29,7 @@ class ParVectorView { * * @param vec Pointer to the mfem HypreParVector */ - ParVectorView( mfem::HypreParVector* vec ) : vec_( vec ) {} + ParVectorView( mfem::HypreParVector* vec ); virtual ~ParVectorView() = default; @@ -143,9 +143,15 @@ class ParVector : public ParVectorView { /// Template constructor forwarding arguments to mfem::HypreParVector constructor template - explicit ParVector( Args&&... args ) - : ParVectorView( nullptr ), owned_vec_( std::make_unique( std::forward( args )... ) ) + explicit ParVector( Args&&... args ) : ParVectorView( nullptr ), owned_vec_( nullptr ) { + // ParVector is host-only for now. + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + owned_vec_ = std::make_unique( std::forward( args )... ); + // Return hypre's memory location to what it was before + HYPRE_SetMemoryLocation( old_hypre_mem_location ); vec_ = owned_vec_.get(); } From d3257f99d69a5d5912ffc81ffe7c7d61e88400c3 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Tue, 10 Feb 2026 17:10:32 -0800 Subject: [PATCH 22/33] make sure more matrices are valid on host --- src/redecomp/transfer/MatrixTransfer.cpp | 26 ++++++++- src/redecomp/transfer/MatrixTransfer.hpp | 2 +- src/tribol/mesh/MfemData.cpp | 2 +- src/tribol/utils/ParSparseMat.cpp | 71 +++++++++++++++++++----- src/tribol/utils/ParSparseMat.hpp | 3 + src/tribol/utils/ParVector.cpp | 3 - 6 files changed, 85 insertions(+), 22 deletions(-) diff --git a/src/redecomp/transfer/MatrixTransfer.cpp b/src/redecomp/transfer/MatrixTransfer.cpp index 5dbc25eb..41404774 100644 --- a/src/redecomp/transfer/MatrixTransfer.cpp +++ b/src/redecomp/transfer/MatrixTransfer.cpp @@ -43,7 +43,7 @@ std::unique_ptr MatrixTransfer::TransferToParallel( { auto J_sparse = TransferToParallelSparse( test_elem_idx, trial_elem_idx, src_elem_mat ); J_sparse.Finalize(); - return ConvertToHypreParMatrix( J_sparse, parallel_assemble ); + return ConvertToHypreParMatrix( std::move(J_sparse), parallel_assemble ); } mfem::SparseMatrix MatrixTransfer::TransferToParallelSparse( const axom::Array& test_elem_idx, @@ -142,7 +142,7 @@ mfem::SparseMatrix MatrixTransfer::TransferToParallelSparse( const axom::Array MatrixTransfer::ConvertToHypreParMatrix( mfem::SparseMatrix& sparse, +std::unique_ptr MatrixTransfer::ConvertToHypreParMatrix( mfem::SparseMatrix&& sparse, bool parallel_assemble ) const { SLIC_ERROR_IF( sparse.Height() != parent_test_fes_.GetVSize(), @@ -161,15 +161,35 @@ std::unique_ptr MatrixTransfer::ConvertToHypreParMatrix( m // update the host pointer J_bigint.HostRead(); + // Force hypre to do this on host + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); auto J_full = std::make_unique( getMPIUtility().MPIComm(), parent_test_fes_.GetVSize(), parent_test_fes_.GlobalVSize(), parent_trial_fes_.GlobalVSize(), sparse.GetI(), J_bigint.GetData(), sparse.GetData(), parent_test_fes_.GetDofOffsets(), parent_trial_fes_.GetDofOffsets() ); + sparse.GetMemoryI().ClearOwnerFlags(); + sparse.GetMemoryJ().ClearOwnerFlags(); + sparse.GetMemoryData().ClearOwnerFlags(); + constexpr auto mfem_owned_host_arrays = 3; + constexpr auto hypre_owned_host_arrays = -1; + J_full->SetOwnerFlags( mfem_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); if ( !parallel_assemble ) { + // Return hypre's memory location to what it was before + HYPRE_SetMemoryLocation( old_hypre_mem_location ); return J_full; } else { + auto& P_test = *parent_test_fes_.Dof_TrueDof_Matrix(); + P_test.HostRead(); + auto& P_trial = *parent_trial_fes_.Dof_TrueDof_Matrix(); + P_trial.HostRead(); auto J_true = std::unique_ptr( - mfem::RAP( parent_test_fes_.Dof_TrueDof_Matrix(), J_full.get(), parent_trial_fes_.Dof_TrueDof_Matrix() ) ); + mfem::RAP( &P_test, J_full.get(), &P_trial ) ); + constexpr auto hypre_owned_host_arrays = -1; + J_true->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + // Return hypre's memory location to what it was before + HYPRE_SetMemoryLocation( old_hypre_mem_location ); return J_true; } } diff --git a/src/redecomp/transfer/MatrixTransfer.hpp b/src/redecomp/transfer/MatrixTransfer.hpp index 484699ce..eae8d9a6 100644 --- a/src/redecomp/transfer/MatrixTransfer.hpp +++ b/src/redecomp/transfer/MatrixTransfer.hpp @@ -94,7 +94,7 @@ class MatrixTransfer { * HypreParMatrix returned to transform it to the t-dofs if parallel_assemble * is false. */ - std::unique_ptr ConvertToHypreParMatrix( mfem::SparseMatrix& sparse, + std::unique_ptr ConvertToHypreParMatrix( mfem::SparseMatrix&& sparse, bool parallel_assemble = true ) const; private: diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 9aa7dce1..5e47e327 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -1002,7 +1002,7 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( // Pick xfer again for conversion redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); - auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( *submesh_J, false ); + auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( std::move(*submesh_J.release()), false ); mfem::HypreParMatrix* block_mat = nullptr; diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index 70fc110b..38a7137a 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -16,9 +16,6 @@ namespace tribol { ParSparseMatView::ParSparseMatView( mfem::HypreParMatrix* mat ) : mat_( mat ) { - SLIC_ERROR_ROOT_IF( - mat != nullptr && hypre_ParCSRMatrixMemoryLocation( mat->operator hypre_ParCSRMatrix*() ) != HYPRE_MEMORY_HOST, - "ParSparseMatView currently requires host data." ); } ParSparseMat operator+( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) @@ -39,9 +36,8 @@ ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs HYPRE_MemoryLocation old_hypre_mem_location; HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - mfem::HypreParMatrix* result = mfem::ParMult( lhs.mat_, rhs.mat_ ); - result->CopyRowStarts(); - result->CopyColStarts(); + mfem::HypreParMatrix* result = mfem::ParMult( lhs.mat_, rhs.mat_, true ); + // This is needed so the destructor doesn't think the hypre data is device data constexpr auto hypre_owned_host_arrays = -1; result->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); HYPRE_SetMemoryLocation( old_hypre_mem_location ); @@ -60,23 +56,64 @@ ParVector ParSparseMatView::operator*( const ParVectorView& x ) const return y; } -ParSparseMat ParSparseMatView::transpose() const { return ParSparseMat( mat_->Transpose() ); } +ParSparseMat ParSparseMatView::transpose() const { + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + ParSparseMat mat_transpose( mat_->Transpose() ); + // This is needed so the destructor doesn't think the hypre data is device data + constexpr auto hypre_owned_host_arrays = -1; + mat_transpose->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); + return mat_transpose; +} ParSparseMat ParSparseMatView::square() const { return *this * *this; } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& P ) const { - return ParSparseMat( mfem::RAP( mat_, P.mat_ ) ); + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + mat_->HostRead(); + P->HostRead(); + ParSparseMat rap( mfem::RAP( mat_, P.mat_ ) ); + // This is needed so the destructor doesn't think the hypre data is device data + constexpr auto hypre_owned_host_arrays = -1; + rap->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); + return rap; } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& A, const ParSparseMatView& P ) { - return ParSparseMat( mfem::RAP( A.mat_, P.mat_ ) ); + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + A->HostRead(); + P->HostRead(); + ParSparseMat rap( mfem::RAP( A.mat_, P.mat_ ) ); + // This is needed so the destructor doesn't think the hypre data is device data + constexpr auto hypre_owned_host_arrays = -1; + rap->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); + return rap; } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& R, const ParSparseMatView& A, const ParSparseMatView& P ) { - return ParSparseMat( mfem::RAP( R.mat_, A.mat_, P.mat_ ) ); + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + R->HostRead(); + A->HostRead(); + P->HostRead(); + ParSparseMat rap( mfem::RAP( R.mat_, A.mat_, P.mat_ ) ); + // This is needed so the destructor doesn't think the hypre data is device data + constexpr auto hypre_owned_host_arrays = -1; + rap->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); + return rap; } void ParSparseMatView::EliminateRows( const mfem::Array& rows ) @@ -91,12 +128,16 @@ void ParSparseMatView::EliminateRows( const mfem::Array& rows ) ParSparseMat ParSparseMatView::EliminateCols( const mfem::Array& cols ) { - // Prevent HypreParMatrix::EliminateRows from changing memory from host to device + // Prevent HypreParMatrix::EliminateCols from changing memory from host to device HYPRE_MemoryLocation old_hypre_mem_location; HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - return ParSparseMat( mat_->EliminateCols( cols ) ); + ParSparseMat elim_cols_mat( mat_->EliminateCols( cols ) ); + // This is needed so the destructor doesn't think the hypre data is device data + constexpr auto hypre_owned_host_arrays = -1; + elim_cols_mat->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); HYPRE_SetMemoryLocation( old_hypre_mem_location ); + return elim_cols_mat; } ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s; } @@ -120,6 +161,7 @@ ParSparseMat ParSparseMatView::add( RealT alpha, const ParSparseMatView& A, Real HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mfem::HypreParMatrix* result = mfem::Add( alpha, A.get(), beta, B.get() ); + // This is needed so the destructor doesn't think the hypre data is device data constexpr auto hypre_owned_host_arrays = -1; result->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); HYPRE_SetMemoryLocation( old_hypre_mem_location ); @@ -148,6 +190,7 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* diag.GetMemoryJ().ClearOwnerFlags(); diag.GetMemoryData().ClearOwnerFlags(); constexpr auto mfem_owned_host_arrays = 3; + // This is needed so the destructor doesn't think the hypre data is device data constexpr auto hypre_owned_host_arrays = -1; owned_mat_->SetOwnerFlags( mfem_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); // Return hypre's memory location to what it was before @@ -271,8 +314,8 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si HYPRE_SetMemoryLocation( old_hypre_mem_location ); diag_hpm->CopyRowStarts(); diag_hpm->CopyColStarts(); - constexpr auto mfem_owned_host_arrays = 3; - diag_hpm->SetOwnerFlags( mfem_owned_host_arrays, mfem_owned_host_arrays, diag_hpm->OwnsColMap() ); + constexpr auto hypre_owned_host_arrays = -1; + diag_hpm->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); return ParSparseMat( std::move( diag_hpm ) ); } diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/tribol/utils/ParSparseMat.hpp index bf813c09..ab2066e9 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/tribol/utils/ParSparseMat.hpp @@ -190,6 +190,9 @@ class ParSparseMat : public ParSparseMatView { HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); owned_mat_ = std::make_unique( std::forward( args )... ); + // This is needed so the destructor doesn't think the hypre data is device data + constexpr auto hypre_owned_host_arrays = -1; + owned_mat_->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); // Return hypre's memory location to what it was before HYPRE_SetMemoryLocation( old_hypre_mem_location ); mat_ = owned_mat_.get(); diff --git a/src/tribol/utils/ParVector.cpp b/src/tribol/utils/ParVector.cpp index ee2a7f2d..7c42b97c 100644 --- a/src/tribol/utils/ParVector.cpp +++ b/src/tribol/utils/ParVector.cpp @@ -15,9 +15,6 @@ namespace tribol { ParVectorView::ParVectorView( mfem::HypreParVector* vec ) : vec_( vec ) { - SLIC_ERROR_ROOT_IF( - vec != nullptr && hypre_ParVectorMemoryLocation( vec->operator hypre_ParVector*() ) != HYPRE_MEMORY_HOST, - "ParVectorView currently requires host data." ); } ParVector::ParVector( mfem::HypreParVector* vec ) : ParVectorView( vec ), owned_vec_( vec ) {} From 039d4011fb7b4bdff2d6ebbe206f86c421dd3da0 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Tue, 10 Feb 2026 17:15:06 -0800 Subject: [PATCH 23/33] formatting --- src/redecomp/transfer/MatrixTransfer.cpp | 5 ++--- src/tribol/mesh/MfemData.cpp | 2 +- src/tribol/utils/ParSparseMat.cpp | 7 +++---- src/tribol/utils/ParVector.cpp | 4 +--- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/redecomp/transfer/MatrixTransfer.cpp b/src/redecomp/transfer/MatrixTransfer.cpp index 41404774..6823bcf6 100644 --- a/src/redecomp/transfer/MatrixTransfer.cpp +++ b/src/redecomp/transfer/MatrixTransfer.cpp @@ -43,7 +43,7 @@ std::unique_ptr MatrixTransfer::TransferToParallel( { auto J_sparse = TransferToParallelSparse( test_elem_idx, trial_elem_idx, src_elem_mat ); J_sparse.Finalize(); - return ConvertToHypreParMatrix( std::move(J_sparse), parallel_assemble ); + return ConvertToHypreParMatrix( std::move( J_sparse ), parallel_assemble ); } mfem::SparseMatrix MatrixTransfer::TransferToParallelSparse( const axom::Array& test_elem_idx, @@ -184,8 +184,7 @@ std::unique_ptr MatrixTransfer::ConvertToHypreParMatrix( m P_test.HostRead(); auto& P_trial = *parent_trial_fes_.Dof_TrueDof_Matrix(); P_trial.HostRead(); - auto J_true = std::unique_ptr( - mfem::RAP( &P_test, J_full.get(), &P_trial ) ); + auto J_true = std::unique_ptr( mfem::RAP( &P_test, J_full.get(), &P_trial ) ); constexpr auto hypre_owned_host_arrays = -1; J_true->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); // Return hypre's memory location to what it was before diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 5e47e327..200f2779 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -1002,7 +1002,7 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( // Pick xfer again for conversion redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); - auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( std::move(*submesh_J.release()), false ); + auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( std::move( *submesh_J.release() ), false ); mfem::HypreParMatrix* block_mat = nullptr; diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/tribol/utils/ParSparseMat.cpp index 38a7137a..fb97256c 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/tribol/utils/ParSparseMat.cpp @@ -14,9 +14,7 @@ namespace tribol { // ParSparseMatView implementations -ParSparseMatView::ParSparseMatView( mfem::HypreParMatrix* mat ) : mat_( mat ) -{ -} +ParSparseMatView::ParSparseMatView( mfem::HypreParMatrix* mat ) : mat_( mat ) {} ParSparseMat operator+( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { @@ -56,7 +54,8 @@ ParVector ParSparseMatView::operator*( const ParVectorView& x ) const return y; } -ParSparseMat ParSparseMatView::transpose() const { +ParSparseMat ParSparseMatView::transpose() const +{ HYPRE_MemoryLocation old_hypre_mem_location; HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); diff --git a/src/tribol/utils/ParVector.cpp b/src/tribol/utils/ParVector.cpp index 7c42b97c..eb7a3d56 100644 --- a/src/tribol/utils/ParVector.cpp +++ b/src/tribol/utils/ParVector.cpp @@ -13,9 +13,7 @@ namespace tribol { -ParVectorView::ParVectorView( mfem::HypreParVector* vec ) : vec_( vec ) -{ -} +ParVectorView::ParVectorView( mfem::HypreParVector* vec ) : vec_( vec ) {} ParVector::ParVector( mfem::HypreParVector* vec ) : ParVectorView( vec ), owned_vec_( vec ) {} From ac9d7f846b856346a2eefdabcf272ca4fedd0aa2 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Tue, 10 Feb 2026 22:03:17 -0800 Subject: [PATCH 24/33] move ParSparseMat and ParVector to tribol_shared --- src/redecomp/transfer/MatrixTransfer.cpp | 43 +++---- src/redecomp/transfer/MatrixTransfer.hpp | 19 +-- src/shared/CMakeLists.txt | 9 +- src/shared/common/BasicTypes.hpp | 108 ++++++++++++++++++ src/shared/config.hpp.in | 7 ++ .../utils => shared/math}/ParSparseMat.cpp | 16 ++- .../utils => shared/math}/ParSparseMat.hpp | 27 +++-- .../utils => shared/math}/ParVector.cpp | 14 ++- .../utils => shared/math}/ParVector.hpp | 19 +-- src/tests/CMakeLists.txt | 19 +-- ...arse_mat.cpp => shared_par_sparse_mat.cpp} | 98 ++++++++-------- ...l_par_vector.cpp => shared_par_vector.cpp} | 55 +++++---- src/tribol/CMakeLists.txt | 6 +- src/tribol/common/BasicTypes.hpp | 100 +--------------- src/tribol/config.hpp.in | 10 +- src/tribol/interface/mfem_tribol.cpp | 4 +- src/tribol/mesh/MfemData.cpp | 30 +++-- src/tribol/mesh/MfemData.hpp | 6 +- 18 files changed, 303 insertions(+), 287 deletions(-) create mode 100644 src/shared/common/BasicTypes.hpp rename src/{tribol/utils => shared/math}/ParSparseMat.cpp (97%) rename src/{tribol/utils => shared/math}/ParSparseMat.hpp (94%) rename src/{tribol/utils => shared/math}/ParVector.cpp (95%) rename src/{tribol/utils => shared/math}/ParVector.hpp (95%) rename src/tests/{tribol_par_sparse_mat.cpp => shared_par_sparse_mat.cpp} (80%) rename src/tests/{tribol_par_vector.cpp => shared_par_vector.cpp} (83%) diff --git a/src/redecomp/transfer/MatrixTransfer.cpp b/src/redecomp/transfer/MatrixTransfer.cpp index 6823bcf6..3e764392 100644 --- a/src/redecomp/transfer/MatrixTransfer.cpp +++ b/src/redecomp/transfer/MatrixTransfer.cpp @@ -10,6 +10,7 @@ #include "mfem/general/forall.hpp" #include "redecomp/RedecompMesh.hpp" +#include "shared/math/ParSparseMat.hpp" namespace redecomp { @@ -37,13 +38,14 @@ MatrixTransfer::MatrixTransfer( const mfem::ParFiniteElementSpace& parent_test_f test_r2p_elem_rank_ = buildRedecomp2ParentElemRank( *test_redecomp, true ); } -std::unique_ptr MatrixTransfer::TransferToParallel( - const axom::Array& test_elem_idx, const axom::Array& trial_elem_idx, - const axom::Array& src_elem_mat, bool parallel_assemble ) const +shared::ParSparseMat MatrixTransfer::TransferToParallel( const axom::Array& test_elem_idx, + const axom::Array& trial_elem_idx, + const axom::Array& src_elem_mat, + bool parallel_assemble ) const { auto J_sparse = TransferToParallelSparse( test_elem_idx, trial_elem_idx, src_elem_mat ); J_sparse.Finalize(); - return ConvertToHypreParMatrix( std::move( J_sparse ), parallel_assemble ); + return ConvertToParSparseMat( std::move( J_sparse ), parallel_assemble ); } mfem::SparseMatrix MatrixTransfer::TransferToParallelSparse( const axom::Array& test_elem_idx, @@ -142,8 +144,7 @@ mfem::SparseMatrix MatrixTransfer::TransferToParallelSparse( const axom::Array MatrixTransfer::ConvertToHypreParMatrix( mfem::SparseMatrix&& sparse, - bool parallel_assemble ) const +shared::ParSparseMat MatrixTransfer::ConvertToParSparseMat( mfem::SparseMatrix&& sparse, bool parallel_assemble ) const { SLIC_ERROR_IF( sparse.Height() != parent_test_fes_.GetVSize(), "Height of sparse must match number of test ParFiniteElementSpace L-dofs." ); @@ -161,34 +162,20 @@ std::unique_ptr MatrixTransfer::ConvertToHypreParMatrix( m // update the host pointer J_bigint.HostRead(); - // Force hypre to do this on host - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - auto J_full = std::make_unique( - getMPIUtility().MPIComm(), parent_test_fes_.GetVSize(), parent_test_fes_.GlobalVSize(), - parent_trial_fes_.GlobalVSize(), sparse.GetI(), J_bigint.GetData(), sparse.GetData(), - parent_test_fes_.GetDofOffsets(), parent_trial_fes_.GetDofOffsets() ); + shared::ParSparseMat J_full( getMPIUtility().MPIComm(), parent_test_fes_.GetVSize(), parent_test_fes_.GlobalVSize(), + parent_trial_fes_.GlobalVSize(), sparse.GetI(), J_bigint.GetData(), sparse.GetData(), + parent_test_fes_.GetDofOffsets(), parent_trial_fes_.GetDofOffsets() ); sparse.GetMemoryI().ClearOwnerFlags(); sparse.GetMemoryJ().ClearOwnerFlags(); sparse.GetMemoryData().ClearOwnerFlags(); - constexpr auto mfem_owned_host_arrays = 3; - constexpr auto hypre_owned_host_arrays = -1; - J_full->SetOwnerFlags( mfem_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); if ( !parallel_assemble ) { - // Return hypre's memory location to what it was before - HYPRE_SetMemoryLocation( old_hypre_mem_location ); return J_full; } else { - auto& P_test = *parent_test_fes_.Dof_TrueDof_Matrix(); - P_test.HostRead(); - auto& P_trial = *parent_trial_fes_.Dof_TrueDof_Matrix(); - P_trial.HostRead(); - auto J_true = std::unique_ptr( mfem::RAP( &P_test, J_full.get(), &P_trial ) ); - constexpr auto hypre_owned_host_arrays = -1; - J_true->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - // Return hypre's memory location to what it was before - HYPRE_SetMemoryLocation( old_hypre_mem_location ); + auto P_test = parent_test_fes_.Dof_TrueDof_Matrix(); + P_test->HostRead(); + auto P_trial = parent_trial_fes_.Dof_TrueDof_Matrix(); + P_trial->HostRead(); + auto J_true = shared::ParSparseMat::RAP( P_test, J_full, P_trial ); return J_true; } } diff --git a/src/redecomp/transfer/MatrixTransfer.hpp b/src/redecomp/transfer/MatrixTransfer.hpp index eae8d9a6..d73b5bd7 100644 --- a/src/redecomp/transfer/MatrixTransfer.hpp +++ b/src/redecomp/transfer/MatrixTransfer.hpp @@ -8,7 +8,11 @@ #include "mfem.hpp" -#include "redecomp/common/TypeDefs.hpp" +#include "axom/core.hpp" + +#include "shared/math/ParSparseMat.hpp" + +#include "redecomp/utils/MPIArray.hpp" namespace redecomp { @@ -26,7 +30,7 @@ class RedecompMesh; * two-stage transfer process is available. First, TransferToParallel() creates * an un-finalized mfem::SparseMatrix with ldofs on the rows and global ldofs on * the columns. This mfem::SparseMatrix is designed to be passed to a - * mfem::HypreParMatrix constructor (done through the ConvertToHypreParMatrix() + * mfem::HypreParMatrix constructor (done through the ConvertToParSparseMat() * method). The two-stage process allows easier manipulation of matrix * contributions before the matrix is finalized. Both square and rectangular * matrices are supported, necessitating test and trial finite element spaces in @@ -61,10 +65,10 @@ class MatrixTransfer { * HypreParMatrix returned to transform it to the t-dofs if parallel_assemble * is false. */ - std::unique_ptr TransferToParallel( const axom::Array& test_elem_idx, - const axom::Array& trial_elem_idx, - const axom::Array& src_elem_mat, - bool parallel_assemble = true ) const; + shared::ParSparseMat TransferToParallel( const axom::Array& test_elem_idx, + const axom::Array& trial_elem_idx, + const axom::Array& src_elem_mat, + bool parallel_assemble = true ) const; /** * @brief Transfers element RedecompMesh matrices to parent mfem::ParMesh @@ -94,8 +98,7 @@ class MatrixTransfer { * HypreParMatrix returned to transform it to the t-dofs if parallel_assemble * is false. */ - std::unique_ptr ConvertToHypreParMatrix( mfem::SparseMatrix&& sparse, - bool parallel_assemble = true ) const; + shared::ParSparseMat ConvertToParSparseMat( mfem::SparseMatrix&& sparse, bool parallel_assemble = true ) const; private: /** diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index b1b05cd9..539ba745 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -6,18 +6,23 @@ ## list of headers set(shared_headers + common/BasicTypes.hpp infrastructure/Profiling.hpp + math/ParSparseMat.hpp + math/ParVector.hpp mesh/MeshBuilder.hpp ) ## list of sources set(shared_sources + math/ParSparseMat.cpp + math/ParVector.cpp mesh/MeshBuilder.cpp ) ## setup the dependency list for tribol shared library -set(shared_depends mfem) +set(shared_depends mfem axom::core) blt_list_append(TO shared_depends ELEMENTS blt::mpi IF TRIBOL_USE_MPI ) blt_list_append(TO shared_depends ELEMENTS caliper IF TRIBOL_USE_CALIPER ) message(STATUS "Tribol shared library dependencies: ${shared_depends}") @@ -31,7 +36,7 @@ blt_add_library( FOLDER shared ) -# Add separable compilation flag +# Add debug flag if(ENABLE_HIP) target_compile_options(tribol_shared PRIVATE $<$: diff --git a/src/shared/common/BasicTypes.hpp b/src/shared/common/BasicTypes.hpp new file mode 100644 index 00000000..76fb9f65 --- /dev/null +++ b/src/shared/common/BasicTypes.hpp @@ -0,0 +1,108 @@ +// 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) + +#ifndef SRC_SHARED_COMMON_BASICTYPES_HPP_ +#define SRC_SHARED_COMMON_BASICTYPES_HPP_ + +// Tribol config include +#include "shared/config.hpp" + +// C includes +#include + +// C++ includes +#include + +// MPI includes +#ifdef TRIBOL_USE_MPI +#include +#endif + +// Axom includes +#include "axom/core/Types.hpp" + +// MFEM includes +#include "mfem.hpp" + +namespace shared { + +#ifdef TRIBOL_USE_MPI + +using CommT = MPI_Comm; +#define TRIBOL_COMM_WORLD MPI_COMM_WORLD +#define TRIBOL_COMM_NULL MPI_COMM_NULL + +#else + +using CommT = int; +#define TRIBOL_COMM_WORLD 0 +#define TRIBOL_COMM_NULL -1 + +#endif + +// match index type used in axom (since data is held in axom data structures) +using IndexT = axom::IndexType; + +// size type matching size of addressable memory +using SizeT = size_t; + +#ifdef TRIBOL_USE_SINGLE_PRECISION + +#error "Tribol does not support single precision." +using RealT = float; + +#else + +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 + +// Execution space specifiers +#if defined( TRIBOL_USE_CUDA ) || defined( TRIBOL_USE_HIP ) +#ifndef __device__ +#error "TRIBOL_USE_CUDA or TRIBOL_USE_HIP but __device__ is undefined. Check include files" +#endif +#define TRIBOL_DEVICE __device__ +#define TRIBOL_HOST_DEVICE __host__ __device__ +#else +#define TRIBOL_DEVICE +#define TRIBOL_HOST_DEVICE +#endif + +// Execution space identifier for defaulted constructors and destructors +#ifdef TRIBOL_USE_HIP +#define TRIBOL_DEFAULT_DEVICE __device__ +#define TRIBOL_DEFAULT_HOST_DEVICE __host__ __device__ +#else +#define TRIBOL_DEFAULT_DEVICE +#define TRIBOL_DEFAULT_HOST_DEVICE +#endif + +// Defined when Tribol doesn't have a device available +#if !( defined( TRIBOL_USE_CUDA ) || defined( TRIBOL_USE_HIP ) ) +#define TRIBOL_USE_HOST +#endif + +// Define variable when in device code +#if defined( __CUDA_ARCH__ ) || defined( __HIP_DEVICE_COMPILE__ ) +#define TRIBOL_DEVICE_CODE +#endif + +// Ignore host code in __host__ __device__ code warning on NVCC +#ifdef TRIBOL_USE_CUDA +#define TRIBOL_NVCC_EXEC_CHECK_DISABLE #pragma nv_exec_check_disable +#else +#define TRIBOL_NVCC_EXEC_CHECK_DISABLE +#endif + +} // namespace shared + +#endif /* SRC_SHARED_COMMON_BASICTYPES_HPP_ */ diff --git a/src/shared/config.hpp.in b/src/shared/config.hpp.in index c421ee4c..dc8cb948 100644 --- a/src/shared/config.hpp.in +++ b/src/shared/config.hpp.in @@ -15,6 +15,13 @@ * Build information */ #cmakedefine TRIBOL_USE_CALIPER +#cmakedefine TRIBOL_USE_ENZYME +#cmakedefine TRIBOL_USE_RAJA +#cmakedefine TRIBOL_USE_UMPIRE +#cmakedefine TRIBOL_USE_SINGLE_PRECISION #cmakedefine TRIBOL_USE_MPI +#cmakedefine TRIBOL_USE_CUDA +#cmakedefine TRIBOL_USE_HIP +#cmakedefine TRIBOL_USE_OPENMP #endif /* SHARED_CONFIG_HPP_ */ diff --git a/src/tribol/utils/ParSparseMat.cpp b/src/shared/math/ParSparseMat.cpp similarity index 97% rename from src/tribol/utils/ParSparseMat.cpp rename to src/shared/math/ParSparseMat.cpp index fb97256c..0fd88ad8 100644 --- a/src/tribol/utils/ParSparseMat.cpp +++ b/src/shared/math/ParSparseMat.cpp @@ -3,14 +3,16 @@ // // SPDX-License-Identifier: (MIT) -#include "tribol/utils/ParSparseMat.hpp" +#include "shared/math/ParSparseMat.hpp" #include #include <_hypre_parcsr_mv.h> #include "axom/slic.hpp" -namespace tribol { +namespace shared { + +#ifdef TRIBOL_USE_MPI // ParSparseMatView implementations @@ -99,15 +101,15 @@ ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& A, const ParSparseMa return rap; } -ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& R, const ParSparseMatView& A, const ParSparseMatView& P ) +ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& Rt, const ParSparseMatView& A, const ParSparseMatView& P ) { HYPRE_MemoryLocation old_hypre_mem_location; HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - R->HostRead(); + Rt->HostRead(); A->HostRead(); P->HostRead(); - ParSparseMat rap( mfem::RAP( R.mat_, A.mat_, P.mat_ ) ); + ParSparseMat rap( mfem::RAP( Rt.mat_, A.mat_, P.mat_ ) ); // This is needed so the destructor doesn't think the hypre data is device data constexpr auto hypre_owned_host_arrays = -1; rap->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); @@ -328,4 +330,6 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si return diagonalMatrix( comm, global_size, row_starts_array, diag_val, ordered_rows, skip_rows ); } -} // namespace tribol +#endif // #ifdef TRIBOL_USE_MPI + +} // namespace shared diff --git a/src/tribol/utils/ParSparseMat.hpp b/src/shared/math/ParSparseMat.hpp similarity index 94% rename from src/tribol/utils/ParSparseMat.hpp rename to src/shared/math/ParSparseMat.hpp index ab2066e9..dbe225aa 100644 --- a/src/tribol/utils/ParSparseMat.hpp +++ b/src/shared/math/ParSparseMat.hpp @@ -3,20 +3,21 @@ // // SPDX-License-Identifier: (MIT) -#ifndef SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ -#define SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ +#ifndef SRC_SHARED_MATH_PARSPARSEMAT_HPP_ +#define SRC_SHARED_MATH_PARSPARSEMAT_HPP_ -#include -#include +#include "shared/config.hpp" -#include "tribol/config.hpp" +#include #include "mfem.hpp" -#include "tribol/common/BasicTypes.hpp" -#include "tribol/utils/ParVector.hpp" +#include "shared/common/BasicTypes.hpp" +#include "shared/math/ParVector.hpp" -namespace tribol { +namespace shared { + +#ifdef TRIBOL_USE_MPI class ParSparseMat; @@ -113,9 +114,9 @@ class ParSparseMatView { static ParSparseMat RAP( const ParSparseMatView& A, const ParSparseMatView& P ); /** - * @brief Returns R * A * P + * @brief Returns Rt^T * A * P */ - static ParSparseMat RAP( const ParSparseMatView& R, const ParSparseMatView& A, const ParSparseMatView& P ); + static ParSparseMat RAP( const ParSparseMatView& Rt, const ParSparseMatView& A, const ParSparseMatView& P ); /** * @brief Eliminates the rows from the matrix @@ -266,6 +267,8 @@ class ParSparseMat : public ParSparseMatView { std::unique_ptr owned_mat_; }; -} // namespace tribol +#endif // #ifdef TRIBOL_USE_MPI + +} // namespace shared -#endif /* SRC_TRIBOL_UTILS_PARSPARSEMAT_HPP_ */ +#endif /* SRC_SHARED_MATH_PARSPARSEMAT_HPP_ */ diff --git a/src/tribol/utils/ParVector.cpp b/src/shared/math/ParVector.cpp similarity index 95% rename from src/tribol/utils/ParVector.cpp rename to src/shared/math/ParVector.cpp index eb7a3d56..2fab479c 100644 --- a/src/tribol/utils/ParVector.cpp +++ b/src/shared/math/ParVector.cpp @@ -3,15 +3,15 @@ // // SPDX-License-Identifier: (MIT) -#include "tribol/utils/ParVector.hpp" - -#include <_hypre_parcsr_mv.h> +#include "shared/math/ParVector.hpp" #include "axom/slic.hpp" -#include "tribol/common/BasicTypes.hpp" +#include "shared/common/BasicTypes.hpp" + +namespace shared { -namespace tribol { +#ifdef TRIBOL_USE_MPI ParVectorView::ParVectorView( mfem::HypreParVector* vec ) : vec_( vec ) {} @@ -148,4 +148,6 @@ ParVector& ParVector::divideInPlace( const ParVectorView& other ) return *this; } -} // namespace tribol +#endif // #ifdef TRIBOL_USE_MPI + +} // namespace shared diff --git a/src/tribol/utils/ParVector.hpp b/src/shared/math/ParVector.hpp similarity index 95% rename from src/tribol/utils/ParVector.hpp rename to src/shared/math/ParVector.hpp index 27f8ed8f..d9d1f973 100644 --- a/src/tribol/utils/ParVector.hpp +++ b/src/shared/math/ParVector.hpp @@ -3,16 +3,19 @@ // // SPDX-License-Identifier: (MIT) -#ifndef SRC_TRIBOL_UTILS_PARVECTOR_HPP_ -#define SRC_TRIBOL_UTILS_PARVECTOR_HPP_ +#ifndef SRC_SHARED_MATH_PARVECTOR_HPP_ +#define SRC_SHARED_MATH_PARVECTOR_HPP_ -#include "tribol/config.hpp" -#include "mfem.hpp" +#include "shared/config.hpp" #include #include -namespace tribol { +#include "mfem.hpp" + +namespace shared { + +#ifdef TRIBOL_USE_MPI class ParVector; @@ -205,6 +208,8 @@ class ParVector : public ParVectorView { std::unique_ptr owned_vec_; }; -} // namespace tribol +#endif // #ifdef TRIBOL_USE_MPI + +} // namespace shared -#endif /* SRC_TRIBOL_UTILS_PARVECTOR_HPP_ */ +#endif /* SRC_SHARED_MATH_PARVECTOR_HPP_ */ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 1c1f8777..90fe6f37 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -65,18 +65,18 @@ foreach( test ${tribol_tests} ) endforeach() #------------------------------------------------------------------------------ -# Add MPI tests +# Add shared tests #------------------------------------------------------------------------------ if ( TRIBOL_USE_MPI ) - set( mpi_tests - tribol_par_sparse_mat.cpp - tribol_par_vector.cpp + set( shared_tests + shared_par_sparse_mat.cpp + shared_par_vector.cpp ) - set(mpi_test_depends tribol gtest) + set(mpi_test_depends tribol_shared gtest) - foreach( test ${mpi_tests} ) + foreach( test ${shared_tests} ) get_filename_component( test_name ${test} NAME_WE ) @@ -91,19 +91,12 @@ if ( TRIBOL_USE_MPI ) COMMAND ${test_name}_test NUM_MPI_TASKS 2 ) - if (ENABLE_CUDA) - set_target_properties(${test_name}_test PROPERTIES CUDA_SEPARABLE_COMPILATION On) - endif() - if (ENABLE_HIP) - target_compile_options(${test_name}_test PRIVATE -fgpu-rdc) target_compile_options(${test_name}_test PRIVATE $<$: -ggdb > ) - target_link_options(${test_name}_test PRIVATE -fgpu-rdc) - target_link_options(${test_name}_test PRIVATE --hip-link) endif() endforeach() diff --git a/src/tests/tribol_par_sparse_mat.cpp b/src/tests/shared_par_sparse_mat.cpp similarity index 80% rename from src/tests/tribol_par_sparse_mat.cpp rename to src/tests/shared_par_sparse_mat.cpp index 73657694..65c7ae4b 100644 --- a/src/tests/tribol_par_sparse_mat.cpp +++ b/src/tests/shared_par_sparse_mat.cpp @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: (MIT) -#include "tribol/config.hpp" +#include "shared/config.hpp" #include @@ -13,8 +13,8 @@ #include "mfem.hpp" -#include "tribol/utils/ParVector.hpp" -#include "tribol/utils/ParSparseMat.hpp" +#include "shared/math/ParVector.hpp" +#include "shared/math/ParSparseMat.hpp" class ParSparseMatTest : public ::testing::Test { protected: @@ -64,14 +64,14 @@ TEST_F( ParSparseMatTest, Construction ) // 1. From mfem::HypreParMatrix* mfem::HypreParMatrix* m1 = - tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 1.0 ).release(); - tribol::ParSparseMat psm1( m1 ); + shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 1.0 ).release(); + shared::ParSparseMat psm1( m1 ); EXPECT_EQ( psm1.Height(), local_size ); // 2. From unique_ptr auto m2 = std::unique_ptr( - tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 2.0 ).release() ); - tribol::ParSparseMat psm2( std::move( m2 ) ); + shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts_array, 2.0 ).release() ); + shared::ParSparseMat psm2( std::move( m2 ) ); EXPECT_EQ( psm2.Height(), local_size ); // 3. From SparseMatrix rvalue @@ -79,7 +79,7 @@ TEST_F( ParSparseMatTest, Construction ) for ( int i = 0; i < local_size; ++i ) diag.Set( i, i, 3.0 ); diag.Finalize(); - tribol::ParSparseMat psm3( MPI_COMM_WORLD, (HYPRE_BigInt)size, row_starts_array.GetData(), std::move( diag ) ); + shared::ParSparseMat psm3( MPI_COMM_WORLD, (HYPRE_BigInt)size, row_starts_array.GetData(), std::move( diag ) ); EXPECT_EQ( psm3.Height(), local_size ); mfem::Vector x( local_size ), y( local_size ); @@ -96,16 +96,16 @@ TEST_F( ParSparseMatTest, View ) if ( rank == 0 ) std::cout << "Testing View..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); // Construct View - tribol::ParSparseMatView view( &A.get() ); + shared::ParSparseMatView view( &A.get() ); EXPECT_EQ( view.Height(), A.Height() ); // Operate on View - tribol::ParSparseMat B = view * 2.0; - tribol::ParVector x( B.get() ); + shared::ParSparseMat B = view * 2.0; + shared::ParVector x( B.get() ); x.Fill( 1.0 ); auto y = B * x; EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); @@ -119,11 +119,11 @@ TEST_F( ParSparseMatTest, Addition ) if ( rank == 0 ) std::cout << "Testing Addition..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); - tribol::ParSparseMat B = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + shared::ParSparseMat B = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); // A + B - tribol::ParSparseMat C = A + B; + shared::ParSparseMat C = A + B; mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; C->Mult( x, y ); @@ -145,11 +145,11 @@ TEST_F( ParSparseMatTest, Subtraction ) if ( rank == 0 ) std::cout << "Testing Subtraction..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 5.0 ); - tribol::ParSparseMat B = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 5.0 ); + shared::ParSparseMat B = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); // A - B - tribol::ParSparseMat C = A - B; + shared::ParSparseMat C = A - B; mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; C->Mult( x, y ); @@ -170,17 +170,17 @@ TEST_F( ParSparseMatTest, ScalarMult ) if ( rank == 0 ) std::cout << "Testing Scalar Multiplication..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); // A * s - tribol::ParSparseMat B = A * 3.0; + shared::ParSparseMat B = A * 3.0; mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; B->Mult( x, y ); EXPECT_NEAR( y.Max(), 6.0, 1e-12 ); // s * A - tribol::ParSparseMat C = 4.0 * A; + shared::ParSparseMat C = 4.0 * A; C->Mult( x, y ); EXPECT_NEAR( y.Max(), 8.0, 1e-12 ); } @@ -193,11 +193,11 @@ TEST_F( ParSparseMatTest, MatrixMult ) if ( rank == 0 ) std::cout << "Testing Matrix Multiplication..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); - tribol::ParSparseMat B = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + shared::ParSparseMat B = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); // A * B - tribol::ParSparseMat C = A * B; + shared::ParSparseMat C = A * B; mfem::Vector x( A.Width() ), y( A.Height() ); x = 1.0; C->Mult( x, y ); @@ -218,17 +218,17 @@ TEST_F( ParSparseMatTest, MatVecMult ) if ( rank == 0 ) std::cout << "Testing Matrix-Vector Multiplication..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); HYPRE_MemoryLocation old_hypre_mem_location; HYPRE_GetMemoryLocation( &old_hypre_mem_location ); HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mfem::HypreParVector x_hypre( A.get(), 1 ); HYPRE_SetMemoryLocation( old_hypre_mem_location ); x_hypre = 1.0; - tribol::ParVectorView x( &x_hypre ); + shared::ParVectorView x( &x_hypre ); // y = A * x - tribol::ParVector y = A * x; + shared::ParVector y = A * x; EXPECT_NEAR( y.Max(), 2.0, 1e-12 ); } @@ -240,12 +240,12 @@ TEST_F( ParSparseMatTest, VecMatMult ) if ( rank == 0 ) std::cout << "Testing Vector-Matrix Multiplication..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); - tribol::ParVector x( A.get(), 0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + shared::ParVector x( A.get(), 0 ); x.Fill( 1.0 ); // y = x^T * A - tribol::ParVector y = x * A; + shared::ParVector y = x * A; EXPECT_NEAR( y.Max(), 3.0, 1e-12 ); EXPECT_NEAR( y.Min(), 3.0, 1e-12 ); } @@ -258,7 +258,7 @@ TEST_F( ParSparseMatTest, Elimination ) if ( rank == 0 ) std::cout << "Testing Elimination..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); // Eliminate row 0 (globally) // Determine if I own row 0 @@ -271,9 +271,9 @@ TEST_F( ParSparseMatTest, Elimination ) // Check if row 0 is identity (or zero with diagonal 1) // Diagonal matrix means we can just check multiplication - tribol::ParVector x( A.get(), 1 ); + shared::ParVector x( A.get(), 1 ); x.Fill( 1.0 ); - tribol::ParVector y = A * x; // y = A * x + shared::ParVector y = A * x; // y = A * x // if rank owns row 0, the result for that row should be 0.0 * x[0] = 0.0 (since diag is 0.0) // other rows should be 3.0 @@ -290,7 +290,7 @@ TEST_F( ParSparseMatTest, Elimination ) } } - A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); + A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 3.0 ); int num_procs; MPI_Comm_size( MPI_COMM_WORLD, &num_procs ); @@ -298,18 +298,18 @@ TEST_F( ParSparseMatTest, Elimination ) auto last_local_col = A.Width() - 1; mfem::Array cols_to_elim( { last_local_col } ); - tribol::ParSparseMat Ae = A.EliminateCols( cols_to_elim ); + shared::ParSparseMat Ae = A.EliminateCols( cols_to_elim ); // Now check A * e_last = 0 // Create vector with 1 at last_local_col, 0 elsewhere - tribol::ParVector x_last( A.get(), 1 ); + shared::ParVector x_last( A.get(), 1 ); x_last.Fill( 0.0 ); x_last[last_local_col] = 1.0; - tribol::ParVector y_last = A * x_last; + shared::ParVector y_last = A * x_last; EXPECT_NEAR( y_last[last_local_col], 0.0, 1e-12 ); // Check Ae * e_last = original value - tribol::ParVector ye = Ae * x_last; + shared::ParVector ye = Ae * x_last; double expected_val = 3.0; EXPECT_NEAR( ye[last_local_col], expected_val, 1e-12 ); @@ -323,17 +323,17 @@ TEST_F( ParSparseMatTest, TransposeSquare ) if ( rank == 0 ) std::cout << "Testing Transpose and Square..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 2.0 ); // Transpose (Diagonal matrix is symmetric) - tribol::ParSparseMat At = A.transpose(); - tribol::ParVector x( At.get(), 0 ); + shared::ParSparseMat At = A.transpose(); + shared::ParVector x( At.get(), 0 ); x.Fill( 1.0 ); auto y = At * x; EXPECT_NEAR( y.Max(), 2.0, 1e-12 ); // Square - tribol::ParSparseMat A2 = A.square(); + shared::ParSparseMat A2 = A.square(); y = A2 * x; EXPECT_NEAR( y.Max(), 4.0, 1e-12 ); } @@ -347,19 +347,19 @@ TEST_F( ParSparseMatTest, RAP ) // Use Identity for P to simplify testing: P^T * A * P = I * A * I = A auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 5.0 ); - tribol::ParSparseMat P = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 1.0 ); - tribol::ParSparseMat R = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 1.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 5.0 ); + shared::ParSparseMat P = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 1.0 ); + shared::ParSparseMat R = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, 10, row_starts, 1.0 ); // RAP(P) - tribol::ParSparseMat Res1 = A.RAP( P ); - tribol::ParVector x( A.get(), 0 ); + shared::ParSparseMat Res1 = A.RAP( P ); + shared::ParVector x( A.get(), 0 ); x.Fill( 1.0 ); auto y = Res1 * x; EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); // RAP(R, A, P) - tribol::ParSparseMat Res2 = tribol::ParSparseMat::RAP( R, A, P ); + shared::ParSparseMat Res2 = shared::ParSparseMat::RAP( R, A, P ); y = Res2 * x; EXPECT_NEAR( y.Max(), 5.0, 1e-12 ); } @@ -376,7 +376,7 @@ TEST_F( ParSparseMatTest, Accessors ) if ( rank == 0 ) std::cout << "Testing Accessors..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, size ); - tribol::ParSparseMat A = tribol::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts, 1.0 ); + shared::ParSparseMat A = shared::ParSparseMat::diagonalMatrix( MPI_COMM_WORLD, size, row_starts, 1.0 ); // get() EXPECT_EQ( A.Height(), local_size ); diff --git a/src/tests/tribol_par_vector.cpp b/src/tests/shared_par_vector.cpp similarity index 83% rename from src/tests/tribol_par_vector.cpp rename to src/tests/shared_par_vector.cpp index 10f8365a..a7c6c4ab 100644 --- a/src/tests/tribol_par_vector.cpp +++ b/src/tests/shared_par_vector.cpp @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: (MIT) -#include "tribol/config.hpp" +#include "shared/config.hpp" #include @@ -13,8 +13,7 @@ #include "mfem.hpp" -#include "tribol/utils/ParVector.hpp" -#include "tribol/utils/ParSparseMat.hpp" +#include "shared/math/ParVector.hpp" class ParVectorTest : public ::testing::Test { protected: @@ -68,7 +67,7 @@ TEST_F( ParVectorTest, Construction ) HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); mfem::HypreParVector* v1 = new mfem::HypreParVector( MPI_COMM_WORLD, size, row_starts_array.GetData() ); HYPRE_SetMemoryLocation( old_hypre_mem_location ); - tribol::ParVector pv1( v1 ); + shared::ParVector pv1( v1 ); EXPECT_EQ( pv1.Size(), local_size ); // 2. From unique_ptr @@ -76,11 +75,11 @@ TEST_F( ParVectorTest, Construction ) HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); auto v2 = std::make_unique( MPI_COMM_WORLD, size, row_starts_array.GetData() ); HYPRE_SetMemoryLocation( old_hypre_mem_location ); - tribol::ParVector pv2( std::move( v2 ) ); + shared::ParVector pv2( std::move( v2 ) ); EXPECT_EQ( pv2.Size(), local_size ); // 3. Template constructor - tribol::ParVector pv3( MPI_COMM_WORLD, size, row_starts_array.GetData() ); + shared::ParVector pv3( MPI_COMM_WORLD, size, row_starts_array.GetData() ); EXPECT_EQ( pv3.Size(), local_size ); } @@ -92,17 +91,17 @@ TEST_F( ParVectorTest, View ) if ( rank == 0 ) std::cout << "Testing View..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); v.Fill( 1.0 ); // Construct View - tribol::ParVectorView view( &v.get() ); + shared::ParVectorView view( &v.get() ); EXPECT_EQ( view.Size(), v.Size() ); EXPECT_NEAR( view.Max(), 1.0, 1e-12 ); // Operate on View - tribol::ParVector v2 = view * 2.0; + shared::ParVector v2 = view * 2.0; EXPECT_NEAR( v2.Max(), 2.0, 1e-12 ); } @@ -114,7 +113,7 @@ TEST_F( ParVectorTest, Accessors ) if ( rank == 0 ) std::cout << "Testing Accessors..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); v.Fill( 0.0 ); if ( v.Size() > 0 ) { v[0] = 5.0; @@ -135,13 +134,13 @@ TEST_F( ParVectorTest, AddSub ) if ( rank == 0 ) std::cout << "Testing Addition and Subtraction..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); - tribol::ParVector v2( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v2( MPI_COMM_WORLD, 10, row_starts.GetData() ); v1.Fill( 2.0 ); v2.Fill( 3.0 ); // v1 + v2 - tribol::ParVector v3 = v1 + v2; + shared::ParVector v3 = v1 + v2; EXPECT_NEAR( v3.Max(), 5.0, 1e-12 ); // v1 += v2 @@ -149,7 +148,7 @@ TEST_F( ParVectorTest, AddSub ) EXPECT_NEAR( v1.Max(), 5.0, 1e-12 ); // v1 - v2 - tribol::ParVector v4 = v1 - v2; + shared::ParVector v4 = v1 - v2; EXPECT_NEAR( v4.Max(), 2.0, 1e-12 ); // v1 -= v2 @@ -165,15 +164,15 @@ TEST_F( ParVectorTest, ScalarMult ) if ( rank == 0 ) std::cout << "Testing Scalar Multiplication..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); v.Fill( 2.0 ); // v * s - tribol::ParVector v2 = v * 3.0; + shared::ParVector v2 = v * 3.0; EXPECT_NEAR( v2.Max(), 6.0, 1e-12 ); // s * v - tribol::ParVector v3 = 4.0 * v; + shared::ParVector v3 = 4.0 * v; EXPECT_NEAR( v3.Max(), 8.0, 1e-12 ); // v *= s @@ -189,13 +188,13 @@ TEST_F( ParVectorTest, ComponentWise ) if ( rank == 0 ) std::cout << "Testing Component-wise operations..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); - tribol::ParVector v2( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v2( MPI_COMM_WORLD, 10, row_starts.GetData() ); v1.Fill( 2.0 ); v2.Fill( 4.0 ); // multiply - tribol::ParVector v3 = v1.multiply( v2 ); + shared::ParVector v3 = v1.multiply( v2 ); EXPECT_NEAR( v3.Max(), 8.0, 1e-12 ); // multiply in-place @@ -203,7 +202,7 @@ TEST_F( ParVectorTest, ComponentWise ) EXPECT_NEAR( v1.Max(), 8.0, 1e-12 ); // divide - tribol::ParVector v4 = v1.divide( v2 ); + shared::ParVector v4 = v1.divide( v2 ); EXPECT_NEAR( v4.Max(), 2.0, 1e-12 ); // divide in-place @@ -219,16 +218,16 @@ TEST_F( ParVectorTest, MoveAndRelease ) if ( rank == 0 ) std::cout << "Testing Move and Release..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); v1.Fill( 7.0 ); // Move constructor - tribol::ParVector v2( std::move( v1 ) ); + shared::ParVector v2( std::move( v1 ) ); EXPECT_NEAR( v2.Max(), 7.0, 1e-12 ); EXPECT_EQ( v1.operator->(), nullptr ); // Move assignment - tribol::ParVector v3( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v3( MPI_COMM_WORLD, 10, row_starts.GetData() ); v3 = std::move( v2 ); EXPECT_NEAR( v3.Max(), 7.0, 1e-12 ); EXPECT_EQ( v2.operator->(), nullptr ); @@ -248,7 +247,7 @@ TEST_F( ParVectorTest, Fill ) if ( rank == 0 ) std::cout << "Testing Fill..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v( MPI_COMM_WORLD, 10, row_starts.GetData() ); v.Fill( 1.0 ); EXPECT_NEAR( v.Max(), 1.0, 1e-12 ); @@ -267,11 +266,11 @@ TEST_F( ParVectorTest, Copy ) if ( rank == 0 ) std::cout << "Testing Copy..." << std::endl; auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); - tribol::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); v1.Fill( 3.0 ); // Copy constructor - tribol::ParVector v2( v1 ); + shared::ParVector v2( v1 ); EXPECT_NEAR( v2.Max(), 3.0, 1e-12 ); // Verify it's a deep copy @@ -280,7 +279,7 @@ TEST_F( ParVectorTest, Copy ) EXPECT_NEAR( v2.Max(), 3.0, 1e-12 ); // Copy assignment - tribol::ParVector v3( MPI_COMM_WORLD, 10, row_starts.GetData() ); + shared::ParVector v3( MPI_COMM_WORLD, 10, row_starts.GetData() ); v3.Fill( 5.0 ); v3 = v2; EXPECT_NEAR( v3.Max(), 3.0, 1e-12 ); diff --git a/src/tribol/CMakeLists.txt b/src/tribol/CMakeLists.txt index 6b753287..aba0db2c 100644 --- a/src/tribol/CMakeLists.txt +++ b/src/tribol/CMakeLists.txt @@ -49,8 +49,6 @@ set(tribol_headers utils/ContactPlaneOutput.hpp utils/DataManager.hpp utils/Math.hpp - utils/ParVector.hpp - utils/ParSparseMat.hpp utils/TestUtils.hpp ) @@ -82,8 +80,6 @@ set(tribol_sources utils/ContactPlaneOutput.cpp utils/Math.cpp - utils/ParVector.cpp - utils/ParSparseMat.cpp utils/TestUtils.cpp ) @@ -122,7 +118,7 @@ blt_add_library( FOLDER tribol ) -# Add separable compilation flag +# Add debug flag if(ENABLE_HIP) target_compile_options(tribol PRIVATE $<$: diff --git a/src/tribol/common/BasicTypes.hpp b/src/tribol/common/BasicTypes.hpp index f8927524..7947a991 100644 --- a/src/tribol/common/BasicTypes.hpp +++ b/src/tribol/common/BasicTypes.hpp @@ -6,103 +6,15 @@ #ifndef SRC_TRIBOL_COMMON_BASICTYPES_HPP_ #define SRC_TRIBOL_COMMON_BASICTYPES_HPP_ -// Tribol config include -#include "tribol/config.hpp" - -// C includes -#include - -// C++ includes -#include - -// MPI includes -#ifdef TRIBOL_USE_MPI -#include -#endif - -// Axom includes -#include "axom/core/Types.hpp" - -// MFEM includes -#include "mfem.hpp" +#include "shared/common/BasicTypes.hpp" namespace tribol { -#ifdef TRIBOL_USE_MPI - -using CommT = MPI_Comm; -#define TRIBOL_COMM_WORLD MPI_COMM_WORLD -#define TRIBOL_COMM_NULL MPI_COMM_NULL - -#else - -using CommT = int; -#define TRIBOL_COMM_WORLD 0 -#define TRIBOL_COMM_NULL -1 - -#endif - -// match index type used in axom (since data is held in axom data structures) -using IndexT = axom::IndexType; - -// size type matching size of addressable memory -using SizeT = size_t; - -#ifdef TRIBOL_USE_SINGLE_PRECISION - -#error "Tribol does not support single precision." -using RealT = float; - -#else - -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 - -// Execution space specifiers -#if defined( TRIBOL_USE_CUDA ) || defined( TRIBOL_USE_HIP ) -#ifndef __device__ -#error "TRIBOL_USE_CUDA or TRIBOL_USE_HIP but __device__ is undefined. Check include files" -#endif -#define TRIBOL_DEVICE __device__ -#define TRIBOL_HOST_DEVICE __host__ __device__ -#else -#define TRIBOL_DEVICE -#define TRIBOL_HOST_DEVICE -#endif - -// Execution space identifier for defaulted constructors and destructors -#ifdef TRIBOL_USE_HIP -#define TRIBOL_DEFAULT_DEVICE __device__ -#define TRIBOL_DEFAULT_HOST_DEVICE __host__ __device__ -#else -#define TRIBOL_DEFAULT_DEVICE -#define TRIBOL_DEFAULT_HOST_DEVICE -#endif - -// Defined when Tribol doesn't have a device available -#if !( defined( TRIBOL_USE_CUDA ) || defined( TRIBOL_USE_HIP ) ) -#define TRIBOL_USE_HOST -#endif - -// Define variable when in device code -#if defined( __CUDA_ARCH__ ) || defined( __HIP_DEVICE_COMPILE__ ) -#define TRIBOL_DEVICE_CODE -#endif - -// Ignore host code in __host__ __device__ code warning on NVCC -#ifdef TRIBOL_USE_CUDA -#define TRIBOL_NVCC_EXEC_CHECK_DISABLE #pragma nv_exec_check_disable -#else -#define TRIBOL_NVCC_EXEC_CHECK_DISABLE -#endif +using CommT = shared::CommT; +using IndexT = shared::IndexT; +using SizeT = shared::SizeT; +using RealT = shared::RealT; } // namespace tribol -#endif /* SRC_TRIBOL_COMMON_BASICTYPES_HPP_ */ +#endif /* SRC_TRIBOL_COMMON_BASICTYPES_HPP_ */ \ No newline at end of file diff --git a/src/tribol/config.hpp.in b/src/tribol/config.hpp.in index 8b5c36e8..c0dcb094 100644 --- a/src/tribol/config.hpp.in +++ b/src/tribol/config.hpp.in @@ -6,6 +6,8 @@ #ifndef SRC_TRIBOL_CONFIG_HPP_ #define SRC_TRIBOL_CONFIG_HPP_ +#include "shared/config.hpp" + /* * Note: Use only C-style comments in this file since it might be included from a C file */ @@ -30,14 +32,6 @@ /* * Build information */ -#cmakedefine TRIBOL_USE_SINGLE_PRECISION -#cmakedefine TRIBOL_USE_MPI -#cmakedefine TRIBOL_USE_UMPIRE -#cmakedefine TRIBOL_USE_RAJA -#cmakedefine TRIBOL_USE_ENZYME -#cmakedefine TRIBOL_USE_CUDA -#cmakedefine TRIBOL_USE_HIP -#cmakedefine TRIBOL_USE_OPENMP #cmakedefine BUILD_REDECOMP #endif /* SRC_TRIBOL_CONFIG_HPP_ */ diff --git a/src/tribol/interface/mfem_tribol.cpp b/src/tribol/interface/mfem_tribol.cpp index 9f29bcd9..a10afc44 100644 --- a/src/tribol/interface/mfem_tribol.cpp +++ b/src/tribol/interface/mfem_tribol.cpp @@ -344,12 +344,12 @@ std::unique_ptr getMfemBlockJacobian( IndexT cs_id ) auto dndx = cs->getMfemJacobianData()->GetMfemBlockJacobian( *cs->getDnDxMethodData(), nonmortar_info, nonmortar_info ); - auto block_00 = ( ParSparseMatView( &static_cast( dfdn->GetBlock( 0, 0 ) ) ) * + auto block_00 = ( shared::ParSparseMatView( &static_cast( dfdn->GetBlock( 0, 0 ) ) ) * &static_cast( dndx->GetBlock( 0, 0 ) ) ) + &static_cast( dfdx->GetBlock( 0, 0 ) ); dfdx->SetBlock( 0, 0, block_00.release() ); - auto block_10 = ( ParSparseMatView( &static_cast( dfdn->GetBlock( 1, 0 ) ) ) * + auto block_10 = ( shared::ParSparseMatView( &static_cast( dfdn->GetBlock( 1, 0 ) ) ) * &static_cast( dndx->GetBlock( 0, 0 ) ) ) + &static_cast( dfdx->GetBlock( 1, 0 ) ); dfdx->SetBlock( 1, 0, block_10.release() ); diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 200f2779..2b0c47dd 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -859,7 +859,7 @@ MfemJacobianData::MfemJacobianData( const MfemMeshData& parent_data, const MfemS mfem::Vector submesh_parent_data( submesh2parent_vdof_list_.Size() ); submesh_parent_data = 1.0; // This constructor copies all of the data, so don't worry about ownership of the CSR data - submesh_parent_vdof_xfer_ = std::make_unique( + submesh_parent_vdof_xfer_ = std::make_unique( TRIBOL_COMM_WORLD, submesh_fes.GetVSize(), submesh_fes.GlobalVSize(), parent_fes.GlobalVSize(), submesh_parent_I.data(), submesh2parent_vdof_list_.GetData(), submesh_parent_data.GetData(), submesh_fes.GetDofOffsets(), parent_fes.GetDofOffsets() ); @@ -1002,26 +1002,24 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( // Pick xfer again for conversion redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); - auto submesh_J_hypre = xfer->ConvertToHypreParMatrix( std::move( *submesh_J.release() ), false ); + auto submesh_J_hypre = xfer->ConvertToParSparseMat( std::move( *submesh_J.release() ), false ); mfem::HypreParMatrix* block_mat = nullptr; if ( r_blk == 0 && c_blk == 0 ) { - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view.RAP( *submesh_parent_vdof_xfer_ ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + auto parent_J = submesh_J_hypre.RAP( *submesh_parent_vdof_xfer_ ); + shared::ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); block_mat = parent_J.RAP( parent_P ).release(); } else if ( r_blk == 0 && c_blk == 1 ) { - auto parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre.get(); - block_mat = ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), parent_J, - submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) + auto parent_J = submesh_parent_vdof_xfer_->transpose() * submesh_J_hypre; + block_mat = shared::ParSparseMat::RAP( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix(), + parent_J, submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ) .release(); } else if ( r_blk == 1 && c_blk == 0 ) { - ParSparseMatView submesh_J_view( submesh_J_hypre.get() ); - auto parent_J = submesh_J_view * ( *submesh_parent_vdof_xfer_ ); - ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); - ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); - block_mat = ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release(); + auto parent_J = submesh_J_hypre * ( *submesh_parent_vdof_xfer_ ); + shared::ParSparseMatView submesh_P( submesh_data_.GetSubmeshFESpace().Dof_TrueDof_Matrix() ); + shared::ParSparseMatView parent_P( parent_data_.GetParentCoords().ParFESpace()->Dof_TrueDof_Matrix() ); + block_mat = shared::ParSparseMat::RAP( submesh_P, parent_J, parent_P ).release(); } block_J->SetBlock( r_blk, c_blk, block_mat ); @@ -1039,9 +1037,9 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( if ( has_11 ) { auto& submesh_fes_full = submesh_data_.GetSubmeshFESpace(); - ParSparseMat inactive_hpm_full = - ParSparseMat::diagonalMatrix( TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), - submesh_fes_full.GetTrueDofOffsets(), 1.0, mortar_tdof_list_, false ); + shared::ParSparseMat inactive_hpm_full = + shared::ParSparseMat::diagonalMatrix( TRIBOL_COMM_WORLD, submesh_fes_full.GlobalTrueVSize(), + submesh_fes_full.GetTrueDofOffsets(), 1.0, mortar_tdof_list_, false ); if ( block_J->IsZeroBlock( 1, 1 ) ) { block_J->SetBlock( 1, 1, inactive_hpm_full.release() ); diff --git a/src/tribol/mesh/MfemData.hpp b/src/tribol/mesh/MfemData.hpp index 17798cce..0f432880 100644 --- a/src/tribol/mesh/MfemData.hpp +++ b/src/tribol/mesh/MfemData.hpp @@ -17,13 +17,13 @@ #include "mfem.hpp" -#include "axom/core.hpp" +#include "shared/math/ParSparseMat.hpp" + #include "redecomp/redecomp.hpp" #include "tribol/common/BasicTypes.hpp" #include "tribol/common/Parameters.hpp" #include "tribol/mesh/MethodCouplingData.hpp" -#include "tribol/utils/ParSparseMat.hpp" namespace tribol { @@ -1731,7 +1731,7 @@ class MfemJacobianData { /** * @brief Submesh to parent transfer operator */ - std::unique_ptr submesh_parent_vdof_xfer_; + std::unique_ptr submesh_parent_vdof_xfer_; /** * @brief List of submesh true dofs that only exist on the mortar surface From d543c278f9ac9362820cb1bbf8f0d5afb959b6ed Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Wed, 11 Feb 2026 12:54:46 -0800 Subject: [PATCH 25/33] clean up host vs device hypreparmatrix creation --- src/shared/CMakeLists.txt | 1 + src/shared/common/ExecModel.hpp | 147 +++++++++++++++++++++++++++++++ src/shared/math/ParSparseMat.cpp | 146 +++++++----------------------- src/shared/math/ParSparseMat.hpp | 78 +++++++++++++--- src/tribol/common/ExecModel.hpp | 134 ++-------------------------- 5 files changed, 255 insertions(+), 251 deletions(-) create mode 100644 src/shared/common/ExecModel.hpp diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index 539ba745..89dd1066 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -7,6 +7,7 @@ set(shared_headers common/BasicTypes.hpp + common/ExecModel.hpp infrastructure/Profiling.hpp math/ParSparseMat.hpp math/ParVector.hpp diff --git a/src/shared/common/ExecModel.hpp b/src/shared/common/ExecModel.hpp new file mode 100644 index 00000000..3bee57d3 --- /dev/null +++ b/src/shared/common/ExecModel.hpp @@ -0,0 +1,147 @@ +// 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) + +#ifndef SRC_SHARED_COMMON_EXECMODEL_HPP_ +#define SRC_SHARED_COMMON_EXECMODEL_HPP_ + +// Shared includes +#include "shared/common/BasicTypes.hpp" + +// Axom includes +#include "axom/core/memory_management.hpp" +#include "axom/slic.hpp" + +namespace shared { + +/** + * @brief A MemorySpace ties a resource to an associated pointer + */ +enum class MemorySpace +{ + // Dynamic can be used for pointers whose resource is defined not using this + // enum. + Dynamic, + Host, +#ifdef TRIBOL_USE_UMPIRE + Device, + Unified +#endif +}; + +/** + * @brief An ExecutionMode defines what resource should be used to do loop + * computations + */ +enum class ExecutionMode +{ + Sequential, +#ifdef TRIBOL_USE_OPENMP + OpenMP, +#endif +#ifdef TRIBOL_USE_CUDA + Cuda, +#endif +#ifdef TRIBOL_USE_HIP + Hip, +#endif + // Dynamic is used to determine the ExecutionMode on the fly. + Dynamic +}; + +/** + * @brief SFINAE struct to deduce axom memory space from a Tribol memory space + * at compile time + * + * @tparam MSPACE + */ +template +struct toAxomMemorySpace { + static constexpr axom::MemorySpace value = axom::MemorySpace::Dynamic; +}; + +#ifdef TRIBOL_USE_UMPIRE + +template <> +struct toAxomMemorySpace { + static constexpr axom::MemorySpace value = axom::MemorySpace::Host; +}; + +template <> +struct toAxomMemorySpace { + static constexpr axom::MemorySpace value = axom::MemorySpace::Device; +}; + +template <> +struct toAxomMemorySpace { + static constexpr axom::MemorySpace value = axom::MemorySpace::Unified; +}; + +#endif + +// Waiting for C++ 17... +// template +// inline constexpr axom::MemorySpace axomMemorySpaceV = toAxomMemorySpace::value; + +#ifdef TRIBOL_USE_UMPIRE + +inline umpire::resource::MemoryResourceType toUmpireMemoryType( MemorySpace mem_space ) +{ + switch ( mem_space ) { + case MemorySpace::Host: + return umpire::resource::MemoryResourceType::Host; + case MemorySpace::Device: + return umpire::resource::MemoryResourceType::Device; + case MemorySpace::Unified: + return umpire::resource::MemoryResourceType::Unified; + default: + return umpire::resource::MemoryResourceType::Unknown; + } +}; + +#endif + +inline int getDefaultAllocatorID() { return axom::getDefaultAllocatorID(); } + +inline int getResourceAllocatorID( MemorySpace mem_space ) +{ + int allocator_id = axom::getDefaultAllocatorID(); +#ifdef TRIBOL_USE_UMPIRE + if ( mem_space != MemorySpace::Dynamic ) { + allocator_id = axom::getUmpireResourceAllocatorID( toUmpireMemoryType( mem_space ) ); + } +#else + TRIBOL_UNUSED_VAR( mem_space ); +#endif + return allocator_id; +} + +inline bool isOnDevice( ExecutionMode exec ) +{ + switch ( exec ) { +#if defined( TRIBOL_USE_CUDA ) + case ExecutionMode::Cuda: + return true; +#elif defined( TRIBOL_USE_HIP ) + case ExecutionMode::Hip: + return true; +#endif +#ifdef TRIBOL_USE_OPENMP + case ExecutionMode::OpenMP: + return false; +#endif + case ExecutionMode::Dynamic: + SLIC_ERROR_ROOT( "Dynamic execution mode does not define a memory space location." ); + return false; + case ExecutionMode::Sequential: + return false; + default: + SLIC_ERROR_ROOT( "Unknown execution mode." ); + return false; + } +} + +} // namespace shared + +#endif /* SRC_SHARED_COMMON_EXECMODEL_HPP_ */ diff --git a/src/shared/math/ParSparseMat.cpp b/src/shared/math/ParSparseMat.cpp index 0fd88ad8..dddaf248 100644 --- a/src/shared/math/ParSparseMat.cpp +++ b/src/shared/math/ParSparseMat.cpp @@ -5,7 +5,6 @@ #include "shared/math/ParSparseMat.hpp" -#include #include <_hypre_parcsr_mv.h> #include "axom/slic.hpp" @@ -32,113 +31,61 @@ ParSparseMat ParSparseMatView::operator*( double s ) const { return add( s, *thi ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs ) { - // Prevent mfem::ParMult from creating device arrays - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - mfem::HypreParMatrix* result = mfem::ParMult( lhs.mat_, rhs.mat_, true ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - result->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); - return ParSparseMat( result ); + return ParSparseMat( ParSparseMatView::createHypreParMatrix( + [&]() { return mfem::ParMult( lhs.mat_, rhs.mat_, true ); } ) ); } ParVector ParSparseMatView::operator*( const ParVectorView& x ) const { ParVector y( *mat_ ); - // Prevent HypreParMatrix::Mult from changing memory from host to device - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - mat_->Mult( const_cast( x.get() ), y.get() ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); + invokeHypreMethod( + [&]() { mat_->Mult( const_cast( x.get() ), y.get() ); } ); return y; } ParSparseMat ParSparseMatView::transpose() const { - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - ParSparseMat mat_transpose( mat_->Transpose() ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - mat_transpose->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); - return mat_transpose; + return ParSparseMat( createHypreParMatrix( [&]() { return mat_->Transpose(); } ) ); } ParSparseMat ParSparseMatView::square() const { return *this * *this; } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& P ) const { - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - mat_->HostRead(); - P->HostRead(); - ParSparseMat rap( mfem::RAP( mat_, P.mat_ ) ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - rap->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); - return rap; + return ParSparseMat( createHypreParMatrix( [&]() { + mat_->HostRead(); + P->HostRead(); + return mfem::RAP( mat_, P.mat_ ); + } ) ); } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& A, const ParSparseMatView& P ) { - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - A->HostRead(); - P->HostRead(); - ParSparseMat rap( mfem::RAP( A.mat_, P.mat_ ) ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - rap->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); - return rap; + return ParSparseMat( createHypreParMatrix( [&]() { + A->HostRead(); + P->HostRead(); + return mfem::RAP( A.mat_, P.mat_ ); + } ) ); } ParSparseMat ParSparseMatView::RAP( const ParSparseMatView& Rt, const ParSparseMatView& A, const ParSparseMatView& P ) { - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - Rt->HostRead(); - A->HostRead(); - P->HostRead(); - ParSparseMat rap( mfem::RAP( Rt.mat_, A.mat_, P.mat_ ) ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - rap->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); - return rap; + return ParSparseMat( createHypreParMatrix( [&]() { + Rt->HostRead(); + A->HostRead(); + P->HostRead(); + return mfem::RAP( Rt.mat_, A.mat_, P.mat_ ); + } ) ); } void ParSparseMatView::EliminateRows( const mfem::Array& rows ) { - // Prevent HypreParMatrix::EliminateRows from changing memory from host to device - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - mat_->EliminateRows( rows ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); + invokeHypreMethod( [&]() { mat_->EliminateRows( rows ); } ); } ParSparseMat ParSparseMatView::EliminateCols( const mfem::Array& cols ) { - // Prevent HypreParMatrix::EliminateCols from changing memory from host to device - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - ParSparseMat elim_cols_mat( mat_->EliminateCols( cols ) ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - elim_cols_mat->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); - return elim_cols_mat; + return ParSparseMat( createHypreParMatrix( [&]() { return mat_->EliminateCols( cols ); } ) ); } ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s; } @@ -146,27 +93,15 @@ ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ) { ParVector y( *mat.mat_, 1 ); - // Prevent HypreParMatrix::MultTranspose from changing memory from host to device - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - mat.mat_->MultTranspose( const_cast( x.get() ), y.get() ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); + ParSparseMatView::invokeHypreMethod( + [&]() { mat.mat_->MultTranspose( const_cast( x.get() ), y.get() ); } ); return y; } ParSparseMat ParSparseMatView::add( RealT alpha, const ParSparseMatView& A, RealT beta, const ParSparseMatView& B ) { - // Prevent HypreParMatrix::Add from returning data on device - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - mfem::HypreParMatrix* result = mfem::Add( alpha, A.get(), beta, B.get() ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - result->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - HYPRE_SetMemoryLocation( old_hypre_mem_location ); - return ParSparseMat( result ); + return ParSparseMat( + createHypreParMatrix( [&]() { return mfem::Add( alpha, A.get(), beta, B.get() ); } ) ); } // ParSparseMat implementations @@ -181,21 +116,12 @@ ParSparseMat::ParSparseMat( std::unique_ptr mat ) ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* row_starts, mfem::SparseMatrix&& diag ) : ParSparseMatView( nullptr ) { - // ParSparseMat works with host data for now. Make sure CSR data is copied on host in the constructor. - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - owned_mat_ = std::make_unique( comm, glob_size, row_starts, &diag ); + owned_mat_.reset( createHypreParMatrix( + [&]() { return new mfem::HypreParMatrix( comm, glob_size, row_starts, &diag ); } ) ); mat_ = owned_mat_.get(); diag.GetMemoryI().ClearOwnerFlags(); diag.GetMemoryJ().ClearOwnerFlags(); diag.GetMemoryData().ClearOwnerFlags(); - constexpr auto mfem_owned_host_arrays = 3; - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - owned_mat_->SetOwnerFlags( mfem_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - // Return hypre's memory location to what it was before - HYPRE_SetMemoryLocation( old_hypre_mem_location ); } ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept @@ -304,19 +230,11 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si // copy row_starts to a new array mfem::Array row_starts_copy = row_starts; - // ParSparseMat is host only for now - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - auto diag_hpm = std::make_unique( comm, global_size, global_size, row_starts_copy.GetData(), - row_starts_copy.GetData(), diag_i, diag_j, diag_data, offd_i, - offd_j, offd_data, 0, offd_col_map, true ); - // Return hypre's memory location to what it was before - HYPRE_SetMemoryLocation( old_hypre_mem_location ); + auto diag_hpm = std::unique_ptr( createHypreParMatrix( + comm, global_size, global_size, row_starts_copy.GetData(), row_starts_copy.GetData(), diag_i, diag_j, diag_data, + offd_i, offd_j, offd_data, 0, offd_col_map, true ) ); diag_hpm->CopyRowStarts(); diag_hpm->CopyColStarts(); - constexpr auto hypre_owned_host_arrays = -1; - diag_hpm->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); return ParSparseMat( std::move( diag_hpm ) ); } diff --git a/src/shared/math/ParSparseMat.hpp b/src/shared/math/ParSparseMat.hpp index dbe225aa..93a59f68 100644 --- a/src/shared/math/ParSparseMat.hpp +++ b/src/shared/math/ParSparseMat.hpp @@ -13,8 +13,13 @@ #include "mfem.hpp" #include "shared/common/BasicTypes.hpp" +#include "shared/common/ExecModel.hpp" #include "shared/math/ParVector.hpp" +#ifdef TRIBOL_USE_MPI +#include +#endif + namespace shared { #ifdef TRIBOL_USE_MPI @@ -145,6 +150,65 @@ class ParSparseMatView { protected: static ParSparseMat add( RealT alpha, const ParSparseMatView& A, RealT beta, const ParSparseMatView& B ); + /** + * @brief Helper to invoke a Hypre method with a specific memory location + */ + template + static auto invokeHypreMethod( F&& f ) + { + HYPRE_MemoryLocation old_hypre_mem_location; + HYPRE_GetMemoryLocation( &old_hypre_mem_location ); + + if constexpr ( MSPACE == MemorySpace::Host ) { + HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); + } + + if constexpr ( std::is_same_v ) { + f(); + HYPRE_SetMemoryLocation( old_hypre_mem_location ); + } else { + auto result = f(); + if constexpr ( std::is_same_v ) { + if ( result ) { + if constexpr ( MSPACE == MemorySpace::Host ) { + constexpr int hypre_owned_host_arrays = -1; + result->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); + } + } + } + HYPRE_SetMemoryLocation( old_hypre_mem_location ); + return result; + } + } + + /** + * @brief Creates a mfem::HypreParMatrix with a specific memory location and sets owner flags + * + * @tparam MSPACE Memory space to use + * @tparam F Lambda type + * @param f Lambda that returns a mfem::HypreParMatrix* + * @return mfem::HypreParMatrix* The created matrix + */ + template , int> = 0> + static auto createHypreParMatrix( F&& f ) + { + return invokeHypreMethod( std::forward( f ) ); + } + + /** + * @brief Creates a mfem::HypreParMatrix with a specific memory location and sets owner flags + * + * @tparam MSPACE Memory space to use + * @tparam Args Constructor argument types + * @param args Constructor arguments + * @return mfem::HypreParMatrix* The created matrix + */ + template + static mfem::HypreParMatrix* createHypreParMatrix( Args&&... args ) + { + return invokeHypreMethod( [&]() { return new mfem::HypreParMatrix( std::forward( args )... ); } ); + } + mfem::HypreParMatrix* mat_; }; @@ -184,18 +248,10 @@ class ParSparseMat : public ParSparseMatView { /// Template constructor forwarding arguments to mfem::HypreParMatrix constructor template - explicit ParSparseMat( Args&&... args ) : ParSparseMatView( nullptr ), owned_mat_( nullptr ) + explicit ParSparseMat( Args&&... args ) + : ParSparseMatView( nullptr ), + owned_mat_( createHypreParMatrix( std::forward( args )... ) ) { - // ParSparseMat is host-only for now. - HYPRE_MemoryLocation old_hypre_mem_location; - HYPRE_GetMemoryLocation( &old_hypre_mem_location ); - HYPRE_SetMemoryLocation( HYPRE_MEMORY_HOST ); - owned_mat_ = std::make_unique( std::forward( args )... ); - // This is needed so the destructor doesn't think the hypre data is device data - constexpr auto hypre_owned_host_arrays = -1; - owned_mat_->SetOwnerFlags( hypre_owned_host_arrays, hypre_owned_host_arrays, hypre_owned_host_arrays ); - // Return hypre's memory location to what it was before - HYPRE_SetMemoryLocation( old_hypre_mem_location ); mat_ = owned_mat_.get(); } diff --git a/src/tribol/common/ExecModel.hpp b/src/tribol/common/ExecModel.hpp index ef4ebed2..16f3f855 100644 --- a/src/tribol/common/ExecModel.hpp +++ b/src/tribol/common/ExecModel.hpp @@ -6,141 +6,23 @@ #ifndef SRC_TRIBOL_COMMON_EXECMODEL_HPP_ #define SRC_TRIBOL_COMMON_EXECMODEL_HPP_ -// Tribol includes -#include "tribol/common/BasicTypes.hpp" - -// Axom includes -#include "axom/core/memory_management.hpp" -#include "axom/slic.hpp" +#include "shared/common/ExecModel.hpp" namespace tribol { -/** - * @brief A MemorySpace ties a resource to an associated pointer - */ -enum class MemorySpace -{ - // Dynamic can be used for pointers whose resource is defined not using this - // enum. - Dynamic, - Host, -#ifdef TRIBOL_USE_UMPIRE - Device, - Unified -#endif -}; - -/** - * @brief An ExecutionMode defines what resource should be used to do loop - * computations - */ -enum class ExecutionMode -{ - Sequential, -#ifdef TRIBOL_USE_OPENMP - OpenMP, -#endif -#ifdef TRIBOL_USE_CUDA - Cuda, -#endif -#ifdef TRIBOL_USE_HIP - Hip, -#endif - // Dynamic is used to determine the ExecutionMode on the fly. - Dynamic -}; +using MemorySpace = shared::MemorySpace; +using ExecutionMode = shared::ExecutionMode; -/** - * @brief SFINAE struct to deduce axom memory space from a Tribol memory space - * at compile time - * - * @tparam MSPACE - */ template -struct toAxomMemorySpace { - static constexpr axom::MemorySpace value = axom::MemorySpace::Dynamic; -}; +using toAxomMemorySpace = shared::toAxomMemorySpace; #ifdef TRIBOL_USE_UMPIRE - -template <> -struct toAxomMemorySpace { - static constexpr axom::MemorySpace value = axom::MemorySpace::Host; -}; - -template <> -struct toAxomMemorySpace { - static constexpr axom::MemorySpace value = axom::MemorySpace::Device; -}; - -template <> -struct toAxomMemorySpace { - static constexpr axom::MemorySpace value = axom::MemorySpace::Unified; -}; - +using shared::toUmpireMemoryType; #endif -// Waiting for C++ 17... -// template -// inline constexpr axom::MemorySpace axomMemorySpaceV = toAxomMemorySpace::value; - -#ifdef TRIBOL_USE_UMPIRE - -inline umpire::resource::MemoryResourceType toUmpireMemoryType( MemorySpace mem_space ) -{ - switch ( mem_space ) { - case MemorySpace::Host: - return umpire::resource::MemoryResourceType::Host; - case MemorySpace::Device: - return umpire::resource::MemoryResourceType::Device; - case MemorySpace::Unified: - return umpire::resource::MemoryResourceType::Unified; - default: - return umpire::resource::MemoryResourceType::Unknown; - } -}; - -#endif - -inline int getDefaultAllocatorID() { return axom::getDefaultAllocatorID(); } - -inline int getResourceAllocatorID( MemorySpace mem_space ) -{ - int allocator_id = axom::getDefaultAllocatorID(); -#ifdef TRIBOL_USE_UMPIRE - if ( mem_space != MemorySpace::Dynamic ) { - allocator_id = axom::getUmpireResourceAllocatorID( toUmpireMemoryType( mem_space ) ); - } -#else - TRIBOL_UNUSED_VAR( mem_space ); -#endif - return allocator_id; -} - -inline bool isOnDevice( ExecutionMode exec ) -{ - switch ( exec ) { -#if defined( TRIBOL_USE_CUDA ) - case ExecutionMode::Cuda: - return true; -#elif defined( TRIBOL_USE_HIP ) - case ExecutionMode::Hip: - return true; -#endif -#ifdef TRIBOL_USE_OPENMP - case ExecutionMode::OpenMP: - return false; -#endif - case ExecutionMode::Dynamic: - SLIC_ERROR_ROOT( "Dynamic execution mode does not define a memory space location." ); - return false; - case ExecutionMode::Sequential: - return false; - default: - SLIC_ERROR_ROOT( "Unknown execution mode." ); - return false; - } -} +using shared::getDefaultAllocatorID; +using shared::getResourceAllocatorID; +using shared::isOnDevice; } // namespace tribol From 8665840247f3a74bc706d3af819c2845ddbb52ea Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Wed, 11 Feb 2026 15:57:50 -0800 Subject: [PATCH 26/33] add dot and division tolerance --- src/shared/math/ParVector.cpp | 18 ++++++++++++++---- src/shared/math/ParVector.hpp | 13 +++++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/shared/math/ParVector.cpp b/src/shared/math/ParVector.cpp index 2fab479c..ff309549 100644 --- a/src/shared/math/ParVector.cpp +++ b/src/shared/math/ParVector.cpp @@ -108,6 +108,12 @@ ParVector& ParVector::operator*=( double s ) return *this; } +mfem::real_t ParVectorView::dot( const ParVectorView& other ) const +{ + SLIC_ASSERT( vec_->Size() == other.get().Size() ); + return mfem::InnerProduct( vec_, other.vec_ ); +} + ParVector ParVectorView::multiply( const ParVectorView& other ) const { ParVector result( *vec_ ); @@ -115,10 +121,10 @@ ParVector ParVectorView::multiply( const ParVectorView& other ) const return result; } -ParVector ParVectorView::divide( const ParVectorView& other ) const +ParVector ParVectorView::divide( const ParVectorView& other, mfem::real_t tol ) const { ParVector result( *vec_ ); - result.divideInPlace( other ); + result.divideInPlace( other, tol ); return result; } @@ -135,7 +141,7 @@ ParVector& ParVector::multiplyInPlace( const ParVectorView& other ) return *this; } -ParVector& ParVector::divideInPlace( const ParVectorView& other ) +ParVector& ParVector::divideInPlace( const ParVectorView& other, mfem::real_t tol ) { SLIC_ASSERT( vec_->Size() == other.get().Size() ); int n = vec_->Size(); @@ -143,7 +149,11 @@ ParVector& ParVector::divideInPlace( const ParVectorView& other ) bool use_device = vec_->UseDevice() || other.get().UseDevice(); auto d_vec = vec_->ReadWrite( use_device ); auto d_other = other.get().Read( use_device ); - mfem::forall_switch( use_device, n, [=] TRIBOL_HOST_DEVICE( int i ) { d_vec[i] /= d_other[i]; } ); + mfem::forall_switch( use_device, n, [=] TRIBOL_HOST_DEVICE( int i ) { + if ( std::abs( d_other[i] ) > tol ) { + d_vec[i] /= d_other[i]; + } + } ); } return *this; } diff --git a/src/shared/math/ParVector.hpp b/src/shared/math/ParVector.hpp index d9d1f973..ae757f23 100644 --- a/src/shared/math/ParVector.hpp +++ b/src/shared/math/ParVector.hpp @@ -88,6 +88,11 @@ class ParVectorView { */ mfem::real_t Min() const { return vec_->Min(); } + /** + * @brief Returns the dot product with another vector + */ + mfem::real_t dot( const ParVectorView& other ) const; + /** * @brief Component-wise multiplication: returns z[i] = x[i] * y[i] */ @@ -95,8 +100,10 @@ class ParVectorView { /** * @brief Component-wise division: returns z[i] = x[i] / y[i] + * + * @param tol Sets a tolerance to prevent division by zero */ - ParVector divide( const ParVectorView& other ) const; + ParVector divide( const ParVectorView& other, mfem::real_t tol = 1.0e-14 ) const; /** * @brief Vector addition: returns x + y @@ -201,8 +208,10 @@ class ParVector : public ParVectorView { /** * @brief Component-wise in-place division: x[i] /= y[i] + * + * @param tol Sets a tolerance to prevent division by zero ); */ - ParVector& divideInPlace( const ParVectorView& other ); + ParVector& divideInPlace( const ParVectorView& other, mfem::real_t tol = 1.0e-14 ); private: std::unique_ptr owned_vec_; From cf3ad7452568add63042fcaae977a6057e73e3bb Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Wed, 11 Feb 2026 16:24:27 -0800 Subject: [PATCH 27/33] add inverse method and test --- src/shared/math/ParVector.cpp | 22 ++++++++++++++++++++++ src/shared/math/ParVector.hpp | 14 ++++++++++++++ src/tests/shared_par_vector.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/src/shared/math/ParVector.cpp b/src/shared/math/ParVector.cpp index ff309549..f33eb6c1 100644 --- a/src/shared/math/ParVector.cpp +++ b/src/shared/math/ParVector.cpp @@ -128,6 +128,13 @@ ParVector ParVectorView::divide( const ParVectorView& other, mfem::real_t tol ) return result; } +ParVector ParVectorView::inverse( mfem::real_t tol ) const +{ + ParVector result( *vec_ ); + result.inverseInPlace( tol ); + return result; +} + ParVector& ParVector::multiplyInPlace( const ParVectorView& other ) { SLIC_ASSERT( vec_->Size() == other.get().Size() ); @@ -158,6 +165,21 @@ ParVector& ParVector::divideInPlace( const ParVectorView& other, mfem::real_t to return *this; } +ParVector& ParVector::inverseInPlace( mfem::real_t tol ) +{ + int n = vec_->Size(); + if ( n > 0 ) { + bool use_device = vec_->UseDevice(); + auto d_vec = vec_->ReadWrite( use_device ); + mfem::forall_switch( use_device, n, [=] TRIBOL_HOST_DEVICE( int i ) { + if ( std::abs( d_vec[i] ) > tol ) { + d_vec[i] = 1.0 / d_vec[i]; + } + } ); + } + return *this; +} + #endif // #ifdef TRIBOL_USE_MPI } // namespace shared diff --git a/src/shared/math/ParVector.hpp b/src/shared/math/ParVector.hpp index ae757f23..c68eec64 100644 --- a/src/shared/math/ParVector.hpp +++ b/src/shared/math/ParVector.hpp @@ -105,6 +105,13 @@ class ParVectorView { */ ParVector divide( const ParVectorView& other, mfem::real_t tol = 1.0e-14 ) const; + /** + * @brief Component-wise inverse: returns z[i] = 1.0 / x[i] + * + * @param tol Sets a tolerance to prevent division by zero + */ + ParVector inverse( mfem::real_t tol = 1.0e-14 ) const; + /** * @brief Vector addition: returns x + y */ @@ -213,6 +220,13 @@ class ParVector : public ParVectorView { */ ParVector& divideInPlace( const ParVectorView& other, mfem::real_t tol = 1.0e-14 ); + /** + * @brief Component-wise in-place inverse: x[i] = 1.0 / x[i] + * + * @param tol Sets a tolerance to prevent division by zero + */ + ParVector& inverseInPlace( mfem::real_t tol = 1.0e-14 ); + private: std::unique_ptr owned_vec_; }; diff --git a/src/tests/shared_par_vector.cpp b/src/tests/shared_par_vector.cpp index a7c6c4ab..490a96b1 100644 --- a/src/tests/shared_par_vector.cpp +++ b/src/tests/shared_par_vector.cpp @@ -210,6 +210,34 @@ TEST_F( ParVectorTest, ComponentWise ) EXPECT_NEAR( v1.Max(), 2.0, 1e-12 ); } +// Test Inverse +TEST_F( ParVectorTest, Inverse ) +{ + int rank; + MPI_Comm_rank( MPI_COMM_WORLD, &rank ); + if ( rank == 0 ) std::cout << "Testing Inverse..." << std::endl; + + auto row_starts = GetRowStarts( MPI_COMM_WORLD, 10 ); + shared::ParVector v1( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v1.Fill( 2.0 ); + + // inverse + shared::ParVector v2 = v1.inverse(); + EXPECT_NEAR( v2.Max(), 0.5, 1e-12 ); + EXPECT_NEAR( v2.Min(), 0.5, 1e-12 ); + + // inverse in-place + v1.inverseInPlace(); + EXPECT_NEAR( v1.Max(), 0.5, 1e-12 ); + EXPECT_NEAR( v1.Min(), 0.5, 1e-12 ); + + // Test with zero and tolerance + shared::ParVector v3( MPI_COMM_WORLD, 10, row_starts.GetData() ); + v3.Fill( 0.0 ); + v3.inverseInPlace( 1e-14 ); + EXPECT_NEAR( v3.Max(), 0.0, 1e-12 ); +} + // Test Move and Release TEST_F( ParVectorTest, MoveAndRelease ) { From d27118a5de4a2ffefeb14166b4531f2d67eff621 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Wed, 11 Feb 2026 18:14:37 -0800 Subject: [PATCH 28/33] fix new vs free issues --- src/shared/math/ParSparseMat.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/shared/math/ParSparseMat.cpp b/src/shared/math/ParSparseMat.cpp index dddaf248..1cbdff9b 100644 --- a/src/shared/math/ParSparseMat.cpp +++ b/src/shared/math/ParSparseMat.cpp @@ -122,6 +122,10 @@ ParSparseMat::ParSparseMat( MPI_Comm comm, HYPRE_BigInt glob_size, HYPRE_BigInt* diag.GetMemoryI().ClearOwnerFlags(); diag.GetMemoryJ().ClearOwnerFlags(); diag.GetMemoryData().ClearOwnerFlags(); + // The mfem::Memory in mfem::SparseMatrix allocates using operator new [], so mark the diag memory as owned by MFEM so + // it can be deleted correctly + constexpr int mfem_owned_host_flag = 3; + owned_mat_->SetOwnerFlags( mfem_owned_host_flag, owned_mat_->OwnsOffd(), owned_mat_->OwnsColMap() ); } ParSparseMat::ParSparseMat( ParSparseMat&& other ) noexcept @@ -235,6 +239,9 @@ ParSparseMat ParSparseMat::diagonalMatrix( MPI_Comm comm, HYPRE_BigInt global_si offd_i, offd_j, offd_data, 0, offd_col_map, true ) ); diag_hpm->CopyRowStarts(); diag_hpm->CopyColStarts(); + // We allocated memory using operator new [], so mark all memory as owned by MFEM so it can be deleted correctly + constexpr int mfem_owned_host_flag = 3; + diag_hpm->SetOwnerFlags( mfem_owned_host_flag, mfem_owned_host_flag, mfem_owned_host_flag ); return ParSparseMat( std::move( diag_hpm ) ); } From 7de4b00e0d1fdf262cf458aa36e450d077cd279e Mon Sep 17 00:00:00 2001 From: EB Chin Date: Thu, 12 Feb 2026 01:10:55 -0800 Subject: [PATCH 29/33] bugfixes --- src/redecomp/transfer/MatrixTransfer.cpp | 3 --- src/shared/math/ParSparseMat.cpp | 4 ++-- src/tests/tribol_enforcement_options.cpp | 24 ++++++++++++------------ src/tribol/mesh/MfemData.cpp | 2 +- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/redecomp/transfer/MatrixTransfer.cpp b/src/redecomp/transfer/MatrixTransfer.cpp index 3e764392..e9a05c2d 100644 --- a/src/redecomp/transfer/MatrixTransfer.cpp +++ b/src/redecomp/transfer/MatrixTransfer.cpp @@ -165,9 +165,6 @@ shared::ParSparseMat MatrixTransfer::ConvertToParSparseMat( mfem::SparseMatrix&& shared::ParSparseMat J_full( getMPIUtility().MPIComm(), parent_test_fes_.GetVSize(), parent_test_fes_.GlobalVSize(), parent_trial_fes_.GlobalVSize(), sparse.GetI(), J_bigint.GetData(), sparse.GetData(), parent_test_fes_.GetDofOffsets(), parent_trial_fes_.GetDofOffsets() ); - sparse.GetMemoryI().ClearOwnerFlags(); - sparse.GetMemoryJ().ClearOwnerFlags(); - sparse.GetMemoryData().ClearOwnerFlags(); if ( !parallel_assemble ) { return J_full; } else { diff --git a/src/shared/math/ParSparseMat.cpp b/src/shared/math/ParSparseMat.cpp index 1cbdff9b..aa0655f7 100644 --- a/src/shared/math/ParSparseMat.cpp +++ b/src/shared/math/ParSparseMat.cpp @@ -37,7 +37,7 @@ ParSparseMat operator*( const ParSparseMatView& lhs, const ParSparseMatView& rhs ParVector ParSparseMatView::operator*( const ParVectorView& x ) const { - ParVector y( *mat_ ); + ParVector y( *mat_, 1 ); invokeHypreMethod( [&]() { mat_->Mult( const_cast( x.get() ), y.get() ); } ); return y; @@ -92,7 +92,7 @@ ParSparseMat operator*( double s, const ParSparseMatView& mat ) { return mat * s ParVector operator*( const ParVectorView& x, const ParSparseMatView& mat ) { - ParVector y( *mat.mat_, 1 ); + ParVector y( *mat.mat_, 0 ); ParSparseMatView::invokeHypreMethod( [&]() { mat.mat_->MultTranspose( const_cast( x.get() ), y.get() ); } ); return y; diff --git a/src/tests/tribol_enforcement_options.cpp b/src/tests/tribol_enforcement_options.cpp index cc2b8e80..12113bc3 100644 --- a/src/tests/tribol_enforcement_options.cpp +++ b/src/tests/tribol_enforcement_options.cpp @@ -195,10 +195,10 @@ TEST_F( EnforcementOptionsTest, penalty_kinematic_element_error ) tribol::finalize(); - delete bulk_modulus_1; - delete bulk_modulus_2; - delete element_thickness_1; - delete element_thickness_2; + delete[] bulk_modulus_1; + delete[] bulk_modulus_2; + delete[] element_thickness_1; + delete[] element_thickness_2; delete mesh; } @@ -355,10 +355,10 @@ TEST_F( EnforcementOptionsTest, penalty_kinematic_element_pass ) tribol::finalize(); - delete bulk_modulus_1; - delete bulk_modulus_2; - delete element_thickness_1; - delete element_thickness_2; + delete[] bulk_modulus_1; + delete[] bulk_modulus_2; + delete[] element_thickness_1; + delete[] element_thickness_2; delete mesh; } @@ -394,10 +394,10 @@ TEST_F( EnforcementOptionsTest, penalty_kinematic_element_invalid_element_input tribol::finalize(); - delete bulk_modulus_1; - delete bulk_modulus_2; - delete element_thickness_1; - delete element_thickness_2; + delete[] bulk_modulus_1; + delete[] bulk_modulus_2; + delete[] element_thickness_1; + delete[] element_thickness_2; delete mesh; } diff --git a/src/tribol/mesh/MfemData.cpp b/src/tribol/mesh/MfemData.cpp index 2b0c47dd..36a2775e 100644 --- a/src/tribol/mesh/MfemData.cpp +++ b/src/tribol/mesh/MfemData.cpp @@ -1002,7 +1002,7 @@ std::unique_ptr MfemJacobianData::GetMfemBlockJacobian( // Pick xfer again for conversion redecomp::MatrixTransfer* xfer = GetUpdateData().submesh_redecomp_xfer_( r_blk, c_blk ).get(); - auto submesh_J_hypre = xfer->ConvertToParSparseMat( std::move( *submesh_J.release() ), false ); + auto submesh_J_hypre = xfer->ConvertToParSparseMat( std::move( *submesh_J ), false ); mfem::HypreParMatrix* block_mat = nullptr; From 8bf734cf7a53825b6127f4d59de058cc4bc62a60 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Thu, 12 Feb 2026 17:50:25 -0800 Subject: [PATCH 30/33] change LoggingLevel enum to lowercase --- src/tribol/common/Parameters.hpp | 12 ++++++------ src/tribol/interface/tribol.cpp | 4 ++-- src/tribol/mesh/CouplingScheme.cpp | 12 ++++++------ src/tribol/utils/TestUtils.cpp | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/tribol/common/Parameters.hpp b/src/tribol/common/Parameters.hpp index c944a135..d5dd2e6b 100644 --- a/src/tribol/common/Parameters.hpp +++ b/src/tribol/common/Parameters.hpp @@ -32,12 +32,12 @@ constexpr int ANY_MESH = -1; */ enum class LoggingLevel { - UNDEFINED, ///! Undefined - DEBUG, ///! Debug and higher - INFO, ///! Info and higher - WARNING, ///! Warning and higher - ERROR, ///! Errors only - NUM_LOGGING_LEVELS = ERROR + Undefined, ///! Undefined + Debug, ///! Debug and higher + Info, ///! Info and higher + Warning, ///! Warning and higher + Error, ///! Errors only + NumLoggingLevels = Error }; /*! diff --git a/src/tribol/interface/tribol.cpp b/src/tribol/interface/tribol.cpp index f31f1f69..bad3cbe3 100644 --- a/src/tribol/interface/tribol.cpp +++ b/src/tribol/interface/tribol.cpp @@ -310,9 +310,9 @@ void setLoggingLevel( IndexT cs_id, LoggingLevel log_level ) SLIC_ERROR_IF( !cs, "tribol::setLoggingLevel(): " << "invalid CouplingScheme id." ); - if ( !in_range( static_cast( log_level ), static_cast( LoggingLevel::NUM_LOGGING_LEVELS ) ) ) { + if ( !in_range( static_cast( log_level ), static_cast( LoggingLevel::NumLoggingLevels ) ) ) { SLIC_INFO_ROOT( "tribol::setLoggingLevel(): Logging level not an option; " << "using 'warning' level." ); - cs->setLoggingLevel( LoggingLevel::WARNING ); + cs->setLoggingLevel( LoggingLevel::Warning ); } else { cs->setLoggingLevel( log_level ); } diff --git a/src/tribol/mesh/CouplingScheme.cpp b/src/tribol/mesh/CouplingScheme.cpp index c7c3bdce..c98a0d22 100644 --- a/src/tribol/mesh/CouplingScheme.cpp +++ b/src/tribol/mesh/CouplingScheme.cpp @@ -340,7 +340,7 @@ CouplingScheme::CouplingScheme( IndexT cs_id, IndexT mesh_id1, IndexT mesh_id2, m_couplingSchemeInfo.cs_case_info = NO_CASE_INFO; m_couplingSchemeInfo.cs_enforcement_info = NO_ENFORCEMENT_INFO; - m_loggingLevel = LoggingLevel::UNDEFINED; + m_loggingLevel = LoggingLevel::Undefined; } // end CouplingScheme::CouplingScheme() @@ -1176,21 +1176,21 @@ bool CouplingScheme::init() void CouplingScheme::setSlicLoggingLevel() { // set slic logging level for coupling schemes that have API modified logging levels - if ( this->m_loggingLevel != LoggingLevel::UNDEFINED ) { + if ( this->m_loggingLevel != LoggingLevel::Undefined ) { switch ( this->m_loggingLevel ) { - case LoggingLevel::DEBUG: { + case LoggingLevel::Debug: { axom::slic::setLoggingMsgLevel( axom::slic::message::Debug ); break; } - case LoggingLevel::INFO: { + case LoggingLevel::Info: { axom::slic::setLoggingMsgLevel( axom::slic::message::Info ); break; } - case LoggingLevel::WARNING: { + case LoggingLevel::Warning: { axom::slic::setLoggingMsgLevel( axom::slic::message::Warning ); break; } - case LoggingLevel::ERROR: { + case LoggingLevel::Error: { axom::slic::setLoggingMsgLevel( axom::slic::message::Error ); break; } diff --git a/src/tribol/utils/TestUtils.cpp b/src/tribol/utils/TestUtils.cpp index 110fa74d..6da885f4 100644 --- a/src/tribol/utils/TestUtils.cpp +++ b/src/tribol/utils/TestUtils.cpp @@ -1125,7 +1125,7 @@ int TestMesh::tribolSetupAndUpdate( ContactMethod method, EnforcementMethod enfo setPlotCycleIncrement( csIndex, 1 ); } - setLoggingLevel( csIndex, LoggingLevel::WARNING ); + setLoggingLevel( csIndex, LoggingLevel::Warning ); if ( method == COMMON_PLANE && enforcement == PENALTY ) { PenaltyConstraintType constraint_type = From 2b8e9952454a107f3dc986c6251cb3d797add442 Mon Sep 17 00:00:00 2001 From: "Eric B. Chin" Date: Wed, 25 Mar 2026 23:08:05 -0700 Subject: [PATCH 31/33] add missing define --- src/shared/config.hpp.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/config.hpp.in b/src/shared/config.hpp.in index dc8cb948..133dfd1e 100644 --- a/src/shared/config.hpp.in +++ b/src/shared/config.hpp.in @@ -23,5 +23,6 @@ #cmakedefine TRIBOL_USE_CUDA #cmakedefine TRIBOL_USE_HIP #cmakedefine TRIBOL_USE_OPENMP +#cmakedefine TRIBOL_USE_GPU_MPI #endif /* SHARED_CONFIG_HPP_ */ From b995bd6be0a2ca6cda18b147ef1f4c8ad72f6dbd Mon Sep 17 00:00:00 2001 From: EB Chin Date: Thu, 2 Apr 2026 23:34:30 -0700 Subject: [PATCH 32/33] update element centroids on host --- src/redecomp/partition/PartitionElements.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/redecomp/partition/PartitionElements.cpp b/src/redecomp/partition/PartitionElements.cpp index 7f8ed837..86382542 100644 --- a/src/redecomp/partition/PartitionElements.cpp +++ b/src/redecomp/partition/PartitionElements.cpp @@ -49,6 +49,8 @@ std::vector> PartitionElements::EntityCoordinates( // b) Interpolate to Quadrature points (Q-vector) qi->Values( e_vec, mesh_centroids ); + // NOTE: mesh_centroids will be fed into the RCB PartitionMethod and CoordList which are host only. make sure host is updated. + mesh_centroids.HostRead(); return mesh_centroids; }; From 104ddaacd381468bc82dcda7694196b7a6f6ffd7 Mon Sep 17 00:00:00 2001 From: EB Chin Date: Thu, 2 Apr 2026 23:43:22 -0700 Subject: [PATCH 33/33] formatting --- src/redecomp/partition/PartitionElements.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/redecomp/partition/PartitionElements.cpp b/src/redecomp/partition/PartitionElements.cpp index 86382542..f4e4b430 100644 --- a/src/redecomp/partition/PartitionElements.cpp +++ b/src/redecomp/partition/PartitionElements.cpp @@ -49,7 +49,8 @@ std::vector> PartitionElements::EntityCoordinates( // b) Interpolate to Quadrature points (Q-vector) qi->Values( e_vec, mesh_centroids ); - // NOTE: mesh_centroids will be fed into the RCB PartitionMethod and CoordList which are host only. make sure host is updated. + // NOTE: mesh_centroids will be fed into the RCB PartitionMethod and CoordList which are host only. make sure host + // is updated. mesh_centroids.HostRead(); return mesh_centroids; };