diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp new file mode 100644 index 000000000..b3a92c0ed --- /dev/null +++ b/include/graphblas/algorithms/bfs.hpp @@ -0,0 +1,498 @@ + +/* + * Copyright 2023 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. + */ + +/** + * @file + * + * Implements Breadth-First-Search (BFS) algorithm. + * + * Two versions are provided: + * - bfs_levels: Computes the first level at which each vertex is reached. + * - bfs_parents: Computes (one of) the parents of each vertex. + * + * @author B. Lozes + * @date: May 26th, 2023 + */ + +#ifndef _H_GRB_BFS +#define _H_GRB_BFS + +#include +#include +#include +#include + +#include + +#include + +// #define _DEBUG + +namespace grb { + + namespace algorithms { + + namespace utils { + template< class Iterator > + void printSparseMatrixIterator( + size_t rows, + size_t cols, + Iterator begin, + Iterator end, + const std::string &name = "", + std::ostream &os = std::cout + ) { + (void)rows; + (void)cols; + (void)begin; + (void)end; + (void)name; + (void)os; + +#ifdef _DEBUG + if( rows > 64 || cols > 64 ) { + return; + } + std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; + // os.precision( 3 ); + for( size_t y = 0; y < rows; y++ ) { + os << std::string( 6, ' ' ); + for( size_t x = 0; x < cols; x++ ) { + auto nnz_val = std::find_if( begin, end, [ y, x ]( const typename std::iterator_traits< Iterator >::value_type &a ) { + return a.first.first == y && a.first.second == x; + } ); + if( nnz_val != end ) + os << std::fixed << ( *nnz_val ).second; + else + os << '_'; + os << " "; + } + os << std::endl; + } + os << "]" << std::endl; +#endif + } + + template< class Iterator > + void printSparsePatternMatrixIterator( + size_t rows, + size_t cols, + Iterator begin, + Iterator end, + const std::string &name = "", + std::ostream &os = std::cout + ) { + (void)rows; + (void)cols; + (void)begin; + (void)end; + (void)name; + (void)os; + +#ifdef _DEBUG + if( rows > 64 || cols > 64 ) { + return; + } + std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; + // os.precision( 3 ); + for( size_t y = 0; y < rows; y++ ) { + os << std::string( 3, ' ' ); + for( size_t x = 0; x < cols; x++ ) { + auto nnz_val = std::find_if( + begin, + end, + [ y, x ]( const typename std::iterator_traits< Iterator >::value_type &a ) + { + return a.first == y && a.second == x; + } + ); + if( nnz_val != end ) + os << "X"; + else + os << '_'; + os << " "; + } + os << std::endl; + } + os << "]" << std::endl; +#endif + } + + template< typename D > + void printSparseMatrix( const Matrix< D > &mat, const std::string &name ) { + (void)mat; + (void)name; +#ifdef _DEBUG + wait( mat ); + printSparseMatrixIterator( + nrows( mat ), ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout + ); +#endif + } + + template<> + void printSparseMatrix< void >( const Matrix< void > &mat, const std::string &name ) { + (void)mat; + (void)name; +#ifdef _DEBUG + wait( mat ); + printSparsePatternMatrixIterator( + nrows( mat ), ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout + ); +#endif + } + + template< typename D > + void printSparseVector( const Vector< D > &v, const std::string &name ) { + (void)v; + (void)name; +#ifdef _DEBUG + if( size( v ) > 64 ) { + return; + } + wait( v ); + std::cout << " [ "; + if( nnz( v ) <= 0 ) { + for( size_t i = 0; i < size( v ); i++ ) + std::cout << "_ "; + } else { + size_t nnz_idx = 0; + auto it = v.cbegin(); + for( size_t i = 0; i < size( v ); i++ ) { + if( nnz_idx < nnz( v ) && i == it->first ) { + std::cout << it->second << " "; + nnz_idx++; + if( nnz_idx < nnz( v ) ) + ++it; + } else { + std::cout << "_ "; + } + } + } + std::cout << "] - " + << "Vector \"" << name << "\" (" << size( v ) << ")" << std::endl; +#endif + } + + template< typename T > + void printStdVector( const std::vector< T > &vector, const std::string &name ) { + (void)vector; + (void)name; +#ifdef _DEBUG + if( vector.size() > 64 ) { + return; + } + std::cout << " [ "; + for( const T &e : vector ) + std::cout << e << " "; + std::cout << "] - " + << "Vector \"" << name << "\" (" << vector.size() << ")" << std::endl; +#endif + } + } // namespace utils + + /** + * @brief Breadth-first search (BFS) algorithm. + */ + enum BFS { LEVELS, PARENTS }; + + + /** + * Breadth-first search (BFS) algorithm. + * This version computes the first level at which each vertex is reached. + * + * @tparam D Matrix values type + * @tparam T Level type + * @tparam BoolSemiring Boolean semiring type + * @tparam MinMonoid Minimum monoid type + * @tparam SetFalseMonoid Assign to false monoid type + * + * @param[in] A Matrix to explore + * @param[in] root Root vertex from which to start the exploration + * @param[out] explored_all Whether all vertices have been explored + * @param[out] max_level Maximum level reached by the BFS algorithm + * @param[out] levels Vector containing the lowest levels at which each vertex is reached. + * Needs to be pre-allocated with nrows(A) values. + * @param[in] x Buffer vector, needs to be pre-allocated with 1 value. + * @param[in] y Buffer vector, no pre-allocation needed. + * @param[in] not_visited Buffer vector, needs to be pre-allocated with nrows(A) values. + * @param[in] max_iterations Max number of iterations to perform (default: -1, no limit) + * + * \parblock + * \par Possible output values: + * -# max_level: [0, nrows(A) - 1] + * -# levels: [0, nrows(A) - 1] for each reached vertices, + * empty for unreached vertices + * \endparblock + * + * \note Values of the matrix A are ignored, + * hence it is recommended to use a pattern matrix. + */ + template< + typename D = void, + typename T = size_t, + class BoolSemiring = Semiring< + operators::logical_or< bool >, + operators::logical_and< bool >, + identities::logical_false, + identities::logical_true + >, + class MinMonoid = Monoid< + operators::min< T >, + identities::infinity + >, + class SetFalseMonoid = Monoid< + operators::logical_nand< bool >, + identities::logical_false + > + > + RC bfs_levels( + const Matrix< D > &A, + const size_t root, + bool &explored_all, + T &max_level, + Vector< T > &levels, + Vector< bool > &x, + Vector< bool > &y, + Vector< bool > ¬_visited, + const long max_iterations = -1L, + const BoolSemiring bool_semiring = BoolSemiring(), + const MinMonoid min_monoid = MinMonoid(), + const SetFalseMonoid not_visited_monoid = SetFalseMonoid(), + const std::enable_if< + is_semiring< BoolSemiring >::value + && is_monoid< MinMonoid >::value, + void + >* = nullptr + ) { + max_level = 0; + explored_all = false; + + RC rc = SUCCESS; + const size_t nvertices = nrows( A ); + + // Frontier vectors + rc = rc ? rc : setElement( x, true, root ); + + utils::printSparseMatrix( A, "A" ); + utils::printSparseVector( x, "x" ); + + // Output vector containing the minimum level at which each vertex is reached + rc = rc ? rc : setElement( levels, static_cast< T >( 0 ), root ); + utils::printSparseVector( levels, "levels" ); + + // Vector of unvisited vertices + rc = rc ? rc : set( not_visited, true ); + rc = rc ? rc : setElement( not_visited, false, root ); + + size_t max_iter = max_iterations < 0 ? nvertices : max_iterations; + for( size_t level = 1; level <= max_iter; level++ ) { +#ifdef _DEBUG + std::cout << "** Level " << level << ":" << std::endl << std::flush; +#endif + // Multiply the current frontier by the adjacency matrix + utils::printSparseVector( x, "x" ); + utils::printSparseVector( not_visited, "not_visited" ); + rc = rc ? rc : clear( y ); + rc = rc ? rc : vxm( y, not_visited, x, A, bool_semiring, Phase::RESIZE ); + rc = rc ? rc : vxm( y, not_visited, x, A, bool_semiring, Phase::EXECUTE ); + utils::printSparseVector( y, "y" ); + + // Update not_visited vector + rc = rc ? rc : foldl( not_visited, y, not_visited_monoid, Phase::RESIZE ); + rc = rc ? rc : foldl( not_visited, y, not_visited_monoid, Phase::EXECUTE ); + + // Assign the current level to the newly discovered vertices only + rc = rc ? rc : foldl( levels, y, level, min_monoid, Phase::RESIZE ); + rc = rc ? rc : foldl( levels, y, level, min_monoid, Phase::EXECUTE ); + utils::printSparseVector( levels, "levels" ); + + // Check if all vertices have been discovered, equivalent of an std::all on the frontier + explored_all = nnz( levels ) == nvertices; + if( explored_all ) { + max_level = level; + // If all vertices are discovered, stop +#ifdef _DEBUG + std::cout << "Explored " << level << " levels to discover all of the " + << nvertices << " vertices.\n" << std::flush; +#endif + return rc; + } + bool can_continue = nnz( y ) > 0; + if( !can_continue ) { + max_level = level - 1; + // If no new vertices are discovered, stop +#ifdef _DEBUG + std::cout << "Explored " << level << " levels to discover " + << nnz( levels ) << " vertices.\n" << std::flush; +#endif + break; + } + + // Swap the frontier, avoid a copy + std::swap( x, y ); + } + + // Maximum number of iteration passed, not every vertex has been discovered +#ifdef _DEBUG + std::cout << "A full exploration is not possible on this graph. " + << "Some vertices are not reachable from the given root: " + << root << std::endl << std::flush; +#endif + return rc; + } + + /** + * Breadth-first search (BFS) algorithm. + * This version computes the parents of each vertex. + * + * @tparam D Matrix values type + * @tparam T Level type + * @tparam MinAddSemiring Boolean semiring type + * @tparam MinMonoid Minimum monoid type + * + * @param[in] A Matrix to explore + * @param[in] root Root vertex from which to start the exploration + * @param[out] explored_all Whether all vertices have been explored + * @param[out] max_level Maximum level reached by the BFS algorithm + * @param[out] parents Vector containing the parent from which each vertex is reached. + * Needs to be pre-allocated with nrows(A) values. + * @param[in] x Buffer vector, needs to be pre-allocated with 1 value. + * @param[in] y Buffer vector, no pre-allocation needed. + * @param[in] max_iterations Max number of iterations to perform + * (default: -1 <=> no limit) + * @param[in] not_find_value Value to use for vertices that have + * not been reached (default: -1) + * + * \parblock + * \par Possible output values: + * -# max_level: [0, nrows(A) - 1] + * -# parents: [0, nrows(A) - 1] for reached vertices, + * not_find_value for unreached vertices + * \endparblock + * + * \warning Parent type T must be a signed integer type. + * + * \note Values of the matrix A are ignored, + * hence it is recommended to use a pattern matrix. + */ + template< + typename D = void, + typename T = long, + class MinAddSemiring = Semiring< + operators::min< T >, + operators::add< T >, + identities::infinity, + identities::zero + >, + class MaxMonoid = Monoid< + operators::max< T >, + identities::negative_infinity + >, + class MinNegativeMonoid = Monoid< + operators::min< T >, + identities::zero + > + > + RC bfs_parents( + const Matrix< D > &A, + const size_t root, + bool &explored_all, + T &max_level, + Vector< T > &parents, + Vector< T > &x, + Vector< T > &y, + const long max_iterations = -1L, + const T not_find_value = static_cast< T >( -1 ), + const MinAddSemiring semiring = MinAddSemiring(), + const MaxMonoid max_monoid = MaxMonoid(), + const MinNegativeMonoid min_negative_monoid = MinNegativeMonoid(), + const std::enable_if< + std::is_arithmetic< T >::value + && std::is_signed::value + && is_semiring< MinAddSemiring >::value + && is_monoid< MaxMonoid >::value + && is_monoid< MinNegativeMonoid >::value, + void + >* = nullptr + ) { + RC rc = SUCCESS; + const size_t nvertices = nrows( A ); + utils::printSparseMatrix( A, "A" ); + + assert( size( x ) == nvertices ); + assert( size( y ) == nvertices ); + assert( capacity( x ) >= 1 ); + assert( capacity( y ) >= 0 ); + + rc = rc ? rc : setElement( x, root, root ); + utils::printSparseVector( x, "x" ); + utils::printSparseVector( y, "y" ); + + assert( size(parents) == nvertices ); + assert( capacity(parents) >= nvertices ); + rc = rc ? rc : set( parents, not_find_value ); + rc = rc ? rc : setElement( parents, root, root ); + utils::printSparseVector( parents, "parents" ); + + size_t max_iter = max_iterations < 0 ? nvertices : max_iterations; + max_level = 0; + explored_all = false; + for( size_t level = 1; level <= max_iter; level++ ) { +#ifdef _DEBUG + std::cout << "** Level " << level << ":" << std::endl << std::flush; +#endif + max_level = level; + + rc = rc ? rc : clear( y ); + rc = rc ? rc + : eWiseLambda( [ &x ]( const size_t i ) { + x[ i ] = i; + }, x ); + utils::printSparseVector( x, "x - after indexing" ); + + rc = rc ? rc : vxm( y, x, A, semiring, Phase::RESIZE ); + rc = rc ? rc : vxm( y, x, A, semiring, Phase::EXECUTE ); + utils::printSparseVector( y, "y - after vxm" ); + + rc = rc ? rc : foldl( parents, y, max_monoid, Phase::RESIZE ); + rc = rc ? rc : foldl( parents, y, max_monoid, Phase::EXECUTE ); + utils::printSparseVector( parents, "parents" ); + + T min_parent = std::numeric_limits< T >::max(); + rc = rc ? rc : foldl( min_parent, parents, min_negative_monoid ); + if( min_parent > not_find_value ) { + explored_all = true; +#ifdef _DEBUG + std::cout << "Explored " << level << " levels to discover all of the " + << nvertices << " vertices.\n" << std::flush; +#endif + break; + } + + // Swap the frontier, avoid a copy + std::swap( x, y ); + } + + return rc; + } + + } // namespace algorithms + +} // namespace grb + +#endif // _H_GRB_BFS diff --git a/include/graphblas/base/internalops.hpp b/include/graphblas/base/internalops.hpp index 6c2df0f5c..637bf1dd6 100644 --- a/include/graphblas/base/internalops.hpp +++ b/include/graphblas/base/internalops.hpp @@ -42,6 +42,120 @@ namespace grb { /** Core implementations of the standard operators in #grb::operators. */ namespace internal { + /** + * Standard negation operator. + * + * Assumes native availability of ! on the given data types or assumes that + * the relevant operators are properly overloaded. + * + * @tparam Op The Operator class to negate. + * Requires the following typedefs: + * - \b D1: The left-hand input domain. + * - \b D2: The right-hand input domain. + * - \b D3: The output domain. + * - \b operator_type: The internal::operator type to negate. + */ + template< + class Op, + enum Backend implementation = config::default_backend + > + class logical_not { + public: + + /** Alias to the left-hand input data type. */ + typedef typename Op::D1 left_type; + + /** Alias to the right-hand input data type. */ + typedef typename Op::D2 right_type; + + /** Alias to the output data type. */ + typedef typename Op::D3 result_type; + + /** Whether this operator has an inplace foldl. */ + static constexpr bool has_foldl = Op::operator_type::has_foldl; + + /** Whether this operator has an inplace foldr. */ + static constexpr bool has_foldr = Op::operator_type::has_foldr; + + /** + * Whether this operator is \em mathematically associative; that is, + * associative when assuming equivalent data types for \a IN1, \a IN2, + * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. + */ + static constexpr bool is_associative = Op::operator_type::is_associative; + + /** + * Whether this operator is \em mathematically commutative; that is, + * commutative when assuming equivalent data types for \a IN1, \a IN2, + * and \a OUT, as well as assuming exact arithmetic, no overflows, etc. + */ + static constexpr bool is_commutative = Op::operator_type::is_commutative; + + /** + * Out-of-place application of the operator. + * + * @param[in] a The left-hand side input. Must be pre-allocated and + * initialised. + * @param[in] b The right-hand side input. Must be pre-allocated and + * initialised. + * @param[out] c The output. Must be pre-allocated. + */ + static void apply( + const left_type * __restrict__ const a, + const right_type * __restrict__ const b, + result_type * __restrict__ const c, + const typename std::enable_if< + std::is_convertible< result_type, bool >::value, + void + >::type * = nullptr + ) { + Op::operator_type::apply( a, b, c ); + *c = !*c; + } + + /** + * In-place left-to-right folding. + * + * @param[in] a Pointer to the left-hand side input data. + * @param[in,out] c Pointer to the right-hand side input data. This also + * dubs as the output memory area. + */ + static void foldr( + const left_type * __restrict__ const a, + result_type * __restrict__ const c, + const typename std::enable_if< + std::is_convertible< result_type, bool >::value, + void + >::type * = nullptr + ) { + Op::operator_type::foldr( a, c ); + *c = !*c; + } + + /** + * In-place right-to-left folding. + * + * @param[in,out] c Pointer to the left-hand side input data. This also + * dubs as the output memory area. + * @param[in] b Pointer to the right-hand side input data. + */ + static void foldl( + result_type * __restrict__ const c, + const right_type * __restrict__ const b, + const typename std::enable_if< + std::is_convertible< result_type, bool >::value, + void + >::type * = nullptr + ) { + Op::operator_type::foldl( c, b ); + *c = !*c; + } + }; + + template< class Op > + class not_op : public logical_not< Op > {}; + + /** * Standard argmin operator. * @@ -4179,6 +4293,9 @@ namespace grb { /** The output domain of this operator. */ typedef typename OperatorBase< OP >::D3 D3; + /** The type of the operator OP. */ + typedef OP operator_type; + /** * Reduces a vector of type \a InputType into a value in \a IOType * by repeated application of this operator. The \a IOType is cast diff --git a/include/graphblas/ops.hpp b/include/graphblas/ops.hpp index e5ff732d5..ed677483f 100644 --- a/include/graphblas/ops.hpp +++ b/include/graphblas/ops.hpp @@ -39,6 +39,30 @@ namespace grb { */ namespace operators { + /** + * This operator discards all right-hand side input and simply copies the + * left-hand side input to the output variable. It exposes the complete + * interface detailed in grb::operators::internal::Operator. This operator + * can be passed to any GraphBLAS function or object constructor. + * + * Mathematical notation: \f$ \odot(x,y)\ \to\ x \f$. + * + * @tparam Op The operator to negate. + */ + template< + class Op + > + class logical_not : public internal::Operator< internal::logical_not< Op > > { + + public: + + template< class A > + using GenericOperator = logical_not< A >; + + logical_not() {} + + }; + /** * This operator discards all right-hand side input and simply copies the * left-hand side input to the output variable. It exposes the complete @@ -499,6 +523,22 @@ namespace grb { logical_and() {} }; + template< + typename D1, typename D2 = D1, typename D3 = D2, + enum Backend implementation = config::default_backend + > + class logical_nand : public logical_not< + logical_and< D1, D2, D3, implementation > + > { + + public: + + template< typename A, typename B, typename C, enum Backend D > + using GenericOperator = logical_nand< A, B, C, D >; + + logical_nand() {} + }; + /** * This operation is equivalent to #grb::operators::min. * @@ -981,6 +1021,11 @@ namespace grb { } // namespace operators + template< class Op > + struct is_operator< operators::logical_not< Op > > { + static const constexpr bool value = is_operator< Op >::value; + }; + template< typename D1, typename D2, typename D3, enum Backend implementation > struct is_operator< operators::left_assign_if< D1, D2, D3, implementation > > { static const constexpr bool value = true; @@ -1063,6 +1108,11 @@ namespace grb { static const constexpr bool value = true; }; + template< typename D1, typename D2, typename D3, enum Backend implementation > + struct is_operator< operators::logical_nand< D1, D2, D3, implementation > > { + static const constexpr bool value = true; + }; + template< typename D1, typename D2, typename D3, enum Backend implementation > struct is_operator< operators::abs_diff< D1, D2, D3, implementation > > { static const constexpr bool value = true; diff --git a/tests/smoke/CMakeLists.txt b/tests/smoke/CMakeLists.txt index 1f99446ee..2ddd8d334 100644 --- a/tests/smoke/CMakeLists.txt +++ b/tests/smoke/CMakeLists.txt @@ -93,6 +93,11 @@ add_grb_executables( from_mpi_launch_simple_pagerank_broadcast simple_pagerank_b ADDITIONAL_LINK_LIBRARIES MPI::MPI_CXX ) +add_grb_executables( bfs bfs.cpp + BACKENDS reference reference_omp hyperdags nonblocking + ADDITIONAL_LINK_LIBRARIES test_utils +) + add_grb_executables( knn knn.cpp ../unit/parser.cpp BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking ) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp new file mode 100644 index 000000000..1130000dc --- /dev/null +++ b/tests/smoke/bfs.cpp @@ -0,0 +1,254 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include + +#include + +using namespace grb; + +template< typename T > +bool verify_parents( const Matrix< void > & A, Vector< T > parents ) { + for( const std::pair< size_t, T > & e : parents ) { + if( e.second < 0 ) // Not found node + continue; + + if( ( (size_t)e.second ) == e.first ) // Root ndoe + continue; + + bool ok = std::any_of( + A.cbegin(), + A.cend(), + [ e ]( const std::pair< size_t, size_t > pos ) { + return pos.first == ( (size_t)e.second ) && pos.second == e.first; + } + ); + + if( not ok ) { + std::cerr << "ERROR: parent " << e.second << " of node " + << e.first << " is not a valid edge" << std::endl; + return false; + } + } + return true; +} + +struct input_t { + // Input file parameters + std::string filename; + bool direct; + // Algorithm parameters + algorithms::BFS algorithm; + size_t root; + bool expected_explored_all; + long expected_max_level; + bool verify = false; + const Vector< long > & expected_values; // Levels or parents depending on the selected algorithm + + // Necessary for distributed backends + input_t( const std::string & filename = "", + bool direct = true, + algorithms::BFS algorithm = algorithms::BFS::LEVELS, + size_t root = 0, + bool expected_explored_all = true, + long expected_max_level = 0, + const Vector< long > & expected_values = { 0 } ) : + filename( filename ), + direct( direct ), + algorithm( algorithm ), + root( root ), + expected_explored_all( expected_explored_all ), + expected_max_level( expected_max_level ), + expected_values( expected_values ) {} +}; + +struct output_t { + RC rc = RC::SUCCESS; + utils::TimerResults times; + size_t data_in_local; +}; + +void grbProgram( const struct input_t & input, struct output_t & output ) { + utils::Timer timer; + long max_level; + bool explored_all; + + // Read matrix from file as a pattern matrix (i.e. no values) + timer.reset(); + utils::MatrixFileReader< void > reader( input.filename, input.direct ); + size_t r = reader.n(), c = reader.m(); + assert( r == c ); + Matrix< void > A( r, c ); + output.rc = buildMatrixUnique( + A, reader.cbegin( IOMode::SEQUENTIAL ), reader.cend( IOMode::SEQUENTIAL ), IOMode::SEQUENTIAL + ); + if( output.rc != RC::SUCCESS ) { + std::cerr << "ERROR during buildMatrixUnique of the pattern matrix: " + << toString( output.rc ) << std::endl; + return; + } + output.times.io = timer.time(); + + // Allocate output vector + timer.reset(); + Vector< long > values( nrows( A ) ); + output.times.preamble = timer.time(); + + switch( input.algorithm ) { + case algorithms::BFS::LEVELS: { + // BFS::LEVELS specific allocations + timer.reset(); + Vector< bool > x( nrows( A ), 1UL ); + Vector< bool > y( nrows( A ), 0UL ); + Vector< bool > not_visited( nrows( A ) ); + output.times.preamble += timer.time(); + + // Run the algorithm + timer.reset(); + output.rc = output.rc + ? output.rc + : algorithms::bfs_levels( A, input.root, explored_all, max_level, values, x, y, not_visited ); + grb::wait(); + output.times.useful = timer.time(); + + break; + } + case algorithms::BFS::PARENTS: { + + // BFS::PARENTS specific allocations + timer.reset(); + Vector< long > x( nrows( A ), 1UL ); + Vector< long > y( nrows( A ), 0UL ); + output.times.preamble += timer.time(); + + // Run the algorithm + timer.reset(); + output.rc = output.rc + ? output.rc + : algorithms::bfs_parents( A, input.root, explored_all, max_level, values, x, y ); + grb::wait(); + output.times.useful = timer.time(); + + break; + } + default: { + std::cerr << "ERROR: unknown algorithm" << std::endl; + output.rc = RC::ILLEGAL; + return; + } + } + + { // Check the outputs + if( explored_all == input.expected_explored_all ) { + std::cout << "SUCCESS: explored_all = " << explored_all << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected explored_all = " + << input.expected_explored_all << " but got " << explored_all << std::endl; + output.rc = output.rc ? output.rc : RC::FAILED; + } + + if( max_level > 0 && max_level <= input.expected_max_level ) { + std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected max_level " + << input.expected_max_level << " but got " << max_level << std::endl; + output.rc = output.rc ? output.rc : RC::FAILED; + } + + // Check levels by comparing it with the expected one + if( input.verify + && not std::equal( input.expected_values.cbegin(), input.expected_values.cend(), values.cbegin() ) + ) { + std::cerr << "FAILED: values are incorrect" << std::endl; + std::cerr << "values != expected_values" << std::endl; + output.rc = output.rc ? output.rc : RC::FAILED; + } + + if( output.rc == RC::SUCCESS + && input.algorithm == algorithms::BFS::PARENTS + ) { + bool correct = verify_parents( A, values ); + std::cout << "CHECK - parents are correct is: " << std::to_string( correct ) << std::endl; + } + } +} + +int main( int argc, char ** argv ) { + (void)argc; + (void)argv; + + size_t inner_iterations = 1, outer_iterations = 1; + Benchmarker< EXEC_MODE::AUTOMATIC > benchmarker; + + if( argc != 6 ) { + std::cerr << "Usage: \n\t" << argv[ 0 ] + << " " + << " [ outer_iters=1 inner_iters=1 ]" << std::endl; + return 1; + } + std::cout << "Test executable: " << argv[ 0 ] << std::endl; + + std::string file_to_test( argv[ 1 ] ); + bool direct = ( std::string( argv[ 2 ] ) == "direct" ); + size_t root = std::stoul( argv[ 3 ] ); + bool expected_explored_all = std::stol( argv[ 4 ] ) > 0; + long expected_max_level = std::stol( argv[ 5 ] ); + if( argc > 6 ) + outer_iterations = std::stoul( argv[ 6 ] ); + if( argc > 7 ) + inner_iterations = std::stoul( argv[ 7 ] ); + + { // Run the test: BFS::LEVELS + std::cout << "-- Running BFS::LEVELS on file " << file_to_test << std::endl; + input_t input( + file_to_test, direct, algorithms::BFS::LEVELS, root, expected_explored_all, expected_max_level + ); + output_t output; + RC rc = benchmarker.exec( &grbProgram, input, output, inner_iterations, outer_iterations, true ); + if( rc ) { + std::cerr << "ERROR during execution: rc = " << toString( rc ) << std::endl; + return rc; + } + if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + } + { // Run the test: BFS::PARENTS + std::cout << "-- Running BFS::PARENTS on file " << file_to_test << std::endl; + input_t input( + file_to_test, direct, algorithms::BFS::PARENTS, root, expected_explored_all, expected_max_level + ); + output_t output; + RC rc = benchmarker.exec( &grbProgram, input, output, inner_iterations, outer_iterations, true ); + if( rc ) { + std::cerr << "ERROR during execution: rc = " << toString( rc ) << std::endl; + return rc; + } + if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + } + + std::cout << "Test OK" << std::endl; + return 0; +} diff --git a/tests/smoke/smoketests.sh b/tests/smoke/smoketests.sh index cd2025dab..f9330b87b 100755 --- a/tests/smoke/smoketests.sh +++ b/tests/smoke/smoketests.sh @@ -256,6 +256,24 @@ for BACKEND in ${BACKENDS[@]}; do grep 'Test OK' ${TEST_OUT_DIR}/gmres_complex_${BACKEND}_${P}_${T}.log || echo "Test FAILED" echo " " fi + + echo ">>> [x] [ ] Testing the BFS algorithms for the matrix west0497.mtx." + echo " This test employs the grb::Launcher in automatic mode. It uses" + echo " direct-mode file IO." + if [ -f ${INPUT_DIR}/west0497.mtx ] && [ "$BACKEND" != "bsp1d" ] && [ "$BACKEND" != "hybrid" ]; then + $runner ${TEST_BIN_DIR}/bfs_${BACKEND} ${INPUT_DIR}/west0497.mtx direct 0 0 497 &> ${TEST_OUT_DIR}/bfs_west0497_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/bfs_west0497_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/bfs_west0497_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + else + echo "Test DISABLED: west0497.mtx was not found. To enable, please provide ${INPUT_DIR}/west0497.mtx" + fi + if [ -f ${INPUT_DIR}/dwt_59.mtx ] && [ "$BACKEND" != "bsp1d" ] && [ "$BACKEND" != "hybrid" ]; then + $runner ${TEST_BIN_DIR}/bfs_${BACKEND} ${INPUT_DIR}/dwt_59.mtx direct 0 1 13 &> ${TEST_OUT_DIR}/bfs_dwt_59_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/bfs_dwt_59_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/bfs_dwt_59_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + else + echo "Test DISABLED: dwt_59.mtx was not found. To enable, please provide ${INPUT_DIR}/dwt_59.mtx" + fi echo ">>> [x] [ ] Testing the BiCGstab algorithm for the 17361 x 17361 input" echo " matrix gyro_m.mtx. This test verifies against a ground-" @@ -296,6 +314,30 @@ for BACKEND in ${BACKENDS[@]}; do fi echo " " + echo ">>> [x] [ ] Testing the Sparse Neural Network algorithm for the GraphChallenge" + echo " dataset (neurons=1024, layers=120) taken from" + echo " ${GNN_DATASET_PATH} and using thresholding 32." + if [ -d ${GNN_DATASET_PATH} ]; then + $runner ${TEST_BIN_DIR}/graphchallenge_nn_multi_inference_${BACKEND} ${GNN_DATASET_PATH} 1024 120 0 1 32 indirect 1 1 verification ${OUTPUT_VERIFICATION_DIR}/graphchallenge_nn_out_1024_120_0_32_threshold_ref &> ${TEST_OUT_DIR}/graphchallenge_nn_single_inference_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/graphchallenge_nn_multi_inference_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/graphchallenge_nn_multi_inference_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + else + echo "Test DISABLED: ${GNN_DATASET_PATH} was not found. To enable, please provide the dataset." + fi + echo " " + + echo ">>> [x] [ ] Testing the Sparse Neural Network algorithm for the GraphChallenge" + echo " dataset (neurons=1024, layers=120) taken from" + echo " ${GNN_DATASET_PATH} and without using thresholding." + if [ -d ${GNN_DATASET_PATH} ]; then + $runner ${TEST_BIN_DIR}/graphchallenge_nn_multi_inference_${BACKEND} ${GNN_DATASET_PATH} 1024 120 0 0 0 indirect 1 1 verification ${OUTPUT_VERIFICATION_DIR}/graphchallenge_nn_out_1024_120_0_no_threshold_ref &> ${TEST_OUT_DIR}/graphchallenge_nn_single_inference_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/graphchallenge_nn_multi_inference_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/graphchallenge_nn_multi_inference_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + else + echo "Test DISABLED: ${GNN_DATASET_PATH} was not found. To enable, please provide the dataset." + fi + echo " " + for ((i=0;i<${#LABELTEST_SIZES[@]};++i)); do LABELTEST_SIZE=${LABELTEST_SIZES[i]} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 16999fd42..b2caf3290 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -272,6 +272,10 @@ add_grb_executables( pinnedVector pinnedVector.cpp BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking ) +add_grb_executables( bfs_unit bfs.cpp + BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking +) + # the below targets test successfully when they compile -- they do not need to # be executed successfully as part of the unit test suite. diff --git a/tests/unit/bfs.cpp b/tests/unit/bfs.cpp new file mode 100644 index 000000000..cecee1959 --- /dev/null +++ b/tests/unit/bfs.cpp @@ -0,0 +1,565 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include + +#include + +using namespace grb; + +Vector< long > createGrbVector( const std::initializer_list< long > & in ) { + Vector< long > out( in.size() ); + for( size_t i = 0; i < in.size(); i++ ) { + setElement( out, *( in.begin() + i ), i ); + } + return out; +} + +template< typename D > +void printSparseVector( const Vector< D > & v, const std::string & name ) { + wait( v ); + std::cout << " [ "; + if( size( v ) > 50 ) { + std::cout << "too large to print " << std::endl; + } else if( nnz( v ) <= 0 ) { + for( size_t i = 0; i < size( v ); i++ ) + std::cout << "_ "; + } else { + size_t nnz_idx = 0; + auto it = v.cbegin(); + for( size_t i = 0; i < size( v ); i++ ) { + if( nnz_idx < nnz( v ) && i == it->first ) { + std::cout << it->second << " "; + nnz_idx++; + if( nnz_idx < nnz( v ) ) + ++it; + } else { + std::cout << "_ "; + } + } + } + std::cout << " ] - " + << "Vector \"" << name << "\" (" << size( v ) << ")" << std::endl; +} + +struct input_t { + const Matrix< void > & A; + const size_t root; + bool expected_explored_all; + long expected_max_level; + const Vector< long > & expected_values; + + // Necessary for distributed backends + input_t( + const Matrix< void > & A = { 0, 0 }, + size_t root = 0, + bool expected_explored_all = true, + long expected_max_level = 0, + const Vector< long > & expected_values = { 0 } + ) : A( A ), + root( root ), + expected_explored_all( expected_explored_all ), + expected_max_level( expected_max_level ), + expected_values( expected_values ) {} +}; + +struct output_t { + RC rc = SUCCESS; +}; + +void grbProgram_BFS_Levels( + const struct input_t & input, + struct output_t & output +) { + utils::Timer timer; + long max_level; + bool explored_all; + + // Allocate output vector + Vector< long > levels( nrows( input.A ) ); + Vector< bool > x( nrows( input.A ), 1UL ); + Vector< bool > y( nrows( input.A ) ); + Vector< bool > not_visited( nrows( input.A ) ); + + // Run the BFS algorithm + grb::wait(); + output.rc = output.rc + ? output.rc + : algorithms::bfs_levels( input.A, input.root, explored_all, max_level, levels, x, y, not_visited ); + grb::wait(); + + { // Check the outputs + if( explored_all == input.expected_explored_all ) { + std::cout << "SUCCESS: explored_all = " << explored_all << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected explored_all = " + << input.expected_explored_all << " but got " << explored_all << std::endl; + output.rc = FAILED; + return; + } + + if( max_level == input.expected_max_level ) { + std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected max_level " + << input.expected_max_level << " but got " << max_level << std::endl; + output.rc = FAILED; + return; + } + + // Check levels by comparing it with the expected one + if( + !std::equal( input.expected_values.cbegin(), input.expected_values.cend(), levels.cbegin() ) + ) { + std::cerr << "FAILED: levels are incorrect" << std::endl; + std::cerr << "levels != expected_values" << std::endl; + printSparseVector( levels, "levels" ); + printSparseVector( input.expected_values, "expected_values" ); + output.rc = FAILED; + return; + } + } +} + +void grbProgram_BFS_Parents( + const struct input_t & input, + struct output_t & output +) { + utils::Timer timer; + long max_level; + bool explored_all; + + // Allocate output vector + Vector< long > parents( nrows( input.A ) ); + Vector< long > x( nrows( input.A ), 1UL ); + Vector< long > y( nrows( input.A ), 0UL ); + + // Run the BFS algorithm + output.rc = output.rc + ? output.rc + : algorithms::bfs_parents( input.A, input.root, explored_all, max_level, parents, x, y ); + grb::wait(); + + { // Check the outputs + if( explored_all == input.expected_explored_all ) { + std::cout << "SUCCESS: explored_all = " << explored_all << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected explored_all = " << input.expected_explored_all + << " but got " << explored_all << std::endl; + output.rc = FAILED; + return; + } + + if( max_level == input.expected_max_level ) { + std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected max_level " << input.expected_max_level + << " but got " << max_level << std::endl; + output.rc = FAILED; + return; + } + + // Check parents by comparing it with the expected one + if( !std::equal( input.expected_values.cbegin(), input.expected_values.cend(), parents.cbegin() ) ) { + std::cerr << "FAILED: parents are incorrect" << std::endl; + std::cerr << "parents != expected_values" << std::endl; + printSparseVector( parents, "parents" ); + printSparseVector( input.expected_values, "expected_values" ); + output.rc = FAILED; + return; + } + } +} + +int main( int argc, char ** argv ) { + (void)argc; + (void)argv; + + Launcher< EXEC_MODE::AUTOMATIC > launcher; + std::cout << "Test executable: " << argv[ 0 ] << std::endl << std::flush; + + /** Matrix A1: + * + * 2 ───── 0 ───── 1 + * │ + * │ + * │ + * 3 + */ + { /* + * Directed version, pattern matrix, root = 0 + * => 1 step(s) to explore all nodes + */ + size_t root = 0; + std::cout << "-- Running test on A1 (directed, non-pattern, root " + << root << ")" << std::endl; + bool expected_explored_all = true; + long expected_max_level = 1; + Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { 0, 0, 0 }; + std::vector< size_t > A_cols { 1, 2, 3 }; + if( SUCCESS != + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), SEQUENTIAL ) + ) { return FAILED; } + Vector< long > expected_levels = createGrbVector( { 0, 1, 1, 1 } ); + Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 0 } ); + + { // Levels + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Levels, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + { // Parents + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Parents, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } + + /** Matrix A2: + * + * 1 ───── 0 ───── 2 ───── 3 + */ + { /* + * Directed version, pattern matrix, root = 0 + * => 2 step(s) to explore all nodes + */ + size_t root = 0; + std::cout << "-- Running test on A2 (directed, pattern, root " + << root << ")" << std::endl; + bool expected_explored_all = true; + long expected_max_level = 2; + Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { 0, 0, 2 }; + std::vector< size_t > A_cols { 1, 2, 3 }; + if( SUCCESS != + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), SEQUENTIAL ) + ) { return FAILED; } + Vector< long > expected_levels = createGrbVector( { 0, 1, 1, 2 } ); + Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 2 } ); + + { // Levels + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Levels, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + { // Parents + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Parents, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } + + /** Matrix A3: + * + * 0 ───── 1 ───── 2 ───── 3 + * └───────────────────────┘ + */ + { /* + * Directed version, non-pattern matrix, root = 0 + * => 3 step(s) to explore all nodes + */ + size_t root = 0; + std::cout << "-- Running test on A3 (directed, non-pattern: int, root " + << root << ")" << std::endl; + bool expected_explored_all = true; + long expected_max_level = 3; + Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { 0, 1, 2, 3 }; + std::vector< size_t > A_cols { 1, 2, 3, 0 }; + if( SUCCESS != + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), SEQUENTIAL ) + ) { return FAILED; } + Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 3 } ); + Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); + + { // Levels + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Levels, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + { // Parents + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Parents, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } + { /* + * Undirected version, pattern matrix, root = 0 + * => 2 step(s) to explore all nodes + */ + size_t root = 0; + std::cout << "-- Running test on A3 (undirected, pattern, root " + << root << ")" << std::endl; + bool expected_explored_all = true; + long expected_max_level = 2; + Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { 0, 0, 1, 1, 2, 2, 3, 3 }; + std::vector< size_t > A_cols { 3, 1, 0, 2, 1, 3, 2, 0 }; + if( SUCCESS != + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), SEQUENTIAL ) + ) { return FAILED; } + Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 1 } ); + Vector< long > expected_parents = createGrbVector( { 1, 0, 1, 0 } ); + + { // Levels + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Levels, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + { // Parents + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Parents, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } + + /** Matrix A4: + * + * 0 ───── 1 ───── 3 + * │ │ + * 2 ──────┘ + */ + { /* + * Directed version, pattern matrix, root = 0 + * => 3 step(s) to explore all nodes + */ + size_t root = 0; + std::cout << "-- Running test on A4 (directed, pattern, one cycle, root " + << root << ")" << std::endl; + bool expected_explored_all = true; + long expected_max_level = 3; + Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { 0, 1, 2, 3 }; + std::vector< size_t > A_cols { 1, 2, 3, 1 }; + if( SUCCESS != + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), SEQUENTIAL ) + ) { return FAILED; } + Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 3 } ); + Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); + + { // Levels + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Levels, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + { // Parents + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Parents, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } + { /* + * Directed version, pattern matrix, root = 1 + * => Impossible to reach vertex 0 + */ + size_t root = 1; + std::cout << "-- Running test on A4 (directed, pattern, root " + << root << ")" << std::endl; + bool expected_explored_all = false; + + Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { 0, 1, 2, 3 }; + std::vector< size_t > A_cols { 1, 2, 3, 1 }; + if( SUCCESS != + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), SEQUENTIAL ) + ) { return FAILED; } + + { // Levels + long expected_max_level = 2; + Vector< long > expected_levels( 4UL, 3UL ); + if( SUCCESS != setElement( expected_levels, 0UL, 1UL ) ) { return FAILED; } + if( SUCCESS != setElement( expected_levels, 1UL, 2UL ) ) { return FAILED; } + if( SUCCESS != setElement( expected_levels, 2UL, 3UL ) ) { return FAILED; } + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Levels, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + { // Parents + long expected_max_level = 4; + Vector< long > expected_parents = createGrbVector( { -1, 3, 1, 2 } ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Parents, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } + + /** Matrix A5: + * + * 0 ───── 1 ──x── 2 ───── 3 + */ + { /* + * Undirected version, pattern matrix, root = 0 + * => Impossible to reach vertices 2 and 3 + */ + size_t root = 0; + std::cout << "-- Running test on A5 (undirected, pattern, root " + << root << ")" << std::endl; + bool expected_explored_all = false; + + Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { 0, 1, 2, 3 }; + std::vector< size_t > A_cols { 1, 0, 3, 2 }; + if( SUCCESS != + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), SEQUENTIAL ) + ) { return FAILED; } + + { // Levels + long expected_max_level = 1; + Vector< long > expected_levels( 4UL, 2UL ); + if( SUCCESS != setElement( expected_levels, 0UL, 0UL ) ) { return FAILED; } + if( SUCCESS != setElement( expected_levels, 1UL, 1UL ) ) { return FAILED; } + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Levels, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + { // Parents + long expected_max_level = 4; + Vector< long > expected_parents = createGrbVector( { 1, 0, -1, -1 } ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram_BFS_Parents, input, output ); + if( bench_rc ) { + std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } + + std::cout << "Test OK" << std::endl; + + return 0; +} diff --git a/tests/unit/unittests.sh b/tests/unit/unittests.sh index 3817164c8..f304b3f36 100755 --- a/tests/unit/unittests.sh +++ b/tests/unit/unittests.sh @@ -378,6 +378,12 @@ for MODE in ${MODES}; do grep "Test OK" ${TEST_OUT_DIR}/argmin_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" echo " " + echo ">>> [x] [ ] Testing grb::algorithms::bfs with small pre-defined cases." + $runner ${TEST_BIN_DIR}/bfs_unit_${MODE}_${BACKEND} 2> ${TEST_OUT_DIR}/bfs_unit_${MODE}_${BACKEND}_${P}_${T}.err 1> ${TEST_OUT_DIR}/bfs_unit_${MODE}_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/bfs_unit_${MODE}_${BACKEND}_${P}_${T}.log + grep "Test OK" ${TEST_OUT_DIR}/bfs_unit_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + echo " " + echo ">>> [x] [ ] Testing grb::argmax" $runner ${TEST_BIN_DIR}/argmax_${MODE}_${BACKEND} 2> ${TEST_OUT_DIR}/argmax_${MODE}_${BACKEND}_${P}_${T}.err 1> ${TEST_OUT_DIR}/argmax_${MODE}_${BACKEND}_${P}_${T}.log head -1 ${TEST_OUT_DIR}/argmax_${MODE}_${BACKEND}_${P}_${T}.log