diff --git a/include/Makefile.in b/include/Makefile.in index 3ab4b8a75f6..56dc6c6766c 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -1053,6 +1053,7 @@ include_HEADERS = \ utils/compare_types.h \ utils/enum_to_string.h \ utils/error_vector.h \ + utils/fuzzy_equals.h \ utils/hashing.h \ utils/hashword.h \ utils/ignore_warnings.h \ diff --git a/include/base/libmesh_common.h b/include/base/libmesh_common.h index 8687f07e3eb..7197fd74605 100644 --- a/include/base/libmesh_common.h +++ b/include/base/libmesh_common.h @@ -634,6 +634,8 @@ inline Tnew libmesh_cast_int (Told oldvar) template constexpr std::false_type always_false{}; +static constexpr std::size_t libmesh_dim = LIBMESH_DIM; + // build a integer representation of version #define LIBMESH_VERSION_ID(major,minor,patch) (((major) << 16) | ((minor) << 8) | ((patch) & 0xFF)) diff --git a/include/include_HEADERS b/include/include_HEADERS index 0abe4ee0070..7b4d9bc991c 100644 --- a/include/include_HEADERS +++ b/include/include_HEADERS @@ -458,6 +458,7 @@ include_HEADERS = \ utils/compare_types.h \ utils/enum_to_string.h \ utils/error_vector.h \ + utils/fuzzy_equals.h \ utils/hashing.h \ utils/hashword.h \ utils/ignore_warnings.h \ diff --git a/include/libmesh/Makefile.am b/include/libmesh/Makefile.am index e266a7ecd73..7b3ce941807 100644 --- a/include/libmesh/Makefile.am +++ b/include/libmesh/Makefile.am @@ -454,6 +454,7 @@ BUILT_SOURCES = \ compare_types.h \ enum_to_string.h \ error_vector.h \ + fuzzy_equals.h \ hashing.h \ hashword.h \ ignore_warnings.h \ @@ -1937,6 +1938,9 @@ enum_to_string.h: $(top_srcdir)/include/utils/enum_to_string.h error_vector.h: $(top_srcdir)/include/utils/error_vector.h $(AM_V_GEN)rm -f $@ && $(LN_S) -f $< $@ +fuzzy_equals.h: $(top_srcdir)/include/utils/fuzzy_equals.h + $(AM_V_GEN)rm -f $@ && $(LN_S) -f $< $@ + hashing.h: $(top_srcdir)/include/utils/hashing.h $(AM_V_GEN)rm -f $@ && $(LN_S) -f $< $@ diff --git a/include/libmesh/Makefile.in b/include/libmesh/Makefile.in index 0350f542e8e..fea8481b416 100644 --- a/include/libmesh/Makefile.in +++ b/include/libmesh/Makefile.in @@ -670,16 +670,17 @@ BUILT_SOURCES = auto_ptr.h dirichlet_boundaries.h dof_map.h \ post_wait_free_buffer.h post_wait_unpack_buffer.h \ post_wait_work.h request.h standard_type.h status.h \ chunked_mapvector.h compare_types.h enum_to_string.h \ - error_vector.h hashing.h hashword.h ignore_warnings.h \ - int_range.h jacobi_polynomials.h libmesh_nullptr.h \ - location_maps.h mapvector.h null_output_iterator.h \ - number_lookups.h ostream_proxy.h parameters.h perf_log.h \ - perfmon.h plt_loader.h point_locator_base.h \ - point_locator_nanoflann.h point_locator_tree.h \ - pointer_to_pointer_iter.h pool_allocator.h restore_warnings.h \ - simple_range.h statistics.h string_to_enum.h timestamp.h \ - topology_map.h tree.h tree_base.h tree_node.h utility.h \ - vectormap.h win_gettimeofday.h xdr_cxx.h \ + error_vector.h fuzzy_equals.h hashing.h hashword.h \ + ignore_warnings.h int_range.h jacobi_polynomials.h \ + libmesh_nullptr.h location_maps.h mapvector.h \ + null_output_iterator.h number_lookups.h ostream_proxy.h \ + parameters.h perf_log.h perfmon.h plt_loader.h \ + point_locator_base.h point_locator_nanoflann.h \ + point_locator_tree.h pointer_to_pointer_iter.h \ + pool_allocator.h restore_warnings.h simple_range.h \ + statistics.h string_to_enum.h timestamp.h topology_map.h \ + tree.h tree_base.h tree_node.h utility.h vectormap.h \ + win_gettimeofday.h xdr_cxx.h \ parallel_communicator_specializations $(am__append_1) \ $(am__append_3) $(am__append_5) $(am__append_7) \ $(am__append_9) $(am__append_11) $(am__append_13) \ @@ -2273,6 +2274,9 @@ enum_to_string.h: $(top_srcdir)/include/utils/enum_to_string.h error_vector.h: $(top_srcdir)/include/utils/error_vector.h $(AM_V_GEN)rm -f $@ && $(LN_S) -f $< $@ +fuzzy_equals.h: $(top_srcdir)/include/utils/fuzzy_equals.h + $(AM_V_GEN)rm -f $@ && $(LN_S) -f $< $@ + hashing.h: $(top_srcdir)/include/utils/hashing.h $(AM_V_GEN)rm -f $@ && $(LN_S) -f $< $@ diff --git a/include/numerics/numeric_vector.h b/include/numerics/numeric_vector.h index 0ac3de4cec7..7254d5c4743 100644 --- a/include/numerics/numeric_vector.h +++ b/include/numerics/numeric_vector.h @@ -314,6 +314,12 @@ class NumericVector : public ReferenceCountedObject>, */ Real l2_norm_diff (const NumericVector & other_vec) const; + /** + * \returns The \f$ \ell_1 \f$-norm of \f$ \vec{u} - \vec{v} \f$, where + * \f$ \vec{u} \f$ is \p this. + */ + Real l1_norm_diff (const NumericVector & other_vec) const; + /** * \returns The size of the vector. */ @@ -1067,6 +1073,19 @@ void NumericVector::swap (NumericVector & v) std::swap(_type, v._type); } +template +auto +l1_norm(const NumericVector & vec) +{ + return vec.l1_norm(); +} + +template +auto +l1_norm_diff(const NumericVector & vec1, const NumericVector & vec2) +{ + return vec1.l1_norm_diff(vec2); +} } // namespace libMesh diff --git a/include/numerics/petsc_matrix.h b/include/numerics/petsc_matrix.h index 733ef30373f..cee7d3409c3 100644 --- a/include/numerics/petsc_matrix.h +++ b/include/numerics/petsc_matrix.h @@ -348,6 +348,9 @@ class PetscMatrix final : public SparseMatrix virtual void create_submatrix_nosort(SparseMatrix & submatrix, const std::vector & rows, const std::vector & cols) const override; + + virtual void scale(const T scale) override; + protected: /** diff --git a/include/numerics/sparse_matrix.h b/include/numerics/sparse_matrix.h index b4d1bed2b47..b369eae9c12 100644 --- a/include/numerics/sparse_matrix.h +++ b/include/numerics/sparse_matrix.h @@ -385,6 +385,11 @@ class SparseMatrix : public ReferenceCountedObject>, */ virtual Real linfty_norm () const = 0; + /** + * \returns The l1_norm() of the difference of \p this and \p other_mat + */ + Real l1_norm_diff (const SparseMatrix & other_mat) const; + /** * \returns \p true if the matrix has been assembled. */ @@ -573,8 +578,12 @@ class SparseMatrix : public ReferenceCountedObject>, std::vector & indices, std::vector & values) const = 0; -protected: + /** + * Scales all elements of this matrix by \p scale + */ + virtual void scale(const T scale); +protected: /** * Protected implementation of the create_submatrix and reinit_submatrix * routines. @@ -626,6 +635,19 @@ std::ostream & operator << (std::ostream & os, const SparseMatrix & m) return os; } +template +auto +l1_norm(const SparseMatrix & mat) +{ + return mat.l1_norm(); +} + +template +auto +l1_norm_diff(const SparseMatrix & mat1, const SparseMatrix & mat2) +{ + return mat1.l1_norm_diff(mat2); +} } // namespace libMesh diff --git a/include/numerics/type_vector.h b/include/numerics/type_vector.h index b6de64921ce..8fedfcdede5 100644 --- a/include/numerics/type_vector.h +++ b/include/numerics/type_vector.h @@ -24,6 +24,8 @@ #include "libmesh/libmesh_common.h" #include "libmesh/compare_types.h" #include "libmesh/tensor_tools.h" +#include "libmesh/int_range.h" +#include "libmesh/fuzzy_equals.h" // C++ includes #include // *must* precede for proper std:abs() on PGI, Sun Studio CC @@ -333,6 +335,11 @@ class TypeVector */ auto norm_sq() const -> decltype(std::norm(T())); + /** + * \returns The L1 norm of the vector + */ + auto l1_norm() const; + /** * \returns True if all values in the vector are zero */ @@ -974,27 +981,26 @@ bool TypeVector::is_zero() const return true; } +template <> +auto +TypeVector::l1_norm() const; + template -inline -bool TypeVector::absolute_fuzzy_equals(const TypeVector & rhs, Real tol) const +auto +TypeVector::l1_norm() const { -#if LIBMESH_DIM == 1 - return (std::abs(_coords[0] - rhs._coords[0]) - <= tol); -#endif + decltype(std::abs(T())) ret{}; + for (const auto i : make_range(libmesh_dim)) + ret += std::abs(_coords[i]); -#if LIBMESH_DIM == 2 - return (std::abs(_coords[0] - rhs._coords[0]) + - std::abs(_coords[1] - rhs._coords[1]) - <= tol); -#endif + return ret; +} -#if LIBMESH_DIM == 3 - return (std::abs(_coords[0] - rhs._coords[0]) + - std::abs(_coords[1] - rhs._coords[1]) + - std::abs(_coords[2] - rhs._coords[2]) - <= tol); -#endif +template +inline +bool TypeVector::absolute_fuzzy_equals(const TypeVector & rhs, Real tol) const +{ + return libMesh::absolute_fuzzy_equals(*this, rhs, tol); } @@ -1003,23 +1009,7 @@ template inline bool TypeVector::relative_fuzzy_equals(const TypeVector & rhs, Real tol) const { -#if LIBMESH_DIM == 1 - return this->absolute_fuzzy_equals(rhs, tol * - (std::abs(_coords[0]) + std::abs(rhs._coords[0]))); -#endif - -#if LIBMESH_DIM == 2 - return this->absolute_fuzzy_equals(rhs, tol * - (std::abs(_coords[0]) + std::abs(rhs._coords[0]) + - std::abs(_coords[1]) + std::abs(rhs._coords[1]))); -#endif - -#if LIBMESH_DIM == 3 - return this->absolute_fuzzy_equals(rhs, tol * - (std::abs(_coords[0]) + std::abs(rhs._coords[0]) + - std::abs(_coords[1]) + std::abs(rhs._coords[1]) + - std::abs(_coords[2]) + std::abs(rhs._coords[2]))); -#endif + return libMesh::relative_fuzzy_equals(*this, rhs, tol); } @@ -1175,6 +1165,21 @@ outer_product(const TypeVector & a, const T2 & b) return ret; } + +template +auto +l1_norm(const TypeVector & var) +{ + return var.l1_norm(); +} + +template +auto +l1_norm_diff(const TypeVector & vec1, const TypeVector & vec2) +{ + return l1_norm(vec1 - vec2); +} + } // namespace libMesh namespace std @@ -1182,7 +1187,8 @@ namespace std template auto norm(const libMesh::TypeVector & vector) -> decltype(std::norm(T())) { - return vector.norm(); + // Yea I agree it's dumb that the standard returns the square of the Euclidean norm + return vector.norm_sq(); } } // namespace std diff --git a/include/utils/compare_types.h b/include/utils/compare_types.h index f45cb731ff9..28843f40abb 100644 --- a/include/utils/compare_types.h +++ b/include/utils/compare_types.h @@ -279,6 +279,12 @@ struct ScalarTraits> { static const bool value = ScalarTraits::value; }; +template +struct RealTraits> +{ + static const bool value = RealTraits::value; +}; + } // namespace libMesh #endif // LIBMESH_HAVE_METAPHYSICL diff --git a/include/utils/fuzzy_equals.h b/include/utils/fuzzy_equals.h new file mode 100644 index 00000000000..069588f496e --- /dev/null +++ b/include/utils/fuzzy_equals.h @@ -0,0 +1,117 @@ +// The libMesh Finite Element Library. +// Copyright (C) 2002-2024 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner + +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef LIBMESH_FUZZY_EQUAL_H +#define LIBMESH_FUZZY_EQUAL_H + +#include "libmesh/libmesh_common.h" +#include "libmesh/tensor_tools.h" +#include "libmesh/compare_types.h" +#include "libmesh/int_range.h" + +#ifdef LIBMESH_HAVE_METAPHYSICL +#include "metaphysicl/raw_type.h" +#endif + +namespace libMesh +{ +/** + * Computes the L1 norm + */ +template ::value, int>::type = 0> +auto +l1_norm(const T & var) +{ + return std::abs(var); +} + +/** + * Computes the L1 norm of the diff between \p var1 and \p var2 + */ +template ::value && ScalarTraits::value, int>::type = 0> +auto +l1_norm_diff(const T & var1, const T2 & var2) +{ + return l1_norm(var1 - var2); +} + +#ifdef LIBMESH_HAVE_METAPHYSICL +/** + * Function to check whether two variables are equal within an absolute tolerance + * @param var1 The first variable to be checked + * @param var2 The second variable to be checked + * @param tol The tolerance to be used + * @return true if var1 and var2 are equal within tol + */ +template +bool +absolute_fuzzy_equals(const T & var1, const T2 & var2, const Real tol = TOLERANCE * TOLERANCE) +{ + return MetaPhysicL::raw_value(l1_norm_diff(var1, var2)) <= tol; +} + +/** + * Function to check whether two variables are equal within a relative tolerance + * @param var1 The first variable to be checked + * @param var2 The second variable to be checked + * @param tol The relative tolerance to be used + * @return true if var1 and var2 are equal within relative tol + */ +template +bool +relative_fuzzy_equals(const T & var1, const T2 & var2, const Real tol = TOLERANCE * TOLERANCE) +{ + return absolute_fuzzy_equals( + var1, + var2, + tol * (MetaPhysicL::raw_value(l1_norm(var1)) + MetaPhysicL::raw_value(l1_norm(var2)))); +} +#else +/** + * Function to check whether two variables are equal within an absolute tolerance + * @param var1 The first variable to be checked + * @param var2 The second variable to be checked + * @param tol The tolerance to be used + * @return true if var1 and var2 are equal within tol + */ +template +bool +absolute_fuzzy_equals(const T & var1, const T2 & var2, const Real tol = TOLERANCE * TOLERANCE) +{ + return l1_norm_diff(var1, var2) <= tol; +} + +/** + * Function to check whether two variables are equal within a relative tolerance + * @param var1 The first variable to be checked + * @param var2 The second variable to be checked + * @param tol The relative tolerance to be used + * @return true if var1 and var2 are equal within relative tol + */ +template +bool +relative_fuzzy_equals(const T & var1, const T2 & var2, const Real tol = TOLERANCE * TOLERANCE) +{ + return absolute_fuzzy_equals(var1, var2, tol * (l1_norm(var1) + l1_norm(var2))); +} +#endif + +} // namespace libMesh + +#endif // LIBMESH_FUZZY_EQUAL_H diff --git a/src/numerics/numeric_vector.C b/src/numerics/numeric_vector.C index af06583546f..e820babe628 100644 --- a/src/numerics/numeric_vector.C +++ b/src/numerics/numeric_vector.C @@ -27,6 +27,7 @@ #include "libmesh/tensor_tools.h" #include "libmesh/enum_solver_package.h" #include "libmesh/int_range.h" +#include "libmesh/fuzzy_equals.h" // C++ includes @@ -375,6 +376,22 @@ Real NumericVector::l2_norm_diff (const NumericVector & v) const +template +Real NumericVector::l1_norm_diff (const NumericVector & v) const +{ + libmesh_assert(this->compatible(v)); + + Real norm = 0; + for (const auto i : make_range(this->first_local_index(), this->last_local_index())) + norm += libMesh::l1_norm_diff((*this)(i), v(i)); + + this->comm().sum(norm); + + return norm; +} + + + template void NumericVector::add_vector (const T * v, const std::vector & dof_indices) diff --git a/src/numerics/petsc_matrix.C b/src/numerics/petsc_matrix.C index 0e31bc90b2c..cdae001b2b5 100644 --- a/src/numerics/petsc_matrix.C +++ b/src/numerics/petsc_matrix.C @@ -1575,6 +1575,14 @@ SparseMatrix & PetscMatrix::operator= (const SparseMatrix & v) return *this; } +template +void PetscMatrix::scale(const T scale) +{ + libmesh_assert(this->closed()); + + const auto ierr = MatScale(this->_mat, scale); + LIBMESH_CHKERR(ierr); +} //------------------------------------------------------------------ // Explicit instantiations diff --git a/src/numerics/sparse_matrix.C b/src/numerics/sparse_matrix.C index 31d85cdd78e..cd2af923169 100644 --- a/src/numerics/sparse_matrix.C +++ b/src/numerics/sparse_matrix.C @@ -855,6 +855,26 @@ void SparseMatrix::read_petsc_hdf5(const std::string &) +template +void SparseMatrix::scale(const T scale) +{ + libmesh_assert(this->closed()); + + for (const auto i : make_range(this->row_start(), this->row_stop())) + for (const auto j : make_range(this->col_start(), this->col_stop())) + this->set(i, j, (*this)(i, j) * scale); +} + + + +template +Real SparseMatrix::l1_norm_diff(const SparseMatrix & other_mat) const +{ + auto diff_mat = this->clone(); + diff_mat->add(-1.0, other_mat); + return diff_mat->l1_norm(); +} + //------------------------------------------------------------------ // Explicit instantiations template class LIBMESH_EXPORT SparseMatrix; diff --git a/src/numerics/type_vector.C b/src/numerics/type_vector.C index 5f747003ead..3191b66fc26 100644 --- a/src/numerics/type_vector.C +++ b/src/numerics/type_vector.C @@ -222,6 +222,19 @@ bool TypeVector::operator >= (const TypeVector & rhs) const +template <> +auto +TypeVector::l1_norm() const +{ + bool ret{}; + for (const auto i : make_range(libmesh_dim)) + ret += _coords[i]; + + return ret; +} + + + // ------------------------------------------------------------ // Explicit instantiations template class LIBMESH_EXPORT TypeVector; diff --git a/tests/numerics/numeric_vector_test.h b/tests/numerics/numeric_vector_test.h index 372e849f4d5..cb390114edd 100644 --- a/tests/numerics/numeric_vector_test.h +++ b/tests/numerics/numeric_vector_test.h @@ -6,6 +6,7 @@ // libMesh includes #include +#include #include "libmesh_cppunit.h" @@ -74,8 +75,12 @@ class NumericVectorTest : public CppUnit::TestCase { auto v_clone = v.clone(); auto & vorig = *v_clone; + CPPUNIT_ASSERT(relative_fuzzy_equals(v, vorig)); + v += v; + CPPUNIT_ASSERT(!relative_fuzzy_equals(v, vorig)); + for (libMesh::dof_id_type n=first; n != last; n++) LIBMESH_ASSERT_FP_EQUAL(libMesh::libmesh_real(v(n)), libMesh::Real(2*n+2), diff --git a/tests/numerics/sparse_matrix_test.h b/tests/numerics/sparse_matrix_test.h index 52228af014c..765f5097837 100644 --- a/tests/numerics/sparse_matrix_test.h +++ b/tests/numerics/sparse_matrix_test.h @@ -7,6 +7,7 @@ // libMesh includes #include #include +#include #include "libmesh_cppunit.h" @@ -223,6 +224,8 @@ class SparseMatrixTest : public CppUnit::TestCase { LOG_UNIT_TEST; + setValues(); + // Matrix must be closed before it can be cloned. matrix->close(); @@ -239,6 +242,9 @@ class SparseMatrixTest : public CppUnit::TestCase // Check that copy has same values as original LIBMESH_ASSERT_FP_EQUAL(copy->l1_norm(), matrix->l1_norm(), _tolerance); + CPPUNIT_ASSERT(relative_fuzzy_equals(*matrix, *copy)); + copy->scale(2); + CPPUNIT_ASSERT(!relative_fuzzy_equals(*matrix, *copy)); } { diff --git a/tests/numerics/type_vector_test.h b/tests/numerics/type_vector_test.h index e1a97940ecf..381d9100460 100644 --- a/tests/numerics/type_vector_test.h +++ b/tests/numerics/type_vector_test.h @@ -2,6 +2,7 @@ #define TYPE_VECTOR_TEST_H #include +#include #include "libmesh_cppunit.h" @@ -364,6 +365,7 @@ class TypeVectorTestBase : public CppUnit::TestCase { CPPUNIT_ASSERT( (*basem_1_1_1) == (*basem_1_1_1) ); CPPUNIT_ASSERT( !((*basem_1_1_1) == (*basem_n1_1_n1)) ); + CPPUNIT_ASSERT( relative_fuzzy_equals(*basem_1_1_1, *basem_1_1_1) ); } void testInEqualityBase() @@ -372,6 +374,7 @@ class TypeVectorTestBase : public CppUnit::TestCase { CPPUNIT_ASSERT( !((*basem_1_1_1) != (*basem_1_1_1)) ); CPPUNIT_ASSERT( (*basem_1_1_1) != (*basem_n1_1_n1) ); + CPPUNIT_ASSERT( !relative_fuzzy_equals(*basem_1_1_1, *basem_n1_1_n1) ); } void testAssignmentBase()