diff --git a/include/graphblas/algorithms/sssp.hpp b/include/graphblas/algorithms/sssp.hpp new file mode 100644 index 000000000..8f1e42c3f --- /dev/null +++ b/include/graphblas/algorithms/sssp.hpp @@ -0,0 +1,320 @@ +/* + * 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. + */ + +/** + * @brief SSSP (Single-Source Shortest-Path) algorithm. + * + * @author B. Lozes + * @date: June 05th, 2023 + */ + +#ifndef _H_GRB_SSSP +#define _H_GRB_SSSP + +#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 + std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; + if( rows > 50 || cols > 50 ) { + os << " too large to print" << std::endl; + } else { + // 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.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 + std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; + if( rows > 50 || cols > 50 ) { + os << " too large to print" << std::endl; + } else { + // 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 grb::Matrix< D > & mat, const std::string & name ) { + grb::wait( mat ); + printSparseMatrixIterator( grb::nrows( mat ), grb::ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout ); + } + + template<> + void printSparseMatrix< void >( const grb::Matrix< void > & mat, const std::string & name ) { + grb::wait( mat ); + printSparsePatternMatrixIterator( grb::nrows( mat ), grb::ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout ); + } + + template< typename D > + void printSparseVector( const grb::Vector< D > & v, const std::string & name ) { + (void)v; + (void)name; +#ifdef _DEBUG + grb::wait( v ); + std::cout << " [ "; + if( grb::size( v ) > 50 ) { + std::cout << "too large to print " << std::endl; + } else if( grb::nnz( v ) <= 0 ) { + for( size_t i = 0; i < grb::size( v ); i++ ) + std::cout << "_ "; + } else { + size_t nnz_idx = 0; + for( size_t i = 0; i < grb::size( v ); i++ ) { + if( nnz_idx < grb::nnz( v ) ) { + auto found = std::find_if( v.cbegin(), v.cend(), [ i ]( const std::pair & a ) { + return a.first == i; + } ); + if( found != v.cend() ){ + std::cout << std::showpos << found->second << " "; + nnz_idx++; + continue; + } + } + std::cout << "__ "; + } + } + std::cout << " ] - " + << "Vector \"" << name << "\" (" << grb::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 + std::cout << " [ "; + if( vector.size() > 50 ) { + std::cout << "too large to print " << std::endl; + } else { + for( const T & e : vector ) + std::cout << e << " "; + } + std::cout << " ] - " + << "Vector \"" << name << "\" (" << vector.size() << ")" << std::endl; +#endif + } + + void debugPrint( const std::string & msg, std::ostream & os = std::cout ) { +#ifdef _DEBUG + os << msg; +#endif + } + } // namespace utils + + /** + * Single-source-shortest-path (SSSP) algorithm. + * + * This version computes the minimum distance from the root to each vertex. + * + * @tparam D Matrix values type + * @tparam T Distance 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] distances Vector containing the minumum distance to + * reach each vertex. + * 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_distance Distance to use for vertices that have + * not been reached (default: -1) + * + * \parblock + * \par Possible output values: + * -# max_level: [0, nrows(A) - 1] + * -# distances: - [0, nrows(A) - 1] for reached vertices, + * - not_find_distance for unreached vertices + * \endparblock + * + * \warning Distance type T must be a signed integer type. + * + * \note The matrix A can be a pattern matrix, in which case + * the identity of the semiring is used as the weight of each edge. + * + * \note The distance to the root is set to zero. + */ + template< + Descriptor descr = descriptors::no_operation, + typename D, + 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 sssp( + const Matrix< D > &A, + size_t root, + bool &explored_all, + size_t &max_level, + Vector< T > &distances, + Vector< T > &x, + Vector< T > &y, + const long max_iterations = -1L, + const T not_find_distance = std::numeric_limits< T >::max(), + const MinAddSemiring semiring = MinAddSemiring(), + const MaxMonoid max_monoid = MaxMonoid(), + const MinNegativeMonoid min_negative_monoid = MinNegativeMonoid(), + const std::enable_if< + std::is_arithmetic< T >::value + && is_semiring< MinAddSemiring >::value + && is_monoid< MaxMonoid >::value + && is_monoid< MinNegativeMonoid >::value, + void + > * const = nullptr + ) { + RC rc = SUCCESS; + const size_t nvertices = nrows( A ); + utils::printSparseMatrix( A, "A" ); + + assert( nrows( A ) == ncols( A ) ); + assert( size( x ) == nvertices ); + assert( size( y ) == nvertices ); + assert( capacity( x ) >= 1 ); + assert( capacity( y ) >= 0 ); + + // Resize the output vector and fill it with -1, except for the root node which is set to 0 + rc = rc ? rc : resize( distances, nrows( A ) ); + rc = rc ? rc : set( distances, not_find_distance ); + rc = rc ? rc : setElement( distances, root, static_cast< T >( 0 ) ); + utils::printSparseVector( distances, "distances" ); + + // Set x to the root node, initial distance is 0 + rc = rc ? rc : setElement( x, static_cast< T >( 0 ), root ); + utils::printSparseVector( x, "x" ); + rc = rc ? rc : set( y, static_cast< T >( 0 ) ); + utils::printSparseVector( y, "y" ); + + 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 ); + + utils::printSparseVector( x, "x" ); + rc = rc ? rc : vxm< descr >( y, x, A, semiring, Phase::RESIZE ); + rc = rc ? rc : vxm< descr >( y, x, A, semiring, Phase::EXECUTE ); + utils::printSparseVector( y, "y" ); + + rc = rc ? rc : foldl( distances, y, y, operators::min< T >(), Phase::RESIZE ); + rc = rc ? rc : foldl( distances, distances, y, operators::min< T >(), Phase::EXECUTE ); + utils::printSparseVector( distances, "distances" ); + + T max_distance = 0; + rc = rc ? rc : foldl( max_distance, distances, max_monoid ); + if( max_distance < not_find_distance ) { + explored_all = true; +#ifdef _DEBUG + std::cout << "Explored " << level << " levels to discover all of the " + << nvertices << " vertices.\n" << std::flush; +#endif + break; + } + + std::swap( x, y ); + } + + return rc; + } + + } // namespace algorithms + +} // namespace grb + +#endif // _H_GRB_SSSP diff --git a/tests/smoke/CMakeLists.txt b/tests/smoke/CMakeLists.txt index 1f99446ee..3dd2159af 100644 --- a/tests/smoke/CMakeLists.txt +++ b/tests/smoke/CMakeLists.txt @@ -180,6 +180,11 @@ add_grb_executables( kcore_decomposition kcore_decomposition.cpp BACKENDS reference reference_omp hyperdags nonblocking bsp1d hybrid ) +add_grb_executables( sssp sssp.cpp + ADDITIONAL_LINK_LIBRARIES test_utils_headers + BACKENDS reference reference_omp hyperdags nonblocking +) + # targets to list and build the test for this category get_property( smoke_tests_list GLOBAL PROPERTY tests_category_smoke ) add_custom_target( "list_tests_category_smoke" diff --git a/tests/smoke/smoketests.sh b/tests/smoke/smoketests.sh index cd2025dab..3a2bafc51 100755 --- a/tests/smoke/smoketests.sh +++ b/tests/smoke/smoketests.sh @@ -296,6 +296,8 @@ for BACKEND in ${BACKENDS[@]}; do fi echo " " + # TODO: Add SSSP test here using a given dataset + for ((i=0;i<${#LABELTEST_SIZES[@]};++i)); do LABELTEST_SIZE=${LABELTEST_SIZES[i]} diff --git a/tests/smoke/sssp.cpp b/tests/smoke/sssp.cpp new file mode 100644 index 000000000..d02b33166 --- /dev/null +++ b/tests/smoke/sssp.cpp @@ -0,0 +1,155 @@ +/* + * 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 +#include +#include +#include + +using namespace grb; + +template< typename T > +Vector< T > stdToGrbVector( const std::vector< T > & in ) { + Vector< T > out( in.size() ); + for( size_t i = 0; i < in.size(); i++ ) + setElement( out, in[ i ], i ); + return out; +} + +template< typename T > +struct input_t { + const Matrix< T >& A; + size_t root; + const Vector< T > & expected_distances; + + // Empty constructor necessary for distributed backends + input_t( + const Matrix& A = {0,0}, + size_t root = 0, + const Vector< T > & expected_distances = {0} + ) : A( A ), root( root ), expected_distances( expected_distances ) {} +}; + +struct output_t { + RC rc = SUCCESS; + utils::TimerResults times; +}; + +template< typename T > +void grbProgram( const struct input_t< T > & input, struct output_t & output ) { + std::cout << std::endl << "Running SSSP" << std::endl; + output.rc = SUCCESS; + utils::Timer timer; + + timer.reset(); + bool explored_all = false; + size_t max_level = 0; + Vector< T > distances( grb::nrows( input.A ) ); + Vector< T > x( grb::nrows( input.A ) ), y( grb::nrows( input.A ) ); + output.times.preamble = timer.time(); + + timer.reset(); + output.rc = algorithms::sssp( input.A, input.root, explored_all, max_level, distances, x, y ); + output.times.useful = timer.time(); + + // Check distances by comparing it with the expected one + bool expected_equals = std::equal( + input.expected_distances.cbegin(), input.expected_distances.cend(), distances.cbegin() + ); + if( expected_equals ) { + std::cout << "SUCCESS: distances are correct" << std::endl; + } else { + std::cerr << "FAILED: distances are incorrect" << std::endl; + std::cerr << "distances != expected_distances" << std::endl; + for( size_t i = 0; i < grb::nrows( input.A ); i++ ) + std::cerr << std::string( 3, ' ' ) << distances[ i ] << " | " + << input.expected_distances[ i ] << std::endl; + output.rc = FAILED; + } +} + +int main( int argc, char ** argv ) { + (void)argc; + (void)argv; + + Benchmarker< AUTOMATIC > benchmarker; + std::cout << "Test executable: " << argv[ 0 ] << std::endl; + + if( argc < 4 ) { + std::cerr << "Usage: \n\t" + << argv[ 0 ] + << " " + << " " + << " " + << " " + << " [ inner_iterations=1 ]" + << " [ outer_iterations=1 ]" + << std::endl; + return 1; + } + std::string dataset( argv[ 1 ] ); + bool direct = ( strcmp( argv[ 2 ], "direct" ) == 0 ); + size_t root = std::stoul( argv[ 3 ] ); + std::string expected_distances_filepath( argv[ 4 ] ); + size_t inner_iterations = ( argc >= 5 ) ? std::stoul( argv[ 4 ] ) : 1; + size_t outer_iterations = ( argc >= 6 ) ? std::stoul( argv[ 5 ] ) : 1; + + std::cout << "-- Running test on file: " << dataset << std::endl; + + // Read matrix from file + utils::MatrixFileReader< double > reader( dataset, direct, true ); + size_t r = reader.n(), c = reader.m(); + assert( r == c ); + Matrix< double > A( r, c ); + RC rc_build = buildMatrixUnique( + A, reader.cbegin( SEQUENTIAL ), reader.cend( SEQUENTIAL ), SEQUENTIAL + ); + if( rc_build != SUCCESS ) { + std::cerr << "ERROR during buildMatrixUnique: rc = " + << grb::toString( rc_build ) << std::endl; + return 1; + } + std::cout << "Matrix read successfully" << std::endl; + + Vector< double > expected_distances( r ); + // TODO: Read expected_distances vector from file + + // Run the algorithm + input_t< double > input { A, root, expected_distances }; + output_t output; + RC bench_rc = benchmarker.exec( + &grbProgram, input, output, inner_iterations, outer_iterations, true + ); + if( bench_rc ) { + std::cerr << "ERROR during execution on file " << dataset + << ": rc = " << grb::toString(bench_rc) << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << grb::toString(output.rc) << std::endl; + return output.rc; + } + + std::cerr << std::flush; + std::cout << "Test OK" << std::endl << std::flush; + + return 0; +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 16999fd42..ed2d18793 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -215,6 +215,11 @@ add_grb_executables( spy spy.cpp BACKENDS reference reference_omp hyperdags nonblocking ) +add_grb_executables( sssp_unit sssp.cpp + ADDITIONAL_LINK_LIBRARIES test_utils_headers + BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking +) + add_grb_executables( dense_spmv dense_spmv.cpp BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking ) diff --git a/tests/unit/sssp.cpp b/tests/unit/sssp.cpp new file mode 100644 index 000000000..d31b4f952 --- /dev/null +++ b/tests/unit/sssp.cpp @@ -0,0 +1,274 @@ +/* + * 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 + +using namespace grb; +using weight_t = int; + +template< typename T > +Vector< T > stdToGrbVector( const std::vector< T > & in ) { + Vector< T > out( in.size() ); + for( size_t i = 0; i < in.size(); i++ ) + grb::setElement( out, in[ i ], i ); + return out; +} + +template< typename T > +struct input_t { + Matrix< T > A; + size_t root; + const std::vector< T >& expected_distances; + + // Empty constructor necessary for distributed backends + explicit input_t( + const Matrix& A = {0,0}, + size_t root = 0, + const std::vector< T >& expected_distances = {} + ) : A( A ), root( root ), expected_distances( expected_distances ) {} +}; + +template< typename T > +RC test_case( const struct input_t< T > & input ) { + std::cout << std::endl << "Running SSSP" << std::endl; + RC rc = SUCCESS; + + bool explored_all = false; + size_t max_level = 0; + Vector< T > distances( grb::nrows( input.A ) ); + Vector< T > x( grb::nrows( input.A ) ), y( grb::nrows( input.A ) ); + + rc = rc ? rc : algorithms::sssp( input.A, input.root, explored_all, max_level, distances, x, y ); + + // Check distances by comparing it with the expected one + bool equals_distances = true; + for( size_t i = 0; i < grb::nrows( input.A ) && equals_distances; i++ ) { + equals_distances &= (input.expected_distances[ i ] == distances[ i ]); + } + if( not equals_distances ) { + std::cerr << "FAILED: distances are incorrect" << std::endl; + std::cerr << "distances != expected_distances" << std::endl; + for( size_t i = 0; i < grb::nrows( input.A ); i++ ) + std::cerr << std::string( 3, ' ' ) << distances[ i ] << " | " << input.expected_distances[ i ] << std::endl; + rc = FAILED; + } + + return rc; +} + +void grb_test_suite( const void *, const size_t, RC& rc ) { + + /** Matrix A0: Fully connected graph + * + * [0] [1] + * ├───────┤ + * [2] [3] + * + */ + { // Directed version, root = 0, uniform weights = 1 + size_t root = 0; + std::vector< weight_t > expected_distances { 0, 1, 1, 1 }; + std::cout << "-- Running test on A0 (undirected, acyclic, root " + std::to_string( root ) + ")" << std::endl; + Matrix< weight_t > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3 } }; + std::vector< size_t > A_cols { { 1, 2, 3, 0, 2, 3, 0, 1, 3, 0, 1, 2 } }; + std::vector< weight_t > A_values( A_rows.size(), 1 ); + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_rows.size(), PARALLEL ); + input_t< weight_t > input { A, root, expected_distances }; + rc = test_case(input ); + if( rc != SUCCESS ) { + std::cerr << "Test failed: rc = " << rc << std::endl; + return; + } + std::cout << std::endl; + } + + /** Matrix A1: Node [0] connected to all other nodes + * + * Schema: + * 0 ----- 1 + * | \ + * | \ + * | \ + * 2 3 + * + * [0] ──┬──▶ [1] + * │ │ + * ▼ ▼ + * [2] [3] + * + */ + { // Directed version, root = 0, uniform weights = 1 + size_t root = 0; + std::vector< weight_t > expected_distances { 0, 1, 1, 1 }; + std::cout << "-- Running test on A1 (directed, root " + std::to_string( root ) + ")" << std::endl; + Matrix< weight_t > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 0 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< weight_t > A_values( A_rows.size(), 1 ); + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_rows.size(), PARALLEL ); + input_t< weight_t > input { A, root, expected_distances }; + rc = test_case(input ); + if( rc != SUCCESS ) { + std::cerr << "Test failed: rc = " << rc << std::endl; + return; + } + std::cout << std::endl; + } + + /** Matrix A2: + * + * [0] ──▶ [2] ──▶ [3] + * │ + * ▼ + * [1] + * + */ + { // Directed version, root = 0, uniform weights = 1 + size_t root = 0; + std::vector< weight_t > expected_distances { 0, 1, 1, 2 }; + std::cout << "-- Running test on A2 (directed, root " + std::to_string( root ) + ")" << std::endl; + Matrix< weight_t > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< weight_t > A_values( A_rows.size(), 1 ); + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_rows.size(), PARALLEL ); + input_t< weight_t > input { A, root, expected_distances }; + rc = test_case(input ); + if( rc != SUCCESS ) { + std::cerr << "Test failed: rc = " << rc << std::endl; + return; + } + std::cout << std::endl; + } + + /** Matrix A3: + * + * [0] ──▶ [1] ──▶ [2] ──▶ [3] + * + */ + { // Directed version, root = 0, uniform weights = 1 + size_t root = 0; + std::vector< weight_t > expected_distances { 0, 1, 2, 3 }; + std::cout << "-- Running test on A3.1 (directed, root " + std::to_string( root ) + ")" << std::endl; + Matrix< weight_t > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< weight_t > A_values( A_rows.size(), 1 ); + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_rows.size(), PARALLEL ); + input_t< weight_t > input { A, root, expected_distances }; + rc = test_case(input ); + if( rc != SUCCESS ) { + std::cerr << "Test failed: rc = " << rc << std::endl; + return; + } + std::cout << std::endl; + } + { // Directed version, root = 0, uniform weights = 10 + size_t root = 0; + std::vector< weight_t > expected_distances { 0, 10, 20, 30 }; + std::cout << "-- Running test on A3.2 (directed, root " + std::to_string( root ) + ")" << std::endl; + Matrix< weight_t > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< weight_t > A_values( A_rows.size(), 10 ); + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_rows.size(), PARALLEL ); + input_t< weight_t > input { A, root, expected_distances }; + rc = test_case(input ); + if( rc != SUCCESS ) { + std::cerr << "Test failed: rc = " << rc << std::endl; + return; + } + std::cout << std::endl; + } + + /** Matrix A4: + * Graph A3 with a shortcut from [0] to [2] + * + * [0] ──▶ [1] ──▶ [2] ──▶ [3] + * │ ▲ + * └───────────────┘ + * + */ + { // Directed version, root = 0, uniform weights = 1 + size_t root = 0; + std::vector< weight_t > expected_distances { 0, 1, 1, 2 }; + std::cout << "-- Running test on A4.1 (directed, root " + std::to_string( root ) + ")" << std::endl; + Matrix< weight_t > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 2, 3 } }; + std::vector< weight_t > A_values( A_rows.size(), 1 ); + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_rows.size(), PARALLEL ); + input_t< weight_t > input { A, root, expected_distances }; + rc = test_case(input ); + if( rc != SUCCESS ) { + std::cerr << "Test failed: rc = " << rc << std::endl; + return; + } + std::cout << std::endl; + } + { // Directed version, root = 0, uniform weights = 10 + size_t root = 0; + std::vector< weight_t > expected_distances { 0, 10, 10, 20 }; + std::cout << "-- Running test on A4.2 (directed, root " + std::to_string( root ) + ")" << std::endl; + Matrix< weight_t > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 2, 3 } }; + std::vector< weight_t > A_values( A_rows.size(), 10 ); + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_rows.size(), PARALLEL ); + input_t< weight_t > input { A, root, expected_distances }; + rc = test_case( input ); + if( rc != SUCCESS ) { + std::cerr << "Test failed: rc = " << rc << std::endl; + return; + } + std::cout << std::endl; + } +} + +int main( int argc, char ** argv ) { + (void)argc; + (void)argv; + + Launcher< AUTOMATIC > launcher; + std::cout << "Test executable: " << argv[ 0 ] << std::endl; + + if( argc != 1 ) { + std::cerr << "Usage: \n\t" << argv[ 0 ] << std::endl; + return 1; + } + + RC success = SUCCESS; + RC execution_rc = launcher.exec( &grb_test_suite, nullptr, 0, success, true ); + if( execution_rc != SUCCESS ) { + std::cerr << "ERROR during execution: execution_rc is " + << toString(execution_rc) << std::endl; + return execution_rc; + } + + if( success != SUCCESS ) { + std::cerr << "Test FAILED. Return code (RC) is " << grb::toString(success) << std::endl; + return FAILED; + } + std::cout << "Test OK" << std::endl; + + return 0; +} diff --git a/tests/unit/unittests.sh b/tests/unit/unittests.sh index 3817164c8..260d72edf 100755 --- a/tests/unit/unittests.sh +++ b/tests/unit/unittests.sh @@ -637,6 +637,13 @@ for MODE in ${MODES}; do fi echo " " + echo ">>> [x] [ ] Testing the Single-Source-Shortest-Path (SSSP) algorithm on small pre-defined" + echo " graphs testing specific border cases." + $runner ${TEST_BIN_DIR}/sssp_unit_${MODE}_${BACKEND} &> ${TEST_OUT_DIR}/sssp_unit_${MODE}_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/sssp_unit_${MODE}_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/sssp_unit_${MODE}_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + echo " " + #if [ "$BACKEND" = "reference_omp" ]; then # echo "Additional standardised unit tests not yet supported for the ${BACKEND} backend" # echo