diff --git a/include/graphblas/base/blas3.hpp b/include/graphblas/base/blas3.hpp index c22bfba71..081880a2a 100644 --- a/include/graphblas/base/blas3.hpp +++ b/include/graphblas/base/blas3.hpp @@ -442,6 +442,198 @@ namespace grb { return ret == SUCCESS ? UNSUPPORTED : ret; } + /** + * Scales, or \em folds, a matrix into a matrix, using a constant + * matrix to perform element-wise operations against. + * + * @tparam descr The descriptor to be used (descriptors::no_operation if + * left unspecified). + * @tparam Monoid The monoid to use. + * @tparam IOType The type of the elements in the input and output + * ALP/GraphBLAS matrix \a A. + * @tparam MaskType The type of the elements in the supplied ALP/GraphBLAS + * matrix \a mask. + * @tparam InputType The type of the input matrix \a B. + * + * @param[in,out] A Any ALP/GraphBLAS matrix, which will be scaled. + * Prior value will be considered. + * @param[in] mask Any ALP/GraphBLAS matrix. + * @param[in] B Any ALP/GraphBLAS matrix, which will be used to + * scale \a A. + * @param[in] monoid The operator under which to perform the element-wise + * scaling. + * + * @return grb::SUCCESS When the call completed successfully. + * @return grb::MISMATCH If A, mask and B don't have the same dimensions. + * + * @see grb::foldr provides similar in-place functionality. + * @see grb::eWiseApply provides out-of-place semantics. + * + * \parblock + * \par Valid descriptors + * - descriptors::no_operation: the default descriptor. + * - descriptors::no_casting: the first domain of + * \a op must match \a IOType, the second domain of \a op + * match \a InputType, the third domain must match \a IOType. + * - descriptors::transpose_left: mask^T will be considered + * instead of \a mask. + * - descriptors::transpose_right: B^T will be considered + * instead of \a B. + * - descriptors::invert_mask: Not supported yet. + * + * \note Invalid descriptors will be ignored. + * + * \endparblock + * + * \par Performance semantics + * Each backend must define performance semantics for this primitive. + * + * @see perfSemantics + */ + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B, + Backend backend + > + RC foldl( + Matrix< IOType, backend, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, backend, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, backend, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { +#ifndef NDEBUG + const bool should_not_call_base_masked_matrix_matrix_foldl = false; + assert( should_not_call_base_masked_matrix_matrix_foldl ); +#endif + (void) A; + (void) mask; + (void) B; + (void) monoid; + return UNSUPPORTED; + } + + /** + * Scales, or \em folds, a matrix into a matrix, using a constant + * matrix to perform element-wise operations against. + * + * Left-to-right unmasked variant. + * + * Please see the masked grb::foldl variant for a full description. + */ + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B, + Backend backend + > + RC foldl( + Matrix< IOType, backend, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, backend, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { +#ifndef NDEBUG + const bool should_not_call_base_unmasked_matrix_matrix_foldl = false; + assert( should_not_call_base_unmasked_matrix_matrix_foldl ); +#endif + (void) A; + (void) B; + (void) monoid; + return UNSUPPORTED; + } + + /** + * Scales, or \em folds, a matrix into a matrix, using a constant + * matrix to perform element-wise operations against. + * + * Right-to-left masked variant. + * + * Please see the masked grb::foldl variant for a full description. + */ + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B, + Backend backend + > + RC foldr( + Matrix< IOType, backend, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, backend, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, backend, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { +#ifndef NDEBUG + const bool should_not_call_base_masked_matrix_matrix_foldr = false; + assert( should_not_call_base_masked_matrix_matrix_foldr ); +#endif + (void) A; + (void) mask; + (void) B; + (void) monoid; + return UNSUPPORTED; + } + + /** + * Scales, or \em folds, a matrix into a matrix, using a constant + * matrix to perform element-wise operations against. + * + * Right-to-left unmasked variant. + * + * Please see the masked grb::foldl variant for a full description. + */ + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B, + Backend backend + > + RC foldr( + Matrix< IOType, backend, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, backend, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { +#ifndef NDEBUG + const bool should_not_call_base_unmasked_matrix_matrix_foldr = false; + assert( should_not_call_base_unmasked_matrix_matrix_foldr ); +#endif + (void) A; + (void) B; + (void) monoid; + return UNSUPPORTED; + } + /** * @} */ diff --git a/include/graphblas/blas0.hpp b/include/graphblas/blas0.hpp index 751b2cf14..5e1b1aaa2 100644 --- a/include/graphblas/blas0.hpp +++ b/include/graphblas/blas0.hpp @@ -32,6 +32,7 @@ #include //enable_if #include "graphblas/descriptors.hpp" +#include "graphblas/identities.hpp" #include "graphblas/rc.hpp" #include "graphblas/type_traits.hpp" @@ -604,6 +605,38 @@ namespace grb { }; + template< typename MaskType > + struct MaskHasValue { + + public: + template < Descriptor descr = descriptors::no_operation, typename MaskStruct > + MaskHasValue( const MaskStruct& mask_raw, const size_t k ) { + bool hasValue = (bool) mask_raw.getValue( k, grb::identities::logical_false() ); + if (descr & grb::descriptors::invert_mask) { + hasValue = !hasValue; + } + value = hasValue; + } + + bool value; + }; + + template<> + struct MaskHasValue< void > { + + public: + template < Descriptor descr = descriptors::no_operation, typename MaskStruct > + MaskHasValue( const MaskStruct& mask_raw, const size_t k ) : + value(not (descr & grb::descriptors::invert_mask)){ + (void) mask_raw; + (void) k; + } + + const bool value; + + }; + + } // namespace internal } // namespace grb diff --git a/include/graphblas/bsp1d/blas3.hpp b/include/graphblas/bsp1d/blas3.hpp index 386beb164..dac420c94 100644 --- a/include/graphblas/bsp1d/blas3.hpp +++ b/include/graphblas/bsp1d/blas3.hpp @@ -205,6 +205,161 @@ namespace grb { return internal::checkGlobalErrorStateOrClear( C, ret ); } + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, BSP1D, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, BSP1D, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, BSP1D, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + +#ifdef _DEBUG + std::cout << "In grb::foldl( BSP1D, matrix, mask, matrix, monoid )\n"; +#endif + RC rc = SUCCESS; + + if( grb::nnz( A ) == 0 || grb::nnz( mask ) == 0 ) { + return rc; + } + + // Do local folding + rc = foldl< descr >( + internal::getLocal( A ), + internal::getLocal( mask ), + internal::getLocal( B ), + monoid + ); + + return rc; + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, BSP1D, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, BSP1D, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + +#ifdef _DEBUG + std::cout << "In grb::foldl( BSP1D, matrix, matrix, monoid )\n"; +#endif + RC rc = SUCCESS; + + if( grb::nnz( A ) == 0 || grb::nnz( B ) == 0 ) { + return rc; + } + + // Do local folding + rc = foldl< descr >( + internal::getLocal( A ), + internal::getLocal( B ), + monoid + ); + + return rc; + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, BSP1D, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, BSP1D, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, BSP1D, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + +#ifdef _DEBUG + std::cout << "In grb::foldr( BSP1D, matrix, mask, matrix, monoid )\n"; +#endif + RC rc = SUCCESS; + + if( grb::nnz( A ) == 0 || grb::nnz( mask ) == 0 ) { + return rc; + } + + // Do local folding + rc = foldr< descr >( + internal::getLocal( A ), + internal::getLocal( mask ), + internal::getLocal( B ), + monoid + ); + + return rc; + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, BSP1D, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, BSP1D, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { +#ifdef _DEBUG + std::cout << "In grb::foldr( BSP1D, matrix, matrix, monoid )\n"; +#endif + RC rc = SUCCESS; + + if( grb::nnz( A ) == 0 || grb::nnz( B ) == 0 ) { + return rc; + } + + // Do local folding + rc = foldr< descr >( + internal::getLocal( A ), + internal::getLocal( B ), + monoid + ); + + return rc; + } + } // namespace grb #endif diff --git a/include/graphblas/hyperdags/blas3.hpp b/include/graphblas/hyperdags/blas3.hpp index ee0c10f36..55cf3294b 100644 --- a/include/graphblas/hyperdags/blas3.hpp +++ b/include/graphblas/hyperdags/blas3.hpp @@ -332,6 +332,186 @@ namespace grb { return ret; } + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, hyperdags, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, hyperdags, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, hyperdags, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + const RC ret = foldl< descr >( + internal::getMatrix( A ), + internal::getMatrix( mask ), + internal::getMatrix( B ), + monoid + ); + if( ret != SUCCESS ) { return ret; } + if( nrows( A ) == 0 || ncols( A ) == 0 ) { return ret; } + if( nrows( mask ) == 0 || ncols( mask ) == 0 ) { return ret; } + if( nrows( B ) == 0 || ncols( B ) == 0 ) { return ret; } + std::array< const void *, 0 > sourcesP{}; + std::array< uintptr_t, 3 > sourcesC{ + getID( internal::getMatrix(A) ), + getID( internal::getMatrix(mask) ), + getID( internal::getMatrix(B) ) + }; + std::array< uintptr_t, 1 > destinations{ + getID( internal::getMatrix(A) ) + }; + internal::hyperdags::generator.addOperation( + internal::hyperdags::FOLDL_MATRIX_MASK_MATRIX_MONOID, + sourcesP.begin(), sourcesP.end(), + sourcesC.begin(), sourcesC.end(), + destinations.begin(), destinations.end() + ); + return ret; + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, hyperdags, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, hyperdags, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + const RC ret = foldl< descr >( + internal::getMatrix( A ), + internal::getMatrix( B ), + monoid + ); + if( ret != SUCCESS ) { return ret; } + if( nrows( A ) == 0 || ncols( A ) == 0 ) { return ret; } + if( nrows( B ) == 0 || ncols( B ) == 0 ) { return ret; } + std::array< const void *, 0 > sourcesP{}; + std::array< uintptr_t, 2 > sourcesC{ + getID( internal::getMatrix(A) ), + getID( internal::getMatrix(B) ) + }; + std::array< uintptr_t, 1 > destinations{ + getID( internal::getMatrix(A) ) + }; + internal::hyperdags::generator.addOperation( + internal::hyperdags::FOLDR_MATRIX_MATRIX_MONOID, + sourcesP.begin(), sourcesP.end(), + sourcesC.begin(), sourcesC.end(), + destinations.begin(), destinations.end() + ); + return ret; + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, hyperdags, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, hyperdags, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, hyperdags, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + const RC ret = foldr< descr >( + internal::getMatrix( A ), + internal::getMatrix( mask ), + internal::getMatrix( B ), + monoid + ); + if( ret != SUCCESS ) { return ret; } + if( nrows( A ) == 0 || ncols( A ) == 0 ) { return ret; } + if( nrows( mask ) == 0 || ncols( mask ) == 0 ) { return ret; } + if( nrows( B ) == 0 || ncols( B ) == 0 ) { return ret; } + std::array< const void *, 0 > sourcesP{}; + std::array< uintptr_t, 3 > sourcesC{ + getID( internal::getMatrix(A) ), + getID( internal::getMatrix(mask) ), + getID( internal::getMatrix(B) ) + }; + std::array< uintptr_t, 1 > destinations{ + getID( internal::getMatrix(A) ) + }; + internal::hyperdags::generator.addOperation( + internal::hyperdags::FOLDR_MATRIX_MASK_MATRIX_MONOID, + sourcesP.begin(), sourcesP.end(), + sourcesC.begin(), sourcesC.end(), + destinations.begin(), destinations.end() + ); + return ret; + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, hyperdags, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, hyperdags, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + const RC ret = foldr< descr >( + internal::getMatrix( A ), + internal::getMatrix( B ), + monoid + ); + if( ret != SUCCESS ) { return ret; } + if( nrows( A ) == 0 || ncols( A ) == 0 ) { return ret; } + if( nrows( B ) == 0 || ncols( B ) == 0 ) { return ret; } + std::array< const void *, 0 > sourcesP{}; + std::array< uintptr_t, 2 > sourcesC{ + getID( internal::getMatrix(A) ), + getID( internal::getMatrix(B) ) + }; + std::array< uintptr_t, 1 > destinations{ + getID( internal::getMatrix(A) ) + }; + internal::hyperdags::generator.addOperation( + internal::hyperdags::FOLDR_MATRIX_MATRIX_MONOID, + sourcesP.begin(), sourcesP.end(), + sourcesC.begin(), sourcesC.end(), + destinations.begin(), destinations.end() + ); + return ret; + } + } // end namespace grb #endif diff --git a/include/graphblas/hyperdags/hyperdags.hpp b/include/graphblas/hyperdags/hyperdags.hpp index 4ef0e0059..b912ddd32 100644 --- a/include/graphblas/hyperdags/hyperdags.hpp +++ b/include/graphblas/hyperdags/hyperdags.hpp @@ -488,12 +488,19 @@ namespace grb { EWISEMUL_VECTOR_VECTOR_ALPHA_BETA_RING, - EWISELAMBDA_FUNC_VECTOR + EWISELAMBDA_FUNC_VECTOR, + FOLDL_MATRIX_MASK_MATRIX_MONOID, + + FOLDL_MATRIX_MATRIX_MONOID, + + FOLDR_MATRIX_MASK_MATRIX_MONOID, + + FOLDR_MATRIX_MATRIX_MONOID }; /** \internal How many operation vertex types exist. */ - const constexpr size_t numOperationVertexTypes = 106; + const constexpr size_t numOperationVertexTypes = 110; /** \internal An array of all operation vertex types. */ const constexpr enum OperationVertexType @@ -604,7 +611,11 @@ namespace grb { EWISEMUL_VECTOR_VECTOR_ALPHA_VECTOR_RING, EWISEMUL_VECTOR_VECTOR_VECTOR_BETA_RING, EWISEMUL_VECTOR_VECTOR_ALPHA_BETA_RING, - EWISELAMBDA_FUNC_VECTOR + EWISELAMBDA_FUNC_VECTOR, + FOLDL_MATRIX_MASK_MATRIX_MONOID, + FOLDL_MATRIX_MATRIX_MONOID, + FOLDR_MATRIX_MASK_MATRIX_MONOID, + FOLDR_MATRIX_MATRIX_MONOID }; /** \internal @returns The operation vertex type as a string. */ diff --git a/include/graphblas/nonblocking/blas3.hpp b/include/graphblas/nonblocking/blas3.hpp index c52c9aaff..5dc6c002a 100644 --- a/include/graphblas/nonblocking/blas3.hpp +++ b/include/graphblas/nonblocking/blas3.hpp @@ -571,6 +571,166 @@ namespace grb { ); } + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, nonblocking, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, nonblocking, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, nonblocking, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + if( internal::NONBLOCKING::warn_if_not_native && + config::PIPELINE::warn_if_not_native + ) { + std::cerr << "Warning: foldl( nonblocking ) currently delegates to a " + << "blocking implementation.\n" + << " Further similar such warnings will be suppressed.\n"; + internal::NONBLOCKING::warn_if_not_native = false; + } + + // nonblocking execution is not supported + // first, execute any computation that is not completed + internal::le.execution(); + + // second, delegate to the reference backend + return foldl< descr, Monoid >( + internal::getRefMatrix( A ), + internal::getRefMatrix( mask ), + internal::getRefMatrix( B ), + monoid + ); + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, nonblocking, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, nonblocking, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + if( internal::NONBLOCKING::warn_if_not_native && + config::PIPELINE::warn_if_not_native + ) { + std::cerr << "Warning: foldl( nonblocking ) currently delegates to a " + << "blocking implementation.\n" + << " Further similar such warnings will be suppressed.\n"; + internal::NONBLOCKING::warn_if_not_native = false; + } + + // nonblocking execution is not supported + // first, execute any computation that is not completed + internal::le.execution(); + + // second, delegate to the reference backend + return foldl< descr, Monoid >( + internal::getRefMatrix( A ), + internal::getRefMatrix( B ), + monoid + ); + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, nonblocking, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, nonblocking, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, nonblocking, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + if( internal::NONBLOCKING::warn_if_not_native && + config::PIPELINE::warn_if_not_native + ) { + std::cerr << "Warning: foldr( nonblocking ) currently delegates to a " + << "blocking implementation.\n" + << " Further similar such warnings will be suppressed.\n"; + internal::NONBLOCKING::warn_if_not_native = false; + } + + // nonblocking execution is not supported + // first, execute any computation that is not completed + internal::le.execution(); + + // second, delegate to the reference backend + return foldr< descr, Monoid >( + internal::getRefMatrix( A ), + internal::getRefMatrix( mask ), + internal::getRefMatrix( B ), + monoid + ); + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, nonblocking, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, nonblocking, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + if( internal::NONBLOCKING::warn_if_not_native && + config::PIPELINE::warn_if_not_native + ) { + std::cerr << "Warning: foldr( nonblocking ) currently delegates to a " + << "blocking implementation.\n" + << " Further similar such warnings will be suppressed.\n"; + internal::NONBLOCKING::warn_if_not_native = false; + } + + // nonblocking execution is not supported + // first, execute any computation that is not completed + internal::le.execution(); + + // second, delegate to the reference backend + return foldr< descr, Monoid >( + internal::getRefMatrix( A ), + internal::getRefMatrix( B ), + monoid + ); + } + } // namespace grb #undef NO_CAST_ASSERT diff --git a/include/graphblas/reference/blas3.hpp b/include/graphblas/reference/blas3.hpp index 868547183..cb0a3988b 100644 --- a/include/graphblas/reference/blas3.hpp +++ b/include/graphblas/reference/blas3.hpp @@ -30,10 +30,6 @@ #include "io.hpp" #include "matrix.hpp" -#ifdef _H_GRB_REFERENCE_OMP_BLAS3 - #include -#endif - #define NO_CAST_ASSERT( x, y, z ) \ static_assert( x, \ "\n\n" \ @@ -1204,6 +1200,332 @@ namespace grb { return SUCCESS; } + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename InputType, typename IOType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC fold_matrix_matrix_unmasked_generic( + Matrix< IOType, reference, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, reference, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid() + ) { +#ifdef _DEBUG + std::cout << "In grb::internal::fold_matrix_matrix_unmasked_generic( reference )\n" << std::flush; +#endif + + if( nnz(B) == 0 || nnz(A) == 0 ) { +#ifdef _DEBUG + std::cout << "One or the two matrices are empty, nothing to compute.\n" << std::flush; +#endif + return SUCCESS; + } + + const auto &A_crs_raw = internal::getCRS( A ); + const auto &A_ccs_raw = internal::getCCS( A ); + const auto &B_raw = descr & grb::descriptors::transpose_right + ? internal::getCCS( B ) + : internal::getCRS( B ); + const size_t m = nrows( A ); + const size_t n = ncols( A ); + const size_t m_B = + descr & grb::descriptors::transpose_right || descr & grb::descriptors::transpose_matrix + ? ncols( B ) + : nrows( B ); + const size_t n_B = + descr & grb::descriptors::transpose_right || descr & grb::descriptors::transpose_matrix + ? nrows( B ) + : ncols( B ); + + // Check mask dimensions + if( m != m_B || n != n_B ) { +#ifdef _DEBUG + std::cout << "Dimensions of matrices do not match!\n" << std::flush; +#endif + return MISMATCH; + } + + RC rc = SUCCESS; + RC local_rc = rc; + const auto &op = monoid.getOperator(); + +#ifdef _H_GRB_REFERENCE_OMP_BLAS3 + #pragma omp parallel default(none) \ + shared(A_crs_raw, A_ccs_raw, B_raw, rc, std::cout) \ + firstprivate(local_rc, m, op) +#endif + { + size_t start_row = 0; + size_t end_row = m; +#ifdef _H_GRB_REFERENCE_OMP_BLAS3 + config::OMP::localRange( start_row, end_row, 0, m ); +#endif + for( auto i = start_row; i < end_row; ++i ) { + auto B_k = B_raw.col_start[ i ]; + const auto A_k_start = A_crs_raw.col_start[ i ]; + const auto A_k_end = A_crs_raw.col_start[ i + 1 ]; + for( auto k = A_k_start; k < A_k_end; ++k ) { + const auto j = A_crs_raw.row_index[ k ]; + /* Increment the mask pointer until we find the right column, + * or a lower column (since the storage withing a row + * is sorted in a descending order) + */ + while( B_k < B_raw.col_start[ i + 1 ] + && B_raw.row_index[ B_k ] > j + ) { + B_k++; + } + // Check if we are out of values in B + if( B_k >= B_raw.col_start[ i + 1 ] ) { +#ifdef _DEBUG + std::cout << "Not value left in B for this column\n" << std::flush; +#endif + break; + } + // Check if we found the right column + if( B_raw.row_index[ B_k ] != j ) { +#ifdef _DEBUG + std::cout << "Skip B value at: ( " + std::to_string( i ) + + ";" + std::to_string( B_raw.row_index[ B_k ] ) + + " )\n" << std::flush; +#endif + continue; + } + + // Get B value + const auto B_val = B_raw.getValue( + B_k, + identities::zero< InputType >::value() + ); +#ifdef _DEBUG + std::cout << "B( " + std::to_string( i ) + ";" + + std::to_string( B_raw.row_index[ B_k ] ) + + " ) = " + std::to_string( B_val ) + "\n" << std::flush; +#endif + + // Get A value + const auto a_val_before = A_crs_raw.values[ k ]; +#ifdef _DEBUG + std::cout << "A( " + std::to_string( i ) + ";" + + std::to_string( j ) + " ) = " + + std::to_string( a_val_before ) + "\n" << std::flush; +#endif + + // Compute the fold for this coordinate + local_rc = local_rc + ? local_rc + : apply< descr >( + A_crs_raw.values[ k ], a_val_before, B_val, op + ); + local_rc = local_rc + ? local_rc + : apply< descr >( + A_ccs_raw.values[ k ], a_val_before, B_val, op + ); +#ifdef _DEBUG + std::cout << "Computing: op(" + std::to_string( a_val_before ) + + ", " + std::to_string( a_val_before ) + ") = " + + std::to_string( A_ccs_raw.values[ k ] ) + "\n" << std::flush; +#endif + } + } + + if( local_rc != SUCCESS ) { +#ifdef _H_GRB_REFERENCE_OMP_BLAS3 + #pragma omp critical +#endif + { // Reduction with the global return code + rc = rc ? rc : local_rc; + } + } + } + return rc; + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename InputType, typename MaskType, typename IOType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC fold_matrix_matrix_masked_generic( + Matrix< IOType, reference, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, reference, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, reference, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid() + ) { + typedef typename std::conditional< + std::is_void< MaskType >::value, bool, MaskType + >::type MaskIdentityType; + +#ifdef _DEBUG + std::cout << "In grb::internal::fold_matrix_matrix_masked_generic( reference )\n" << std::flush; +#endif + + if( nnz(mask) == 0 || nnz(A) == 0 ) { + return SUCCESS; + } + + // Descriptors aliases + constexpr bool transpose_left = descr & grb::descriptors::transpose_left; + constexpr bool transpose_right = descr & grb::descriptors::transpose_right; + + auto &A_crs_raw = internal::getCRS( A ); + auto &A_ccs_raw = internal::getCCS( A ); + const size_t m = nrows( A ); + const size_t n = ncols( A ); + + const auto &mask_raw = transpose_left + ? internal::getCCS( mask ) + : internal::getCRS( mask ); + const size_t m_mask = transpose_left + ? ncols( mask ) + : nrows( mask ); + const size_t n_mask = transpose_left + ? nrows( mask ) + : ncols( mask ); + + const auto &B_raw = transpose_right + ? internal::getCCS( B ) + : internal::getCRS( B ); + const size_t m_B = transpose_right + ? ncols( B ) + : nrows( B ); + const size_t n_B = transpose_right + ? nrows( B ) + : ncols( B ); + + // Check mask dimensions + if( m != m_B || n != n_B || m != m_mask || n != n_mask ) { +#ifdef _DEBUG + std::cout << "Dimensions of matrices do not match!\n" << std::flush; +#endif + return MISMATCH; + } + + RC rc = SUCCESS; + RC local_rc = rc; + const auto &op = monoid.getOperator(); + const InputType A_identity = monoid.template getIdentity< IOType >(); + const InputType B_identity = monoid.template getIdentity< InputType >(); + +#ifdef _H_GRB_REFERENCE_OMP_BLAS3 + #pragma omp parallel default(none) \ + shared(A_crs_raw, A_ccs_raw, mask_raw, B_raw, rc, std::cout) \ + firstprivate(local_rc, m, op, A_identity, B_identity) +#endif + { + size_t start_row = 0; + size_t end_row = m; +#ifdef _H_GRB_REFERENCE_OMP_BLAS3 + config::OMP::localRange( start_row, end_row, 0, m ); +#endif + for( auto i = start_row; i < end_row; ++i ) { + auto B_k = B_raw.col_start[ i ]; + auto mask_k = mask_raw.col_start[ i ]; + const auto A_k_start = A_crs_raw.col_start[ i ]; + const auto A_k_end = A_crs_raw.col_start[ i + 1 ]; + for( auto A_k = A_k_start; A_k < A_k_end; ++A_k ) { + const auto j = A_crs_raw.row_index[ A_k ]; + + /* Increment the mask pointer until we find the right column, + * or a lower column (since the storage withing a row + * is sorted in a descending order) + */ + while( + mask_k < mask_raw.col_start[ i + 1 ] && mask_raw.row_index[ mask_k ] > j + ) { + mask_k++; + } + if( mask_k >= mask_raw.col_start[ i + 1 ] ) { +#ifdef _DEBUG + std::cout << "Not value left in for this column\n" << std::flush; +#endif + break; + } + + const bool mask_has_value = mask_raw.getValue( + mask_k, + identities::logical_true::value() + ); + if( mask_raw.row_index[ B_k ] != j || !mask_has_value ) { +#ifdef _DEBUG + std::cout << "Skip value at: ( " + std::to_string( i ) + + ";" + std::to_string( mask_raw.row_index[ B_k ] ) + + " )\n" << std::flush; +#endif + continue; + } + + /* Increment the mask pointer until we find the right column, + * or a lower column (since the storage withing a row + * is sorted in a descending order) + */ + while( B_k < B_raw.col_start[ i + 1 ] + && B_raw.row_index[ B_k ] > j + ) { + B_k++; + } + + // Get B value (or identity if not found) + InputType B_val = B_identity; + if( B_k < B_raw.col_start[ i + 1 ] && B_raw.row_index[ B_k ] == j ) { +#ifdef _DEBUG + std::cout << "Found B value at: ( " + std::to_string( i ) + + ";" + std::to_string( B_raw.row_index[ B_k ] ) + + " )\n" << std::flush; +#endif + B_val = B_raw.values[ B_k ]; + B_k++; + } else { +#ifdef _DEBUG + std::cout << "Not found B, using identity: ( " + + std::to_string( i ) + ";" + std::to_string( j ) + + " ) = " + std::to_string( B_val ) + "\n" << std::flush; +#endif + } + + // Get A value + const IOType A_val = A_crs_raw.getValue( A_k, A_identity ); +#ifdef _DEBUG + std::cout << "A( " + std::to_string( i ) + ";" + + std::to_string( j ) + " ) = " + + std::to_string( A_val ) + "\n" << std::flush; +#endif + + // Compute the fold for this coordinate + IOType result; + local_rc = local_rc + ? local_rc + : apply< descr >( + result, A_val, B_val, op + ); + A_crs_raw.setValue( A_k, result ); + A_ccs_raw.setValue( A_k, result ); +#ifdef _DEBUG + std::cout << "Computing: op(" + std::to_string( A_val ) + + ", " + std::to_string( B_val ) + ") = " + + std::to_string( result ) + "\n" << std::flush; +#endif + } + } + + if( local_rc != SUCCESS ){ +#ifdef _H_GRB_REFERENCE_OMP_BLAS3 + #pragma omp critical +#endif + { // Reduction with the global return code + rc = rc ? rc : local_rc; + } + } + } + return rc; + } + } // namespace internal /** @@ -1326,6 +1648,194 @@ namespace grb { ); } + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, reference, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, reference, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, reference, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + // static checks + static_assert( !std::is_same< IOType, void >::value, + "grb::foldl ( reference, IOType <- op( IOType, InputType ): " + "the operator version of foldl cannot be used if the " + "scalar is of type void" + ); + static_assert( (std::is_same< typename Monoid::D1, IOType >::value), + "grb::foldl ( reference, IOType <- op( IOType, InputType ): " + "called with a prefactor input type that does not match the first domain of the given operator" + ); + static_assert( (std::is_same< typename Monoid::D2, InputType >::value), + "grb::foldr ( reference, IOType <- op( IOType, InputType ): " + "called with a prefactor input type that does not match the second domain of the given operator" + ); + static_assert( (std::is_same< typename Monoid::D3, IOType >::value), + "grb::foldl ( reference, IOType <- op( IOType, InputType ): " + "called with an output type that does not match the output domain of the given operator" + ); + +#ifdef _DEBUG + std::cout << "In grb::foldl( reference, matrix, mask, matrix, monoid )\n"; +#endif + + return internal::fold_matrix_matrix_masked_generic< descr, Monoid >( + A, mask, B, monoid + ); + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldl( + Matrix< IOType, reference, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, reference, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + // static checks + static_assert( !std::is_same< IOType, void >::value, + "grb::foldl ( reference, IOType <- op( IOType, InputType ): " + "the operator version of foldl cannot be used if the " + "scalar is of type void" + ); + static_assert( (std::is_same< typename Monoid::D1, IOType >::value), + "grb::foldl ( reference, IOType <- op( IOType, InputType ): " + "called with a prefactor input type that does not match the first domain of the given operator" + ); + static_assert( + (std::is_same< typename Monoid::D2, InputType >::value), + "grb::foldr ( reference, IOType <- op( IOType, InputType ): " + "called with a prefactor input type that does not match the second domain of the given operator" + ); + static_assert( (std::is_same< typename Monoid::D3, IOType >::value), + "grb::foldl ( reference, IOType <- op( IOType, InputType ): " + "called with an output type that does not match the output domain of the given operator" + ); + +#ifdef _DEBUG + std::cout << "In grb::foldl( reference, matrix, matrix, monoid )\n"; +#endif + + return internal::fold_matrix_matrix_unmasked_generic< descr, Monoid >( + A, B, monoid + ); + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename MaskType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_M, typename CIT_M, typename NIT_M, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, reference, RIT_A, CIT_A, NIT_A > &A, + const Matrix< MaskType, reference, RIT_M, CIT_M, NIT_M > &mask, + const Matrix< InputType, reference, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + !grb::is_object< MaskType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + // static checks + static_assert( !std::is_same< IOType, void >::value, + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "the operator version of foldr cannot be used if the " + "input matrix is a pattern matrix (of type void)" + ); + static_assert( (std::is_same< typename Monoid::D1, InputType >::value), + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "called with a prefactor input type that does not match the first domain of the given operator" + ); + static_assert( (std::is_same< typename Monoid::D2, IOType >::value), + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "called with a postfactor input type that does not match the second domain of the given operator" + ); + static_assert( (std::is_same< typename Monoid::D3, IOType >::value), + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "called with an output type that does not match the output domain of the given operator" + ); + +#ifdef _DEBUG + std::cout << "In grb::foldr( reference, matrix, mask, matrix, monoid )\n"; +#endif + + return internal::fold_matrix_matrix_masked_generic< descr, Monoid >( + A, mask, B, monoid + ); + } + + template< + Descriptor descr = descriptors::no_operation, + class Monoid, + typename IOType, typename InputType, + typename RIT_A, typename CIT_A, typename NIT_A, + typename RIT_B, typename CIT_B, typename NIT_B + > + RC foldr( + Matrix< IOType, reference, RIT_A, CIT_A, NIT_A > &A, + const Matrix< InputType, reference, RIT_B, CIT_B, NIT_B > &B, + const Monoid &monoid = Monoid(), + const typename std::enable_if< + !grb::is_object< IOType >::value && + !grb::is_object< InputType >::value && + grb::is_monoid< Monoid >::value, void + >::type * const = nullptr + ) { + // static checks + static_assert( !std::is_same< IOType, void >::value, + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "the operator version of foldr cannot be used if the " + "input matrix is a pattern matrix (of type void)" + ); + static_assert( (std::is_same< typename Monoid::D1, InputType >::value), + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "called with a prefactor input type that does not match the first domain of the given operator" + ); + static_assert( (std::is_same< typename Monoid::D2, IOType >::value), + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "called with a postfactor input type that does not match the second domain of the given operator" + ); + static_assert( (std::is_same< typename Monoid::D3, IOType >::value), + "grb::foldr ( reference, IOType <- op( InputType, IOType ): " + "called with an output type that does not match the output domain of the given operator" + ); + +#ifdef _DEBUG + std::cout << "In grb::foldr( reference, matrix, matrix, monoid )\n"; +#endif + + return internal::fold_matrix_matrix_unmasked_generic< descr, Monoid >( + A, B, monoid + ); + } + + } // namespace grb #undef NO_CAST_ASSERT diff --git a/src/graphblas/hyperdags/hyperdags.cpp b/src/graphblas/hyperdags/hyperdags.cpp index 6000f3af7..55cdaaea3 100644 --- a/src/graphblas/hyperdags/hyperdags.cpp +++ b/src/graphblas/hyperdags/hyperdags.cpp @@ -379,6 +379,18 @@ std::string grb::internal::hyperdags::toString( case GETID_MATRIX: return "getID( matrix )"; + + case FOLDL_MATRIX_MASK_MATRIX_MONOID: + return "foldl( matrix, mask, matrix, monoid )"; + + case FOLDL_MATRIX_MATRIX_MONOID: + return "foldl( matrix, matrix, monoid )"; + + case FOLDR_MATRIX_MASK_MATRIX_MONOID: + return "foldr( matrix, mask, matrix, monoid )"; + + case FOLDR_MATRIX_MATRIX_MONOID: + return "foldr( matrix, matrix, monoid )"; } assert( false ); diff --git a/tests/performance/bench_kernels.h b/tests/performance/bench_kernels.h index 8fea223b3..29901a1dc 100644 --- a/tests/performance/bench_kernels.h +++ b/tests/performance/bench_kernels.h @@ -62,7 +62,7 @@ void bench_kernels_axpy( const size_t n ); -/** +/** * Executes the inner-product computation \f$ alpha = (x,y) \f$ with \a x and * \a y vectors of length \a n. * diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index ab0dac498..32bc3b8a8 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -153,6 +153,10 @@ add_grb_executables( factories factories.cpp BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking ) +add_grb_executables( fold_matrix_to_matrix fold_matrix_to_matrix.cpp + BACKENDS reference reference_omp hyperdags bsp1d hybrid nonblocking +) + add_grb_executables( muladd muladd.cpp BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking ) diff --git a/tests/unit/fold_matrix_to_matrix.cpp b/tests/unit/fold_matrix_to_matrix.cpp new file mode 100644 index 000000000..10753fdc1 --- /dev/null +++ b/tests/unit/fold_matrix_to_matrix.cpp @@ -0,0 +1,328 @@ + +/* + * Copyright 2021 Huawei Technologies Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Tests for: + * - foldl+r( Matrix[in,out], Matrix[in], Monoid ) + * - foldl+r( Matrix[in,out], Mask[in], Matrix[in], Monoid ) + * + * @author Benjamin Lozes + * @date 27/05/2023 + * + * Tests whether the foldl and foldr API calls produce the expected results. + * + * The test cases are focused on the following aspects: + * * The types of the result, the matrix values and the operator + * * The initial value of the reduction result + * * The order of the operands (foldr, foldl) + */ + +#include +#include +#include +#include +#include +#include + +#include + +using namespace grb; + +constexpr bool SKIP_FOLDL = false; +constexpr bool SKIP_FOLDR = false; +constexpr bool SKIP_UNMASKED = false; +constexpr bool SKIP_MASKED = false; // Not implemented yet + +template< typename D > +bool are_matrices_equals( + const Matrix< D > & A, + const Matrix< D > & B +) { + if( nrows( A ) != nrows( B ) || ncols( A ) != ncols( B ) ) { return false ; } + + grb::wait( A ); + grb::wait( B ); + + std::vector< std::pair< std::pair< size_t, size_t >, D > > A_vec( A.cbegin(), A.cend() ); + std::vector< std::pair< std::pair< size_t, size_t >, D > > B_vec( B.cbegin(), B.cend() ); + return std::is_permutation( A_vec.cbegin(), A_vec.cend(), B_vec.cbegin() ); +} + +/** + * Structure for testing + * + */ +template< typename T, typename M, typename S, class MonoidFoldl, class MonoidFoldr > +struct input { + const char * test_label; + const char * test_description; + const Matrix< T > & initial; + const Matrix< M > & mask; + const Matrix< T > & B; + const Matrix< T > & expected; + const bool skip_masked, skip_unmasked; + const MonoidFoldl & monoidFoldl; + const MonoidFoldr & monoidFoldr; + + input( const char * test_label = "", + const char * test_description = "", + const Matrix< T > & initial = { 0, 0 }, + const Matrix< M > & mask = { 0, 0 }, + const Matrix< T > & B = { 0, 0 }, + const Matrix< T > & expected = { 0, 0 }, + bool skip_masked = false, + bool skip_unmasked = false, + const MonoidFoldl & monoidFoldl = MonoidFoldl(), + const MonoidFoldr & monoidFoldr = MonoidFoldr() ) : + test_label( test_label ), + test_description( test_description ), + initial( initial ), + mask( mask ), + B( B ), + expected( expected ), + skip_masked( skip_masked ), + skip_unmasked( skip_unmasked ), + monoidFoldl( monoidFoldl ), + monoidFoldr( monoidFoldr ) {} +}; + +template< typename T, typename M, typename S, class MonoidFoldl, class MonoidFoldr > +void grb_program( + const input< T, M, S, MonoidFoldl, MonoidFoldr > & in, + RC & rc +) { + rc = SUCCESS; + + // Unmasked variant foldl + if( !in.skip_unmasked && !SKIP_FOLDL && !SKIP_UNMASKED && rc == SUCCESS ) { + std::cout << "foldl( unmasked ) \"" << in.test_label << "\": "; + + Matrix< T > result = in.initial; + rc = rc ? rc : foldl( result, in.B, in.monoidFoldl ); + if( rc == SUCCESS ) { + std::cout << "Execution OK" << std::flush << std::endl; + } else { + std::cerr << "Execution failed - " << std::endl << in.test_description << std::endl; + } + rc = rc ? rc : ( are_matrices_equals( result, in.expected ) ? SUCCESS : FAILED ); + if( rc == SUCCESS ) { + std::cout << "Check OK" << std::flush << std::endl; + } else { + std::cerr << "Check failed - " << std::endl << in.test_description << std::endl; + } + } + + // Masked variant foldl + if( !in.skip_masked && !SKIP_FOLDL && !SKIP_MASKED && rc == SUCCESS ) { + std::cout << "foldl( masked ) \"" << in.test_label << "\": "; + + Matrix< T > result = in.initial; + rc = rc ? rc : foldl( result, in.mask, in.B, in.monoidFoldl ); + if( rc == SUCCESS ) { + std::cout << "Execution OK" << std::flush << std::endl; + } else { + std::cerr << "Execution failed - " << std::endl << in.test_description << std::endl; + } + + rc = rc ? rc : ( are_matrices_equals( result, in.expected ) ? SUCCESS : FAILED ); + if( rc == SUCCESS ) { + std::cout << "Check OK" << std::flush << std::endl; + } else { + std::cerr << "Check failed - " << std::endl << in.test_description << std::endl; + } + } + + // Unmasked variant foldr + if( !in.skip_unmasked && !SKIP_FOLDR && !SKIP_UNMASKED && rc == SUCCESS ) { + std::cout << "foldr( unmasked ) \"" << in.test_label << "\": "; + + Matrix< T > result = in.initial; + rc = rc ? rc : foldr( result, in.B, in.monoidFoldr ); + if( rc == SUCCESS ) { + std::cout << "Execution OK" << std::flush << std::endl; + } else { + std::cerr << "Execution failed - " << std::endl << in.test_description << std::endl; + } + + rc = rc ? rc : ( are_matrices_equals( result, in.expected ) ? SUCCESS : FAILED ); + if( rc == SUCCESS ) { + std::cout << "Check OK" << std::flush << std::endl; + } else { + std::cerr << "Check failed - " << std::endl << in.test_description << std::endl; + } + } + + // Masked variant foldr + if( !in.skip_masked && !SKIP_FOLDR && !SKIP_MASKED && rc == SUCCESS ) { + std::cout << "foldr( masked ) \"" << in.test_label << "\": "; + + Matrix< T > result = in.initial; + rc = rc ? rc : foldr( result, in.mask, in.B, in.monoidFoldr ); + if( rc == SUCCESS ) { + std::cout << "Execution OK" << std::flush << std::endl; + } else { + std::cerr << "Execution failed - " << std::endl << in.test_description << std::endl; + } + + rc = rc ? rc : ( are_matrices_equals( result, in.expected ) ? SUCCESS : FAILED ); + if( rc == SUCCESS ) { + std::cout << "Check OK" << std::flush << std::endl; + } else { + std::cerr << "Check failed - " << std::endl << in.test_description << std::endl; + } + } +} + +int main( int argc, char ** argv ) { + // defaults + bool printUsage = false; + size_t n = 10; + + // error checking + if( argc > 2 ) { + printUsage = true; + } + if( argc == 2 ) { + n = std::atol( argv[ 1 ] ); + } + if( printUsage ) { + std::cerr << "Usage: " << argv[ 0 ] << " [n]\n"; + std::cerr << " -n (optional, default is 10): an even integer, the test " + << "size.\n"; + return 1; + } + + std::cout << "This is functional test " << argv[ 0 ] << "\n"; + grb::Launcher< AUTOMATIC > launcher; + RC rc = SUCCESS; + + // Identity matrix: I + Matrix< int > I( n, n ); + std::vector< size_t > I_coords( n ); + std::vector< int > I_vals( n, 1 ); + std::iota( I_coords.begin(), I_coords.end(), 0 ); + if( SUCCESS != + buildMatrixUnique( I, I_coords.data(), I_coords.data(), I_vals.data(), I_vals.size(), SEQUENTIAL ) + ) { + std::cerr << "Error building identity matrix" << std::endl; + rc = FAILED; + return 1; + } + + { // Test 01: I *. I -> I + const std::string label( "Test 01" ); + const std::string description( "A: Identity int [" + std::to_string( n ) + ";" + + std::to_string( n ) + "]\n" + + "Mask: Identity void matrix (matching the input).\n" + + "B: Identity int [" + std::to_string( n ) + ";" + + std::to_string( n ) + "]\n" + "Operator: mul\n" + + "Expected: Identity int [" + std::to_string( n ) + + ";" + std::to_string( n ) + "]" ); + // Mask: Pattern identity + Matrix< void > mask( n, n ); + if( SUCCESS != + buildMatrixUnique( mask, I_coords.data(), I_coords.data(), I_coords.size(), SEQUENTIAL ) + ) { + throw std::runtime_error( + "[Line " + std::to_string(__LINE__) + "]: Error building mask" ); + } + // B: Identity + Matrix< int > B = I; + // Expected matrix: Identity + Matrix< int > expected = I; + // Run test + std::cout << "-- Running " << label << " --" << std::endl; + input< + int, + void, + int, + Monoid< operators::mul< int >, identities::one >, + Monoid< operators::mul< int >, identities::one > + > in { + label.c_str(), + description.c_str(), + I, + mask, + B, + expected + }; + if( launcher.exec( &grb_program, in, rc, true ) != SUCCESS ) { + std::cerr << "Launching " << label << " failed" << std::endl; + return 255; + } + std::cout << std::endl << std::flush; + } + + { // Test 01: I +. I -> 2 * I + const std::string label( "Test 01" ); + const std::string description( "A: Identity int [" + std::to_string( n ) + ";" + + std::to_string( n ) + "]\n" + + "Mask: Identity void matrix (matching the input).\n" + + "B: Identity int [" + std::to_string( n ) + ";" + + std::to_string( n ) + "]\n" + "Operator: add\n" + + "Expected: Identity int [" + std::to_string( n ) + + ";" + std::to_string( n ) + "] * 2" ); + // Mask: Pattern identity + Matrix< void > mask( n, n ); + if( SUCCESS != + buildMatrixUnique( mask, I_coords.data(), I_coords.data(), I_coords.size(), SEQUENTIAL ) + ) { + throw std::runtime_error( + "[Line " + std::to_string(__LINE__) + "]: Error building mask" ); + } + // B: Identity + Matrix< int > B = I; + // Expected matrix: Identity * 2 + Matrix< int > expected( n, n ); + std::vector< int > expected_vals( n, 2 ); + if( SUCCESS != + buildMatrixUnique( expected, I_coords.data(), I_coords.data(), expected_vals.data(), expected_vals.size(), SEQUENTIAL ) + ) { + throw std::runtime_error( + "[Line " + std::to_string(__LINE__) + "]: Error building mask" ); + } + // Run test + std::cout << "-- Running " << label << " --" << std::endl; + input< + int, + void, + int, + Monoid< operators::add< int >, identities::zero >, + Monoid< operators::add< int >, identities::zero > + > in { + label.c_str(), + description.c_str(), + I, + mask, + B, + expected + }; + if( launcher.exec( &grb_program, in, rc, true ) != SUCCESS ) { + std::cerr << "Launching " << label << " failed" << std::endl; + return 255; + } + std::cout << std::endl << std::flush; + } + + if( rc != SUCCESS ) { + std::cout << "Test FAILED (" << grb::toString( rc ) << ")" << std::endl; + return rc; + } else { + std::cout << "Test OK" << std::endl; + return 0; + } +} diff --git a/tests/unit/unittests.sh b/tests/unit/unittests.sh index 5b7dfc16a..e668e06f5 100755 --- a/tests/unit/unittests.sh +++ b/tests/unit/unittests.sh @@ -280,6 +280,13 @@ for MODE in ${MODES}; do grep 'Test OK' ${TEST_OUT_DIR}/ewiseapply_large_${MODE}_${BACKEND}_${P}_${T} || echo "Test FAILED" echo " " + echo ">>> [x] [x] Testing grb::foldl and grb::foldr reducing sparse" + echo " matrix in-place using an operator." + $runner ${TEST_BIN_DIR}/fold_matrix_to_matrix_${MODE}_${BACKEND} ${P} &> ${TEST_OUT_DIR}/fold_matrix_to_matrix_${MODE}_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/fold_matrix_to_matrix_${MODE}_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/fold_matrix_to_matrix_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + echo " " + echo ">>> [x] [x] Testing grb::foldl and grb::foldr reducing dense" echo " vectors into scalars using operators and monoids." $runner ${TEST_BIN_DIR}/fold_to_scalar_${MODE}_${BACKEND} ${P} &> ${TEST_OUT_DIR}/fold_to_scalar_${MODE}_${BACKEND}_${P}_${T}.log