From 61ada026ed651921d909483e31f23f8b928c3f1f Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:28:11 +0200 Subject: [PATCH 01/22] bfs.hpp algorithm file & utils functions --- include/graphblas/algorithms/bfs.hpp | 166 +++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 include/graphblas/algorithms/bfs.hpp diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp new file mode 100644 index 000000000..3cfe056be --- /dev/null +++ b/include/graphblas/algorithms/bfs.hpp @@ -0,0 +1,166 @@ + +/* + * 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) algorithms. + * + * @author B. Lozes + * @date: May 26th, 2023 + */ + +#ifndef _H_GRB_TRIANGLE_ENUMERATION +#define _H_GRB_TRIANGLE_ENUMERATION + +#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 << "Vector \"" << name << "\" (" << grb::size( v ) << "):" << std::endl << "[ "; + if( grb::size( v ) > 50 ) { + std::cout << "too large to print " << std::endl; + } else { + for( const std::pair< size_t, D > & pair : v ) + std::cout << pair.second << " "; + std::cout << " ]" << std::endl; + } +#endif + } + + void debugPrint( const std::string & msg, std::ostream & os = std::cout ) { +#ifdef _DEBUG + os << msg; +#endif + } + } // namespace utils + + template< typename D > + RC bfs_steps( size_t & total_steps, const Matrix< D > & A, size_t root ) { + grb::RC rc = grb::RC::SUCCESS; + return rc; + } + + template< typename D > + RC bfs_steps_per_vertex( size_t & total_steps, grb::Vector< size_t > & steps_per_vertex, const Matrix< D > & A, size_t root ) { + grb::RC rc = grb::RC::SUCCESS; + return rc; + } + + } // namespace algorithms + +} // namespace grb + +#endif // _H_GRB_TRIANGLE_ENUMERATION From 3b89fdafe534966ee278f7062d6f663477fdfd46 Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:55:58 +0200 Subject: [PATCH 02/22] Smoke test for BFS --- tests/smoke/CMakeLists.txt | 5 ++ tests/smoke/bfs.cpp | 138 +++++++++++++++++++++++++++++++++++++ tests/smoke/smoketests.sh | 30 ++++++++ 3 files changed, 173 insertions(+) create mode 100644 tests/smoke/bfs.cpp diff --git a/tests/smoke/CMakeLists.txt b/tests/smoke/CMakeLists.txt index 1f99446ee..b2ed80bc4 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 bsp1d hybrid 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..0a6392658 --- /dev/null +++ b/tests/smoke/bfs.cpp @@ -0,0 +1,138 @@ +/* + * 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 + +grb::Vector< size_t > stdVectorToGrbVector( const std::vector< size_t > & in ) { + grb::Vector< size_t > out( in.size() ); + for( size_t i = 0; i < in.size(); i++ ) + grb::setElement( out, in[ i ], i ); + return out; +} + +template< typename T = void > +struct input_t { + grb::Matrix< T > A; + size_t root; + size_t expected_total_steps; + bool test_steps_per_vertex; + grb::Vector< size_t > expected_steps_per_vertex; +}; + +struct output_t { + grb::RC rc = grb::RC::SUCCESS; + grb::utils::TimerResults times; + size_t data_in_local; +}; + +template< typename T > +void grbProgram( const input_t< T > & input, output_t & output ) { + std::cout << std::endl << "Running BFS" << std::endl; + grb::utils::Timer timer; + size_t total_steps = 0; + + timer.reset(); + output.rc = output.rc ? output.rc : grb::algorithms::bfs_steps( total_steps, input.A, input.root ); + timer.reset(); + + // TODO: Add check for steps_per_vertex + if( total_steps <= input.expected_total_steps ) { + std::cout << "SUCCESS: total_steps = " << total_steps << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected maximum " << input.expected_total_steps << " total_steps but got " << total_steps << std::endl; + output.rc = grb::RC::FAILED; + } + + timer.reset(); + grb::Vector< size_t > steps_per_vertex( grb::nrows( input.A ) ); + output.rc = output.rc ? output.rc : grb::algorithms::bfs_steps_per_vertex( total_steps, steps_per_vertex, input.A, input.root ); + timer.reset(); + + // Check steps_per_vertex by comparing it with the expected one + if( std::equal( input.expected_steps_per_vertex.cbegin(), input.expected_steps_per_vertex.cend(), steps_per_vertex.cbegin() ) ) { + std::cout << "SUCCESS: steps_per_vertex is correct" << std::endl; + } else { + std::cerr << "FAILED: steps_per_vertex is incorrect" << std::endl; + output.rc = grb::RC::FAILED; + } +} + +int main( int argc, char ** argv ) { + (void)argc; + (void)argv; + constexpr size_t niterations = 1; + + grb::Benchmarker< grb::EXEC_MODE::AUTOMATIC > benchmarker; + std::cout << "Test executable: " << argv[ 0 ] << std::endl; + + // Check if we are testing on a file + if( argc != 1 && argc != 3 ) { + std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ ]" << std::endl; + return 1; + } + bool test_on_file = argc == 3; + std::string file_to_test( test_on_file ? argv[ 1 ] : "" ); + size_t expected_file_triangles = test_on_file ? std::stoul( argv[ 2 ] ) : 0; + + /** Matrix A1: + * + * Schema: + * 0 ----- 1 + * | \ + * | \ + * | \ + * 2 3 + * + * => 1 step(s) to reach all nodes, root = 0 + */ + { // Directed version, pattern matrix + std::cout << "-- Running test on A1 (directed, non-pattern)" << std::endl; + size_t expected_total_steps = 1; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 0 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 1 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + + std::cout << "Test OK" << std::endl; + + return 0; +} diff --git a/tests/smoke/smoketests.sh b/tests/smoke/smoketests.sh index cd2025dab..5ec560899 100755 --- a/tests/smoke/smoketests.sh +++ b/tests/smoke/smoketests.sh @@ -256,6 +256,12 @@ 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 algorithm" + $runner ${TEST_BIN_DIR}/bfs_${BACKEND} &> ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + echo " " 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 +302,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]} From f99591f31515dfc169d2aad00cd8c6b3b7669032 Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:56:29 +0200 Subject: [PATCH 03/22] BFS implementation: total_steps count --- include/graphblas/algorithms/bfs.hpp | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 3cfe056be..26c51bf54 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -150,6 +150,45 @@ namespace grb { template< typename D > RC bfs_steps( size_t & total_steps, const Matrix< D > & A, size_t root ) { grb::RC rc = grb::RC::SUCCESS; + + total_steps = ULONG_MAX; + const size_t nvertices = grb::nrows( A ); + std::cout << "Running BFS from " << root << " on " << nvertices << " vertices." << std::endl; + grb::Vector< bool > x( nvertices ), y( nvertices ); + grb::set( x, false ); + grb::setElement( x, true, root ); + grb::set( y, x ); + + utils::printSparseMatrix( A, "A" ); + + grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true > bool_monoid; + + for( size_t depth = 0; depth < nvertices; depth++ ) { + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + + utils::debugPrint( "-- Depth " + std::to_string( depth + 1 ) + ":\n" ); + utils::printSparseVector( x, "x" ); + utils::printSparseVector( y, "y" ); + + bool all_visited = true; + rc = rc ? rc : grb::foldl( all_visited, y, bool_monoid ); + + if( all_visited ) { + // If all vertices are discovered, stop + utils::debugPrint( "Took " + std::to_string( depth + 1 ) + " steps to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + total_steps = depth + 1; + return rc; + } + + std::swap( x, y ); + } + + // Maximum number of iteration passed, not every vertex has been discovered + utils::debugPrint( "A full exploration is not possible on this graph. " + "Some vertices are not reachable from the given root: " + + std::to_string( root ) + "\n" ); return rc; } From 91486f7045a437503b123a9d5396162dc9457d38 Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:56:42 +0200 Subject: [PATCH 04/22] BFS implementation: steps_per_vertex count --- include/graphblas/algorithms/bfs.hpp | 63 ++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 26c51bf54..e874ba2fa 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -195,6 +195,69 @@ namespace grb { template< typename D > RC bfs_steps_per_vertex( size_t & total_steps, grb::Vector< size_t > & steps_per_vertex, const Matrix< D > & A, size_t root ) { grb::RC rc = grb::RC::SUCCESS; + const size_t nvertices = grb::nrows( A ); + + std::cout << "Running BFS from " << root << " on " << nvertices << " vertices." << std::endl; + + total_steps = ULONG_MAX; + grb::Vector< bool > x( nvertices ), y( nvertices ), previous_x( nvertices ); + grb::set( x, false ); + grb::setElement( x, true, root ); + grb::set( y, x ); + grb::set( previous_x, false ); + utils::printSparseVector( x, "X - initial" ); + utils::printSparseVector( y, "Y - initial" ); + + grb::resize( steps_per_vertex, nvertices ); + grb::set( steps_per_vertex, ULONG_MAX, grb::Phase::EXECUTE ); + grb::setElement( steps_per_vertex, 0UL, root ); + utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); + + utils::printSparseMatrix( A, "A" ); + + grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true > bool_monoid; + + grb::Semiring< grb::operators::right_assign_if< size_t >, grb::operators::min< size_t >, grb::identities::one, grb::identities::zero > dist_semiring; + + for( size_t depth = 0; depth < nvertices; depth++ ) { + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + + utils::debugPrint( "-- Depth " + std::to_string( depth ) + ":\n" ); + utils::printSparseVector( x, "X " ); + utils::printSparseVector( y, "Y" ); + + grb::set( x, y ); + grb::set( previous_x, y ); + + grb::eWiseLambda( + [ &steps_per_vertex, &y, depth ]( const size_t i ) { + if( y[ i ] ) + steps_per_vertex[ i ] = std::min( steps_per_vertex[ i ], depth + 1 ); + }, + steps_per_vertex, y ); + + utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); + + bool all_visited = true; + rc = rc ? rc : grb::foldl( all_visited, y, bool_monoid ); + + if( all_visited ) { + // If all vertices are discovered, stop + utils::debugPrint( "Took " + std::to_string( depth + 1 ) + " steps to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + total_steps = depth + 1; + return rc; + } + + std::swap( x, y ); + } + + // Maximum number of iteration passed, not every vertex has been discovered + utils::debugPrint( "A full exploration is not possible on this graph. " + "Some vertices are not reachable from the given root: " + + std::to_string( root ) + "\n" ); + return rc; } From 3f9179a41ce3dd4d14979aee4e9b29b5d9d496cd Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:57:07 +0200 Subject: [PATCH 05/22] New test case for BFS (A2) --- tests/smoke/bfs.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 0a6392658..5766cac47 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -131,6 +131,39 @@ int main( int argc, char ** argv ) { std::cout << std::endl; } + /** Matrix A2: + * + * Schema: + * 0 ----- 1 + * | + * | + * | + * 2 ----- 3 + * + * => 2 step(s) to reach all nodes, root = 0 + */ + { // Directed version, pattern matrix + std::cout << "-- Running test on A2 (directed, pattern)" << std::endl; + size_t expected_total_steps = 2; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 2 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + std::cout << "Test OK" << std::endl; From 1df278429f874f7079742fda0ec1575ff6bdb941 Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:57:23 +0200 Subject: [PATCH 06/22] New test case for BFS (A3) --- tests/smoke/bfs.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 5766cac47..2976698e5 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -164,6 +164,61 @@ int main( int argc, char ** argv ) { std::cout << std::endl; } + /** Matrix A3: + * + * Schema: + * 0 ----- 1 + * | + * | + * | + * 2 ----- 3 + * + * => 3 step(s) to reach all nodes, root = 0 + */ + { // Directed version, non-pattern matrix + std::cout << "-- Running test on A3 (directed, non-pattern: int)" << std::endl; + size_t expected_total_steps = 3; + size_t root = 0; + grb::Matrix< int > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 3 } }; + std::vector< size_t > A_cols { { 1, 3, 2 } }; + std::vector< int > A_values( A_rows.size(), 1 ); + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; + input_t< int > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + { // Directed version, pattern matrix + std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; + size_t expected_total_steps = 3; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 3 } }; + std::vector< size_t > A_cols { { 1, 3, 2 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + std::cout << "Test OK" << std::endl; From 968518f92e5ecf52a49ef11e39b3878f31652a49 Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:57:34 +0200 Subject: [PATCH 07/22] New test case for BFS (A4) --- tests/smoke/bfs.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 2976698e5..68e72f617 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -219,6 +219,39 @@ int main( int argc, char ** argv ) { std::cout << std::endl; } + /** Matrix A4: + * + * Schema: + * 0 ----- 1 + * / | + * / | + * / | + * 2 ----- 3 + * + * => 3 step(s) to reach all nodes, but contains a cycle, root = 0 + */ + { // Directed version, pattern matrix + std::cout << "-- Running test on A4 (directed, pattern, one cycle)" << std::endl; + size_t expected_total_steps = 3; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; + std::vector< size_t > A_cols { { 1, 3, 1, 2 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + std::cout << "Test OK" << std::endl; From 0962056961a9447092bc64bc57b67dd2e33b27c4 Mon Sep 17 00:00:00 2001 From: byjtew Date: Sun, 4 Jun 2023 22:59:36 +0200 Subject: [PATCH 08/22] New test case for BFS: User given matrix --- tests/smoke/bfs.cpp | 51 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 68e72f617..2f0a425f6 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -39,7 +39,7 @@ struct input_t { grb::Matrix< T > A; size_t root; size_t expected_total_steps; - bool test_steps_per_vertex; + bool steps_per_vertex; grb::Vector< size_t > expected_steps_per_vertex; }; @@ -67,17 +67,19 @@ void grbProgram( const input_t< T > & input, output_t & output ) { output.rc = grb::RC::FAILED; } - timer.reset(); - grb::Vector< size_t > steps_per_vertex( grb::nrows( input.A ) ); - output.rc = output.rc ? output.rc : grb::algorithms::bfs_steps_per_vertex( total_steps, steps_per_vertex, input.A, input.root ); - timer.reset(); + if( input.steps_per_vertex ) { + timer.reset(); + grb::Vector< size_t > steps_per_vertex( grb::nrows( input.A ) ); + output.rc = output.rc ? output.rc : grb::algorithms::bfs_steps_per_vertex( total_steps, steps_per_vertex, input.A, input.root ); + timer.reset(); - // Check steps_per_vertex by comparing it with the expected one - if( std::equal( input.expected_steps_per_vertex.cbegin(), input.expected_steps_per_vertex.cend(), steps_per_vertex.cbegin() ) ) { - std::cout << "SUCCESS: steps_per_vertex is correct" << std::endl; - } else { - std::cerr << "FAILED: steps_per_vertex is incorrect" << std::endl; - output.rc = grb::RC::FAILED; + // Check steps_per_vertex by comparing it with the expected one + if( std::equal( input.expected_steps_per_vertex.cbegin(), input.expected_steps_per_vertex.cend(), steps_per_vertex.cbegin() ) ) { + std::cout << "SUCCESS: steps_per_vertex is correct" << std::endl; + } else { + std::cerr << "FAILED: steps_per_vertex is incorrect" << std::endl; + output.rc = grb::RC::FAILED; + } } } @@ -252,6 +254,33 @@ int main( int argc, char ** argv ) { std::cout << std::endl; } + /** Given matrix in input **/ + if( test_on_file ) { + std::cout << "-- Running test on file " << file_to_test << std::endl; + + // Read matrix from file as a pattern matrix (i.e. no values) + grb::utils::MatrixFileReader< void > reader( file_to_test, false, true ); + size_t r = reader.n(), c = reader.m(); + grb::Matrix< void > A( r, c ); + grb::RC rc_build = buildMatrixUnique( A, reader.cbegin( grb::IOMode::SEQUENTIAL ), reader.cend( grb::IOMode::SEQUENTIAL ), grb::IOMode::PARALLEL ); + if( rc_build != grb::RC::SUCCESS ) { + std::cerr << "ERROR during buildMatrixUnique of the pattern matrix: rc = " << rc_build << std::endl; + return 1; + } + + std::cout << "Matrix read successfully" << std::endl; + // TODO: Find a way to ask the steps_per_vertex to the user + input_t< void > input { A, 0, expected_file_triangles, false, { 0 } }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + if( bench_rc ) { + std::cerr << "ERROR during execution of file " << file_to_test << ": rc = " << bench_rc << std::endl; + return bench_rc; + } else if( output.rc ) { + std::cerr << "Test failed: rc = " << output.rc << std::endl; + return output.rc; + } + } std::cout << "Test OK" << std::endl; From d981dce989a60ece4179ffb7cdb5edfb0948b46b Mon Sep 17 00:00:00 2001 From: byjtew Date: Mon, 5 Jun 2023 10:34:32 +0200 Subject: [PATCH 09/22] bfs standard signature + computation optimisation --- include/graphblas/algorithms/bfs.hpp | 126 ++++++++++----------------- tests/smoke/bfs.cpp | 20 ++--- 2 files changed, 56 insertions(+), 90 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index e874ba2fa..1f748a5b4 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -147,109 +147,69 @@ namespace grb { } } // namespace utils - template< typename D > - RC bfs_steps( size_t & total_steps, const Matrix< D > & A, size_t root ) { - grb::RC rc = grb::RC::SUCCESS; - - total_steps = ULONG_MAX; - const size_t nvertices = grb::nrows( A ); - std::cout << "Running BFS from " << root << " on " << nvertices << " vertices." << std::endl; - grb::Vector< bool > x( nvertices ), y( nvertices ); - grb::set( x, false ); - grb::setElement( x, true, root ); - grb::set( y, x ); - - utils::printSparseMatrix( A, "A" ); - - grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true > bool_monoid; - - for( size_t depth = 0; depth < nvertices; depth++ ) { - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); - - utils::debugPrint( "-- Depth " + std::to_string( depth + 1 ) + ":\n" ); - utils::printSparseVector( x, "x" ); - utils::printSparseVector( y, "y" ); - - bool all_visited = true; - rc = rc ? rc : grb::foldl( all_visited, y, bool_monoid ); - - if( all_visited ) { - // If all vertices are discovered, stop - utils::debugPrint( "Took " + std::to_string( depth + 1 ) + " steps to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - total_steps = depth + 1; - return rc; - } - - std::swap( x, y ); - } - - // Maximum number of iteration passed, not every vertex has been discovered - utils::debugPrint( "A full exploration is not possible on this graph. " - "Some vertices are not reachable from the given root: " + - std::to_string( root ) + "\n" ); - return rc; - } - - template< typename D > - RC bfs_steps_per_vertex( size_t & total_steps, grb::Vector< size_t > & steps_per_vertex, const Matrix< D > & A, size_t root ) { + template< typename D, bool compute_steps_per_vertex = true > + grb::RC bfs( const Matrix< D > & A, size_t root, size_t & total_steps, grb::Vector< size_t > & steps_per_vertex ) { grb::RC rc = grb::RC::SUCCESS; const size_t nvertices = grb::nrows( A ); std::cout << "Running BFS from " << root << " on " << nvertices << " vertices." << std::endl; total_steps = ULONG_MAX; - grb::Vector< bool > x( nvertices ), y( nvertices ), previous_x( nvertices ); + grb::Vector< bool > x( nvertices ), y( nvertices ); grb::set( x, false ); grb::setElement( x, true, root ); grb::set( y, x ); - grb::set( previous_x, false ); - utils::printSparseVector( x, "X - initial" ); - utils::printSparseVector( y, "Y - initial" ); - - grb::resize( steps_per_vertex, nvertices ); - grb::set( steps_per_vertex, ULONG_MAX, grb::Phase::EXECUTE ); - grb::setElement( steps_per_vertex, 0UL, root ); - utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); utils::printSparseMatrix( A, "A" ); + utils::printSparseVector( x, "X - initial" ); + utils::printSparseVector( y, "Y - initial" ); - grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true > bool_monoid; - - grb::Semiring< grb::operators::right_assign_if< size_t >, grb::operators::min< size_t >, grb::identities::one, grb::identities::zero > dist_semiring; + if( compute_steps_per_vertex ) { + // Fill the steps_per_vertex vector with ULONG_MAX, except for the root vertex which is set to 0 + grb::resize( steps_per_vertex, nvertices ); + grb::set( steps_per_vertex, ULONG_MAX, grb::Phase::EXECUTE ); + grb::setElement( steps_per_vertex, 0UL, root ); + utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); + } for( size_t depth = 0; depth < nvertices; depth++ ) { - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + + { // Multiply the current frontier by the adjacency matrix + grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + } utils::debugPrint( "-- Depth " + std::to_string( depth ) + ":\n" ); utils::printSparseVector( x, "X " ); utils::printSparseVector( y, "Y" ); - grb::set( x, y ); - grb::set( previous_x, y ); - - grb::eWiseLambda( - [ &steps_per_vertex, &y, depth ]( const size_t i ) { - if( y[ i ] ) - steps_per_vertex[ i ] = std::min( steps_per_vertex[ i ], depth + 1 ); - }, - steps_per_vertex, y ); - - utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); + if( compute_steps_per_vertex ) { // Assign the current depth to the newly discovered vertices only + // This block is identical to the commented eWiseApply below + grb::Semiring< grb::operators::left_assign_if< size_t >, grb::operators::min< size_t >, grb::identities::infinity, grb::identities::infinity > min_semiring; + grb::eWiseMul( steps_per_vertex, y, depth + 1, steps_per_vertex, min_semiring, grb::Phase::EXECUTE ); + // grb::eWiseLambda( + // [ &steps_per_vertex, &y, depth ]( const size_t i ) { + // if( y[ i ] ) + // steps_per_vertex[ i ] = std::min( steps_per_vertex[ i ], depth + 1 ); + // }, + // steps_per_vertex, y ); + utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); + } - bool all_visited = true; - rc = rc ? rc : grb::foldl( all_visited, y, bool_monoid ); + { // Check if all vertices have been discovered, equivalent of an std::all on the frontier + bool all_visited = true; + rc = rc ? rc : grb::foldl( all_visited, y, grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true >() ); - if( all_visited ) { - // If all vertices are discovered, stop - utils::debugPrint( "Took " + std::to_string( depth + 1 ) + " steps to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - total_steps = depth + 1; - return rc; + if( all_visited ) { + // If all vertices are discovered, stop + utils::debugPrint( "Took " + std::to_string( depth + 1 ) + " steps to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + total_steps = depth + 1; + return rc; + } } + // Swap the frontier, avoid a copy std::swap( x, y ); } @@ -261,6 +221,12 @@ namespace grb { return rc; } + template< typename D > + grb::RC bfs( const Matrix< D > & A, size_t root, size_t & total_steps ) { + grb::Vector< size_t > steps_per_vertex( 0 ); + return bfs< D, false >( A, root, total_steps, steps_per_vertex ); + } + } // namespace algorithms } // namespace grb diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 2f0a425f6..ced2af44a 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -39,7 +39,7 @@ struct input_t { grb::Matrix< T > A; size_t root; size_t expected_total_steps; - bool steps_per_vertex; + bool compute_steps_per_vertex; grb::Vector< size_t > expected_steps_per_vertex; }; @@ -53,13 +53,18 @@ template< typename T > void grbProgram( const input_t< T > & input, output_t & output ) { std::cout << std::endl << "Running BFS" << std::endl; grb::utils::Timer timer; - size_t total_steps = 0; + size_t total_steps = ULONG_MAX; + grb::Vector< size_t > steps_per_vertex( grb::nrows( input.A ), 0UL ); timer.reset(); - output.rc = output.rc ? output.rc : grb::algorithms::bfs_steps( total_steps, input.A, input.root ); + if( input.compute_steps_per_vertex ) { + grb::resize( steps_per_vertex, grb::nrows( input.A ) ); + output.rc = output.rc ? output.rc : grb::algorithms::bfs( input.A, input.root, total_steps, steps_per_vertex ); + } else { + output.rc = output.rc ? output.rc : grb::algorithms::bfs( input.A, input.root, total_steps ); + } timer.reset(); - // TODO: Add check for steps_per_vertex if( total_steps <= input.expected_total_steps ) { std::cout << "SUCCESS: total_steps = " << total_steps << " is correct" << std::endl; } else { @@ -67,12 +72,7 @@ void grbProgram( const input_t< T > & input, output_t & output ) { output.rc = grb::RC::FAILED; } - if( input.steps_per_vertex ) { - timer.reset(); - grb::Vector< size_t > steps_per_vertex( grb::nrows( input.A ) ); - output.rc = output.rc ? output.rc : grb::algorithms::bfs_steps_per_vertex( total_steps, steps_per_vertex, input.A, input.root ); - timer.reset(); - + if( input.compute_steps_per_vertex ) { // Check steps_per_vertex by comparing it with the expected one if( std::equal( input.expected_steps_per_vertex.cbegin(), input.expected_steps_per_vertex.cend(), steps_per_vertex.cbegin() ) ) { std::cout << "SUCCESS: steps_per_vertex is correct" << std::endl; From 15e84eb5a9e060efcf48f6713fd1faf56bb59668 Mon Sep 17 00:00:00 2001 From: byjtew Date: Mon, 5 Jun 2023 10:56:12 +0200 Subject: [PATCH 10/22] New test case for BFS: Impossible exploration --- tests/smoke/bfs.cpp | 79 ++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index ced2af44a..6fa4ffcc5 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -78,6 +78,10 @@ void grbProgram( const input_t< T > & input, output_t & output ) { std::cout << "SUCCESS: steps_per_vertex is correct" << std::endl; } else { std::cerr << "FAILED: steps_per_vertex is incorrect" << std::endl; + std::cerr << "steps_per_vertex != expected_steps_per_vertex" << std::endl; + for( size_t i = 0; i < grb::nrows( input.A ); i++ ) + std::cerr << std::string(3, ' ') << steps_per_vertex[ i ] << " | " << input.expected_steps_per_vertex[ i ] << std::endl; + output.rc = grb::RC::FAILED; } } @@ -109,9 +113,9 @@ int main( int argc, char ** argv ) { * | \ * 2 3 * - * => 1 step(s) to reach all nodes, root = 0 + * => 1 step(s) to reach all nodes */ - { // Directed version, pattern matrix + { // Directed version, pattern matrix, root = 0 std::cout << "-- Running test on A1 (directed, non-pattern)" << std::endl; size_t expected_total_steps = 1; size_t root = 0; @@ -136,15 +140,17 @@ int main( int argc, char ** argv ) { /** Matrix A2: * * Schema: - * 0 ----- 1 + * 0 ----- 2 ----- 3 * | * | * | - * 2 ----- 3 + * 1 * - * => 2 step(s) to reach all nodes, root = 0 */ - { // Directed version, pattern matrix + { /* + * Directed version, pattern matrix, root = 0 + * => 2 step(s) to reach all nodes + */ std::cout << "-- Running test on A2 (directed, pattern)" << std::endl; size_t expected_total_steps = 2; size_t root = 0; @@ -169,24 +175,23 @@ int main( int argc, char ** argv ) { /** Matrix A3: * * Schema: - * 0 ----- 1 - * | - * | - * | - * 2 ----- 3 + * + * 0 ----- 1 ----- 2 ----- 3 * - * => 3 step(s) to reach all nodes, root = 0 */ - { // Directed version, non-pattern matrix + { /* + * Directed version, non-pattern matrix, root = 0 + * => 3 step(s) to reach all nodes + */ std::cout << "-- Running test on A3 (directed, non-pattern: int)" << std::endl; size_t expected_total_steps = 3; size_t root = 0; grb::Matrix< int > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 3 } }; - std::vector< size_t > A_cols { { 1, 3, 2 } }; + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; std::vector< int > A_values( A_rows.size(), 1 ); grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; + std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; input_t< int > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); @@ -199,15 +204,42 @@ int main( int argc, char ** argv ) { } std::cout << std::endl; } - { // Directed version, pattern matrix + { /* + * Directed version, pattern matrix, root = 0 + * => 3 step(s) to reach all nodes + */ std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; size_t expected_total_steps = 3; size_t root = 0; grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 3 } }; - std::vector< size_t > A_cols { { 1, 3, 2 } }; + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; + std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + { /* + * Directed version, pattern matrix, root = 3 + * => impossible to reach all nodes + */ + std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; + size_t expected_total_steps = ULONG_MAX; + size_t root = 3; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { ULONG_MAX, ULONG_MAX, ULONG_MAX, 0 }; input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); @@ -230,9 +262,12 @@ int main( int argc, char ** argv ) { * / | * 2 ----- 3 * - * => 3 step(s) to reach all nodes, but contains a cycle, root = 0 + * Note: Contains one cycle */ - { // Directed version, pattern matrix + { /* + * Directed version, pattern matrix, root = 0 + * => 3 step(s) to reach all nodes + */ std::cout << "-- Running test on A4 (directed, pattern, one cycle)" << std::endl; size_t expected_total_steps = 3; size_t root = 0; From f972635ccf9f13c87b3835ab5a911c1481c998e3 Mon Sep 17 00:00:00 2001 From: byjtew Date: Mon, 5 Jun 2023 10:59:09 +0200 Subject: [PATCH 11/22] Skipping BFS hardcoded tests if an input is given --- tests/smoke/bfs.cpp | 381 ++++++++++++++++++++++---------------------- 1 file changed, 190 insertions(+), 191 deletions(-) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 6fa4ffcc5..0e0ee0e49 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -79,9 +79,9 @@ void grbProgram( const input_t< T > & input, output_t & output ) { } else { std::cerr << "FAILED: steps_per_vertex is incorrect" << std::endl; std::cerr << "steps_per_vertex != expected_steps_per_vertex" << std::endl; - for( size_t i = 0; i < grb::nrows( input.A ); i++ ) - std::cerr << std::string(3, ' ') << steps_per_vertex[ i ] << " | " << input.expected_steps_per_vertex[ i ] << std::endl; - + for( size_t i = 0; i < grb::nrows( input.A ); i++ ) + std::cerr << std::string( 3, ' ' ) << steps_per_vertex[ i ] << " | " << input.expected_steps_per_vertex[ i ] << std::endl; + output.rc = grb::RC::FAILED; } } @@ -104,193 +104,7 @@ int main( int argc, char ** argv ) { std::string file_to_test( test_on_file ? argv[ 1 ] : "" ); size_t expected_file_triangles = test_on_file ? std::stoul( argv[ 2 ] ) : 0; - /** Matrix A1: - * - * Schema: - * 0 ----- 1 - * | \ - * | \ - * | \ - * 2 3 - * - * => 1 step(s) to reach all nodes - */ - { // Directed version, pattern matrix, root = 0 - std::cout << "-- Running test on A1 (directed, non-pattern)" << std::endl; - size_t expected_total_steps = 1; - size_t root = 0; - grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 0, 0 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 1 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; - } - - /** Matrix A2: - * - * Schema: - * 0 ----- 2 ----- 3 - * | - * | - * | - * 1 - * - */ - { /* - * Directed version, pattern matrix, root = 0 - * => 2 step(s) to reach all nodes - */ - std::cout << "-- Running test on A2 (directed, pattern)" << std::endl; - size_t expected_total_steps = 2; - size_t root = 0; - grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 0, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 2 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; - } - - /** Matrix A3: - * - * Schema: - * - * 0 ----- 1 ----- 2 ----- 3 - * - */ - { /* - * Directed version, non-pattern matrix, root = 0 - * => 3 step(s) to reach all nodes - */ - std::cout << "-- Running test on A3 (directed, non-pattern: int)" << std::endl; - size_t expected_total_steps = 3; - size_t root = 0; - grb::Matrix< int > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; - std::vector< int > A_values( A_rows.size(), 1 ); - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; - input_t< int > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; - } - { /* - * Directed version, pattern matrix, root = 0 - * => 3 step(s) to reach all nodes - */ - std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; - size_t expected_total_steps = 3; - size_t root = 0; - grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; - } - { /* - * Directed version, pattern matrix, root = 3 - * => impossible to reach all nodes - */ - std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; - size_t expected_total_steps = ULONG_MAX; - size_t root = 3; - grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { ULONG_MAX, ULONG_MAX, ULONG_MAX, 0 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; - } - - /** Matrix A4: - * - * Schema: - * 0 ----- 1 - * / | - * / | - * / | - * 2 ----- 3 - * - * Note: Contains one cycle - */ - { /* - * Directed version, pattern matrix, root = 0 - * => 3 step(s) to reach all nodes - */ - std::cout << "-- Running test on A4 (directed, pattern, one cycle)" << std::endl; - size_t expected_total_steps = 3; - size_t root = 0; - grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; - std::vector< size_t > A_cols { { 1, 3, 1, 2 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; - } - - /** Given matrix in input **/ - if( test_on_file ) { + if( test_on_file ) { // Test on a file std::cout << "-- Running test on file " << file_to_test << std::endl; // Read matrix from file as a pattern matrix (i.e. no values) @@ -315,8 +129,193 @@ int main( int argc, char ** argv ) { std::cerr << "Test failed: rc = " << output.rc << std::endl; return output.rc; } - } + } else { + + /** Matrix A1: + * + * Schema: + * 0 ----- 1 + * | \ + * | \ + * | \ + * 2 3 + * + * => 1 step(s) to reach all nodes + */ + { // Directed version, pattern matrix, root = 0 + std::cout << "-- Running test on A1 (directed, non-pattern)" << std::endl; + size_t expected_total_steps = 1; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 0 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 1 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + /** Matrix A2: + * + * Schema: + * 0 ----- 2 ----- 3 + * | + * | + * | + * 1 + * + */ + { /* + * Directed version, pattern matrix, root = 0 + * => 2 step(s) to reach all nodes + */ + std::cout << "-- Running test on A2 (directed, pattern)" << std::endl; + size_t expected_total_steps = 2; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 0, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 2 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + /** Matrix A3: + * + * Schema: + * + * 0 ----- 1 ----- 2 ----- 3 + * + */ + { /* + * Directed version, non-pattern matrix, root = 0 + * => 3 step(s) to reach all nodes + */ + std::cout << "-- Running test on A3 (directed, non-pattern: int)" << std::endl; + size_t expected_total_steps = 3; + size_t root = 0; + grb::Matrix< int > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< int > A_values( A_rows.size(), 1 ); + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; + input_t< int > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + { /* + * Directed version, pattern matrix, root = 0 + * => 3 step(s) to reach all nodes + */ + std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; + size_t expected_total_steps = 3; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + { /* + * Directed version, pattern matrix, root = 3 + * => impossible to reach all nodes + */ + std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; + size_t expected_total_steps = ULONG_MAX; + size_t root = 3; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 2, 3 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { ULONG_MAX, ULONG_MAX, ULONG_MAX, 0 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + + /** Matrix A4: + * + * Schema: + * 0 ----- 1 + * / | + * / | + * / | + * 2 ----- 3 + * + * Note: Contains one cycle + */ + { /* + * Directed version, pattern matrix, root = 0 + * => 3 step(s) to reach all nodes + */ + std::cout << "-- Running test on A4 (directed, pattern, one cycle)" << std::endl; + size_t expected_total_steps = 3; + size_t root = 0; + grb::Matrix< void > A( 4, 4 ); + std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; + std::vector< size_t > A_cols { { 1, 3, 1, 2 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; + input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + 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 = " << output.rc << std::endl; + return output.rc; + } + std::cout << std::endl; + } + } std::cout << "Test OK" << std::endl; return 0; From 6cc98743766a0bf77aff6d95de3e9a8dae3b140f Mon Sep 17 00:00:00 2001 From: byjtew Date: Mon, 5 Jun 2023 11:12:13 +0200 Subject: [PATCH 12/22] header typo fix --- include/graphblas/algorithms/bfs.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 1f748a5b4..cb6b387c1 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -24,8 +24,8 @@ * @date: May 26th, 2023 */ -#ifndef _H_GRB_TRIANGLE_ENUMERATION -#define _H_GRB_TRIANGLE_ENUMERATION +#ifndef _H_GRB_BFS +#define _H_GRB_BFS #include #include @@ -231,4 +231,4 @@ namespace grb { } // namespace grb -#endif // _H_GRB_TRIANGLE_ENUMERATION +#endif // _H_GRB_BFS From bcf15e17a661f70ed38809c083735042c5303714 Mon Sep 17 00:00:00 2001 From: byjtew Date: Tue, 6 Jun 2023 15:23:26 +0200 Subject: [PATCH 13/22] Refactoring BFS for levels and parents algos --- include/graphblas/algorithms/bfs.hpp | 118 ++++++++++++++------------ tests/smoke/CMakeLists.txt | 2 +- tests/smoke/bfs.cpp | 119 ++++++++++++++------------- 3 files changed, 130 insertions(+), 109 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index cb6b387c1..03aa67f4a 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -126,17 +126,29 @@ namespace grb { void printSparseVector( const grb::Vector< D > & v, const std::string & name ) { (void)v; (void)name; - #ifdef _DEBUG grb::wait( v ); std::cout << "Vector \"" << name << "\" (" << grb::size( v ) << "):" << std::endl << "[ "; 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 { - for( const std::pair< size_t, D > & pair : v ) - std::cout << pair.second << " "; - std::cout << " ]" << std::endl; + size_t nnz_idx = 0; + auto it = v.cbegin(); + for( size_t i = 0; i < grb::size( v ); i++ ) { + if( nnz_idx < grb::nnz( v ) && i == it->first ) { + std::cout << it->second << " "; + nnz_idx++; + if( nnz_idx < grb::nnz( v ) ) + ++it; + } else { + std::cout << "_ "; + } + } } + std::cout << " ]" << std::endl; #endif } @@ -147,66 +159,68 @@ namespace grb { } } // namespace utils - template< typename D, bool compute_steps_per_vertex = true > - grb::RC bfs( const Matrix< D > & A, size_t root, size_t & total_steps, grb::Vector< size_t > & steps_per_vertex ) { + template< typename D > + grb::RC bfs( const Matrix< D > & A, size_t root, size_t & max_level, bool compute_levels, grb::Vector< size_t > & levels, bool compute_parents, grb::Vector< size_t > & parents ) { grb::RC rc = grb::RC::SUCCESS; const size_t nvertices = grb::nrows( A ); std::cout << "Running BFS from " << root << " on " << nvertices << " vertices." << std::endl; - total_steps = ULONG_MAX; - grb::Vector< bool > x( nvertices ), y( nvertices ); + max_level = std::numeric_limits< size_t >::max(); + grb::Vector< bool > x( nvertices, nvertices ), y( nvertices, nvertices ); grb::set( x, false ); grb::setElement( x, true, root ); grb::set( y, x ); utils::printSparseMatrix( A, "A" ); - utils::printSparseVector( x, "X - initial" ); - utils::printSparseVector( y, "Y - initial" ); - - if( compute_steps_per_vertex ) { - // Fill the steps_per_vertex vector with ULONG_MAX, except for the root vertex which is set to 0 - grb::resize( steps_per_vertex, nvertices ); - grb::set( steps_per_vertex, ULONG_MAX, grb::Phase::EXECUTE ); - grb::setElement( steps_per_vertex, 0UL, root ); - utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); - } + utils::printSparseVector( x, "x" ); - for( size_t depth = 0; depth < nvertices; depth++ ) { + if( compute_levels ) { + grb::resize( levels, nvertices ); + grb::set( levels, std::numeric_limits< size_t >::max() ); + grb::setElement( levels, 0UL, root ); + utils::printSparseVector( levels, "levels" ); + } + if( compute_parents ) { + grb::resize( parents, nvertices ); + grb::set( parents, std::numeric_limits< size_t >::max() ); + grb::setElement( parents, root, root ); + utils::printSparseVector( parents, "parents" ); + // TODO: + } - { // Multiply the current frontier by the adjacency matrix - grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + for( size_t level = 0; level < nvertices; level++ ) { + utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); + + // Multiply the current frontier by the adjacency matrix + grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + + utils::printSparseVector( x, "x " ); + utils::printSparseVector( y, "y" ); + + if( compute_levels ) { // Assign the current level to the newly discovered vertices only + grb::eWiseLambda( + [ &levels, &y, level ]( const size_t i ) { + if( y[ i ] ) + levels[ i ] = std::min( levels[ i ], level + 1 ); + }, + levels, y ); + utils::printSparseVector( levels, "levels" ); } - - utils::debugPrint( "-- Depth " + std::to_string( depth ) + ":\n" ); - utils::printSparseVector( x, "X " ); - utils::printSparseVector( y, "Y" ); - - if( compute_steps_per_vertex ) { // Assign the current depth to the newly discovered vertices only - // This block is identical to the commented eWiseApply below - grb::Semiring< grb::operators::left_assign_if< size_t >, grb::operators::min< size_t >, grb::identities::infinity, grb::identities::infinity > min_semiring; - grb::eWiseMul( steps_per_vertex, y, depth + 1, steps_per_vertex, min_semiring, grb::Phase::EXECUTE ); - // grb::eWiseLambda( - // [ &steps_per_vertex, &y, depth ]( const size_t i ) { - // if( y[ i ] ) - // steps_per_vertex[ i ] = std::min( steps_per_vertex[ i ], depth + 1 ); - // }, - // steps_per_vertex, y ); - utils::printSparseVector( steps_per_vertex, "steps_per_vertex" ); + if( compute_parents ) { + // TODO: } - { // Check if all vertices have been discovered, equivalent of an std::all on the frontier - bool all_visited = true; - rc = rc ? rc : grb::foldl( all_visited, y, grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true >() ); - - if( all_visited ) { - // If all vertices are discovered, stop - utils::debugPrint( "Took " + std::to_string( depth + 1 ) + " steps to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - total_steps = depth + 1; - return rc; - } + // Check if all vertices have been discovered, equivalent of an std::all on the frontier + bool all_visited = true; + rc = rc ? rc : grb::foldl( all_visited, y, grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true >() ); + if( all_visited ) { + // If all vertices are discovered, stop + utils::debugPrint( "Explored " + std::to_string( level + 1 ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + max_level = level + 1; + return rc; } // Swap the frontier, avoid a copy @@ -222,9 +236,9 @@ namespace grb { } template< typename D > - grb::RC bfs( const Matrix< D > & A, size_t root, size_t & total_steps ) { - grb::Vector< size_t > steps_per_vertex( 0 ); - return bfs< D, false >( A, root, total_steps, steps_per_vertex ); + grb::RC bfs( const Matrix< D > & A, size_t root, size_t & max_level ) { + grb::Vector< size_t > unusued_vec( 0 ); + return bfs( A, root, max_level, false, unusued_vec, false, unusued_vec ); } } // namespace algorithms diff --git a/tests/smoke/CMakeLists.txt b/tests/smoke/CMakeLists.txt index b2ed80bc4..2ddd8d334 100644 --- a/tests/smoke/CMakeLists.txt +++ b/tests/smoke/CMakeLists.txt @@ -94,7 +94,7 @@ add_grb_executables( from_mpi_launch_simple_pagerank_broadcast simple_pagerank_b ) add_grb_executables( bfs bfs.cpp - BACKENDS reference reference_omp bsp1d hybrid hyperdags nonblocking + BACKENDS reference reference_omp hyperdags nonblocking ADDITIONAL_LINK_LIBRARIES test_utils ) diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 0e0ee0e49..570974904 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -34,13 +34,15 @@ grb::Vector< size_t > stdVectorToGrbVector( const std::vector< size_t > & in ) { return out; } -template< typename T = void > +template< typename T > struct input_t { grb::Matrix< T > A; size_t root; - size_t expected_total_steps; - bool compute_steps_per_vertex; - grb::Vector< size_t > expected_steps_per_vertex; + size_t expected_max_level; + bool compute_levels; + const grb::Vector< size_t > & expected_levels; + bool compute_parents; + const grb::Vector< size_t > & expected_parents; }; struct output_t { @@ -50,41 +52,40 @@ struct output_t { }; template< typename T > -void grbProgram( const input_t< T > & input, output_t & output ) { +void grbProgram( const struct input_t< T > & input, struct output_t & output ) { std::cout << std::endl << "Running BFS" << std::endl; grb::utils::Timer timer; - size_t total_steps = ULONG_MAX; - grb::Vector< size_t > steps_per_vertex( grb::nrows( input.A ), 0UL ); + size_t max_level; + grb::Vector< size_t > levels( grb::nrows( input.A ), 0UL ), parents( grb::nrows( input.A ), 0UL ); timer.reset(); - if( input.compute_steps_per_vertex ) { - grb::resize( steps_per_vertex, grb::nrows( input.A ) ); - output.rc = output.rc ? output.rc : grb::algorithms::bfs( input.A, input.root, total_steps, steps_per_vertex ); - } else { - output.rc = output.rc ? output.rc : grb::algorithms::bfs( input.A, input.root, total_steps ); - } + output.rc = output.rc ? output.rc : grb::algorithms::bfs( input.A, input.root, max_level, input.compute_levels, levels, input.compute_parents, parents ); timer.reset(); - if( total_steps <= input.expected_total_steps ) { - std::cout << "SUCCESS: total_steps = " << total_steps << " is correct" << std::endl; + if( max_level <= input.expected_max_level ) { + std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; } else { - std::cerr << "FAILED: expected maximum " << input.expected_total_steps << " total_steps but got " << total_steps << std::endl; + std::cerr << "FAILED: expected maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; output.rc = grb::RC::FAILED; + return; } - if( input.compute_steps_per_vertex ) { - // Check steps_per_vertex by comparing it with the expected one - if( std::equal( input.expected_steps_per_vertex.cbegin(), input.expected_steps_per_vertex.cend(), steps_per_vertex.cbegin() ) ) { - std::cout << "SUCCESS: steps_per_vertex is correct" << std::endl; + if( input.compute_levels ) { + // Check levels by comparing it with the expected one + if( std::equal( input.expected_levels.cbegin(), input.expected_levels.cend(), levels.cbegin() ) ) { + std::cout << "SUCCESS: expected_levels is correct" << std::endl; } else { - std::cerr << "FAILED: steps_per_vertex is incorrect" << std::endl; - std::cerr << "steps_per_vertex != expected_steps_per_vertex" << std::endl; + std::cerr << "FAILED: levels is incorrect" << std::endl; + std::cerr << "levels != expected_levels" << std::endl; for( size_t i = 0; i < grb::nrows( input.A ); i++ ) - std::cerr << std::string( 3, ' ' ) << steps_per_vertex[ i ] << " | " << input.expected_steps_per_vertex[ i ] << std::endl; - + std::cerr << std::string( 3, ' ' ) << levels[ i ] << " | " << input.expected_levels[ i ] << std::endl; output.rc = grb::RC::FAILED; + return; } } + if( input.compute_parents ) { + // TODO: + } } int main( int argc, char ** argv ) { @@ -96,15 +97,17 @@ int main( int argc, char ** argv ) { std::cout << "Test executable: " << argv[ 0 ] << std::endl; // Check if we are testing on a file - if( argc != 1 && argc != 3 ) { - std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ ]" << std::endl; + if( argc != 1 && argc != 4 ) { + std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ ]" << std::endl; return 1; } - bool test_on_file = argc == 3; - std::string file_to_test( test_on_file ? argv[ 1 ] : "" ); - size_t expected_file_triangles = test_on_file ? std::stoul( argv[ 2 ] ) : 0; + bool test_on_file = ( argc == 4 ); if( test_on_file ) { // Test on a file + std::string file_to_test( argv[ 1 ] ); + size_t root = std::stoul( argv[ 2 ] ); + size_t expected_max_level = std::stoul( argv[ 3 ] ); + std::cout << "-- Running test on file " << file_to_test << std::endl; // Read matrix from file as a pattern matrix (i.e. no values) @@ -118,10 +121,9 @@ int main( int argc, char ** argv ) { } std::cout << "Matrix read successfully" << std::endl; - // TODO: Find a way to ask the steps_per_vertex to the user - input_t< void > input { A, 0, expected_file_triangles, false, { 0 } }; + input_t< void > input { A, root, expected_max_level, false, { 0 }, false, { 0 } }; output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { std::cerr << "ERROR during execution of file " << file_to_test << ": rc = " << bench_rc << std::endl; return bench_rc; @@ -144,16 +146,16 @@ int main( int argc, char ** argv ) { */ { // Directed version, pattern matrix, root = 0 std::cout << "-- Running test on A1 (directed, non-pattern)" << std::endl; - size_t expected_total_steps = 1; + size_t expected_max_level = 1; size_t root = 0; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 0 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 1 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + std::vector< size_t > expected_levels { 0, 1, 1, 1 }; + input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, { 0 } }; output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; return bench_rc; @@ -179,16 +181,17 @@ int main( int argc, char ** argv ) { * => 2 step(s) to reach all nodes */ std::cout << "-- Running test on A2 (directed, pattern)" << std::endl; - size_t expected_total_steps = 2; + size_t expected_max_level = 2; size_t root = 0; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 1, 2 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + std::vector< size_t > expected_levels { 0, 1, 1, 2 }; + std::vector< size_t > expected_parents { 0, 0, 0, 2 }; + input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; return bench_rc; @@ -211,17 +214,18 @@ int main( int argc, char ** argv ) { * => 3 step(s) to reach all nodes */ std::cout << "-- Running test on A3 (directed, non-pattern: int)" << std::endl; - size_t expected_total_steps = 3; + size_t expected_max_level = 3; size_t root = 0; grb::Matrix< int > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; std::vector< int > A_values( A_rows.size(), 1 ); grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; - input_t< int > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + std::vector< size_t > expected_levels { 0, 1, 2, 3 }; + std::vector< size_t > expected_parents { 0, 0, 1, 2 }; + input_t< int > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; return bench_rc; @@ -236,16 +240,17 @@ int main( int argc, char ** argv ) { * => 3 step(s) to reach all nodes */ std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; - size_t expected_total_steps = 3; + size_t expected_max_level = 3; size_t root = 0; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 2, 3 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + std::vector< size_t > expected_levels { 0, 1, 2, 3 }; + std::vector< size_t > expected_parents { 0, 0, 1, 2 }; + input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; return bench_rc; @@ -260,16 +265,17 @@ int main( int argc, char ** argv ) { * => impossible to reach all nodes */ std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; - size_t expected_total_steps = ULONG_MAX; + size_t expected_max_level = ULONG_MAX; size_t root = 3; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { ULONG_MAX, ULONG_MAX, ULONG_MAX, 0 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + std::vector< size_t > expected_levels { ULONG_MAX, ULONG_MAX, ULONG_MAX, 0 }; + std::vector< size_t > expected_parents { ULONG_MAX, ULONG_MAX, ULONG_MAX, 3 }; + input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; return bench_rc; @@ -296,16 +302,17 @@ int main( int argc, char ** argv ) { * => 3 step(s) to reach all nodes */ std::cout << "-- Running test on A4 (directed, pattern, one cycle)" << std::endl; - size_t expected_total_steps = 3; + size_t expected_max_level = 3; size_t root = 0; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; std::vector< size_t > A_cols { { 1, 3, 1, 2 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_steps_per_vertex { 0, 1, 3, 2 }; - input_t< void > input { A, root, expected_total_steps, true, stdVectorToGrbVector( expected_steps_per_vertex ) }; + std::vector< size_t > expected_levels { 0, 1, 3, 2 }; + std::vector< size_t > expected_parents { 0, 0, 3, 1 }; + input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1 ); + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { std::cerr << "ERROR during execution: rc = " << bench_rc << std::endl; return bench_rc; From 37a971a8a06a6ef6d619fe8b598eb651fd6f9bbb Mon Sep 17 00:00:00 2001 From: byjtew Date: Tue, 6 Jun 2023 17:31:41 +0200 Subject: [PATCH 14/22] Implement BFS(parents) using a stack --- include/graphblas/algorithms/bfs.hpp | 163 +++++++++++++++++++++------ tests/smoke/bfs.cpp | 106 ++++++++++------- 2 files changed, 193 insertions(+), 76 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 03aa67f4a..89098f895 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -128,7 +129,7 @@ namespace grb { (void)name; #ifdef _DEBUG grb::wait( v ); - std::cout << "Vector \"" << name << "\" (" << grb::size( v ) << "):" << std::endl << "[ "; + std::cout << " [ "; if( grb::size( v ) > 50 ) { std::cout << "too large to print " << std::endl; } else if( grb::nnz( v ) <= 0 ) { @@ -148,7 +149,28 @@ namespace grb { } } } - std::cout << " ]" << std::endl; + std::cout << " ] - " + << "Vector \"" << name << "\" (" << grb::size( v ) << ")" << std::endl; +#endif + } + + template< typename T > + void printStack( const std::stack< T > & stack, const std::string & name ) { + (void)stack; + (void)name; +#ifdef _DEBUG + std::cout << " [ "; + if( stack.size() > 50 ) { + std::cout << "too large to print " << std::endl; + } else { + auto tmp = stack; + for( size_t i = 0; i < stack.size(); i++ ) { + std::cout << tmp.top() << " "; + tmp.pop(); + } + } + std::cout << " ] - " + << "Stack \"" << name << "\" (" << stack.size() << ")" << std::endl; #endif } @@ -160,34 +182,25 @@ namespace grb { } // namespace utils template< typename D > - grb::RC bfs( const Matrix< D > & A, size_t root, size_t & max_level, bool compute_levels, grb::Vector< size_t > & levels, bool compute_parents, grb::Vector< size_t > & parents ) { + grb::RC bfs_levels( const Matrix< D > & A, size_t root, size_t & max_level, grb::Vector< size_t > & levels ) { grb::RC rc = grb::RC::SUCCESS; const size_t nvertices = grb::nrows( A ); - std::cout << "Running BFS from " << root << " on " << nvertices << " vertices." << std::endl; + std::cout << std::endl << "==== Running BFS (levels) from root " << root << " on " << nvertices << " vertices ====" << std::endl; max_level = std::numeric_limits< size_t >::max(); - grb::Vector< bool > x( nvertices, nvertices ), y( nvertices, nvertices ); - grb::set( x, false ); - grb::setElement( x, true, root ); - grb::set( y, x ); + grb::Vector< bool > x( nvertices ), y( nvertices ); + rc = rc ? rc : grb::set( x, false ); + rc = rc ? rc : grb::setElement( x, true, root ); + rc = rc ? rc : grb::set( y, x ); utils::printSparseMatrix( A, "A" ); utils::printSparseVector( x, "x" ); - if( compute_levels ) { - grb::resize( levels, nvertices ); - grb::set( levels, std::numeric_limits< size_t >::max() ); - grb::setElement( levels, 0UL, root ); - utils::printSparseVector( levels, "levels" ); - } - if( compute_parents ) { - grb::resize( parents, nvertices ); - grb::set( parents, std::numeric_limits< size_t >::max() ); - grb::setElement( parents, root, root ); - utils::printSparseVector( parents, "parents" ); - // TODO: - } + rc = rc ? rc : grb::resize( levels, nvertices ); + rc = rc ? rc : grb::set( levels, std::numeric_limits< size_t >::max() ); + rc = rc ? rc : grb::setElement( levels, 0UL, root ); + utils::printSparseVector( levels, "levels" ); for( size_t level = 0; level < nvertices; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); @@ -197,21 +210,18 @@ namespace grb { rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); - utils::printSparseVector( x, "x " ); + utils::printSparseVector( x, "x" ); utils::printSparseVector( y, "y" ); - if( compute_levels ) { // Assign the current level to the newly discovered vertices only - grb::eWiseLambda( - [ &levels, &y, level ]( const size_t i ) { - if( y[ i ] ) - levels[ i ] = std::min( levels[ i ], level + 1 ); - }, - levels, y ); - utils::printSparseVector( levels, "levels" ); - } - if( compute_parents ) { - // TODO: - } + // Assign the current level to the newly discovered vertices only + rc = rc ? rc : + grb::eWiseLambda( + [ &levels, &y, level ]( const size_t i ) { + if( y[ i ] ) + levels[ i ] = std::min( levels[ i ], level + 1 ); + }, + levels, y ); + utils::printSparseVector( levels, "levels" ); // Check if all vertices have been discovered, equivalent of an std::all on the frontier bool all_visited = true; @@ -236,9 +246,88 @@ namespace grb { } template< typename D > - grb::RC bfs( const Matrix< D > & A, size_t root, size_t & max_level ) { - grb::Vector< size_t > unusued_vec( 0 ); - return bfs( A, root, max_level, false, unusued_vec, false, unusued_vec ); + grb::RC bfs_parents( const Matrix< D > & A, size_t root, size_t & max_level, grb::Vector< long > & parents ) { + grb::RC rc = grb::RC::SUCCESS; + const size_t nvertices = grb::nrows( A ); + + std::cout << std::endl << "==== Running BFS (parents) from root " << root << " on " << nvertices << " vertices ====" << std::endl; + + utils::printSparseMatrix( A, "A" ); + + max_level = std::numeric_limits< size_t >::max(); + grb::Vector< bool > x( nvertices ), y( nvertices ); + rc = rc ? rc : grb::set( x, false ); + rc = rc ? rc : grb::set( y, false ); + + utils::printSparseVector( x, "x" ); + utils::printSparseVector( y, "y" ); + + rc = rc ? rc : grb::resize( parents, nvertices ); + grb::set( parents, -1L ); + grb::setElement( parents, root, root ); + utils::printSparseVector( parents, "parents" ); + + std::vector< size_t > visited; + visited.reserve( nvertices ); + std::stack< size_t > to_visit_current_level, to_visit_next_level; + to_visit_current_level.push( root ); + utils::printStack( to_visit_current_level, "to_visit_current_level" ); + + for( size_t level = 0; level < nvertices; level++ ) { + utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); + + grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + + do { + size_t visiting = to_visit_current_level.top(); + to_visit_current_level.pop(); + visited.push_back( visiting ); + utils::debugPrint( " Visiting " + std::to_string( visiting ) + "\n" ); + assert( std::find( visited.begin(), visited.end(), visiting ) != visited.end() ); + + grb::set( x, false ); + grb::setElement( x, true, visiting ); + utils::printSparseVector( x, "x" ); + grb::set( y, false ); + + // Multiply the current frontier by the adjacency matrix + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + utils::printSparseVector( y, "y" ); + + // Assign the current level to the newly discovered vertices only + rc = rc ? rc : + grb::eWiseLambda( + [ &parents, y, visiting ]( const size_t i ) { + if( y[ i ] ) + parents[ i ] = parents[ i ] < 0L ? visiting : parents[ i ]; + }, + parents, y ); + utils::printSparseVector( parents, "parents" ); + + // Add the newly discovered vertices to the frontier + for( std::pair< size_t, bool > pair : y ) + if( pair.second && std::find( visited.begin(), visited.end(), pair.first ) == visited.end() ) + to_visit_next_level.push( pair.first ); + utils::printStack( to_visit_next_level, "to_visit_next_level" ); + } while( ! to_visit_current_level.empty() ); + + if( to_visit_next_level.empty() ) { + // If all vertices are discovered, stop + utils::debugPrint( "Explored " + std::to_string( level + 1 ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + max_level = level; + return rc; + } + + std::swap( to_visit_current_level, to_visit_next_level ); + } + + // Maximum number of iteration passed, not every vertex has been discovered + utils::debugPrint( "A full exploration is not possible on this graph. " + "Some vertices are not reachable from the given root: " + + std::to_string( root ) + "\n" ); + + return rc; } } // namespace algorithms diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 570974904..44cf88226 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -27,8 +27,9 @@ #include #include -grb::Vector< size_t > stdVectorToGrbVector( const std::vector< size_t > & in ) { - grb::Vector< size_t > out( in.size() ); +template< typename T > +grb::Vector< T > stdToGrbVector( const std::vector< T > & in ) { + grb::Vector< T > out( in.size() ); for( size_t i = 0; i < in.size(); i++ ) grb::setElement( out, in[ i ], i ); return out; @@ -42,7 +43,7 @@ struct input_t { bool compute_levels; const grb::Vector< size_t > & expected_levels; bool compute_parents; - const grb::Vector< size_t > & expected_parents; + const grb::Vector< long > & expected_parents; }; struct output_t { @@ -56,21 +57,22 @@ void grbProgram( const struct input_t< T > & input, struct output_t & output ) { std::cout << std::endl << "Running BFS" << std::endl; grb::utils::Timer timer; size_t max_level; - grb::Vector< size_t > levels( grb::nrows( input.A ), 0UL ), parents( grb::nrows( input.A ), 0UL ); - timer.reset(); - output.rc = output.rc ? output.rc : grb::algorithms::bfs( input.A, input.root, max_level, input.compute_levels, levels, input.compute_parents, parents ); - timer.reset(); + if( input.compute_levels ) { + grb::Vector< size_t > levels( grb::nrows( input.A ) ); - if( max_level <= input.expected_max_level ) { - std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; - } else { - std::cerr << "FAILED: expected maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; - output.rc = grb::RC::FAILED; - return; - } + timer.reset(); + output.rc = output.rc ? output.rc : grb::algorithms::bfs_levels( input.A, input.root, max_level, levels ); + timer.reset(); + + if( max_level <= input.expected_max_level ) { + std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; + output.rc = grb::RC::FAILED; + return; + } - if( input.compute_levels ) { // Check levels by comparing it with the expected one if( std::equal( input.expected_levels.cbegin(), input.expected_levels.cend(), levels.cbegin() ) ) { std::cout << "SUCCESS: expected_levels is correct" << std::endl; @@ -83,8 +85,33 @@ void grbProgram( const struct input_t< T > & input, struct output_t & output ) { return; } } + if( input.compute_parents ) { - // TODO: + grb::Vector< long > parents( grb::nrows( input.A ) ); + + timer.reset(); + output.rc = output.rc ? output.rc : grb::algorithms::bfs_parents( input.A, input.root, max_level, parents ); + timer.reset(); + + if( max_level <= input.expected_max_level ) { + std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; + } else { + std::cerr << "FAILED: expected maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; + output.rc = grb::RC::FAILED; + return; + } + + // Check levels by comparing it with the expected one + if( std::equal( input.expected_parents.cbegin(), input.expected_parents.cend(), parents.cbegin() ) ) { + std::cout << "SUCCESS: expected_parents is correct" << std::endl; + } else { + std::cerr << "FAILED: parents is incorrect" << std::endl; + std::cerr << "parents != expected_parents" << std::endl; + for( size_t i = 0; i < grb::nrows( input.A ); i++ ) + std::cerr << std::string( 3, ' ' ) << parents[ i ] << " | " << input.expected_parents[ i ] << std::endl; + output.rc = grb::RC::FAILED; + return; + } } } @@ -145,15 +172,16 @@ int main( int argc, char ** argv ) { * => 1 step(s) to reach all nodes */ { // Directed version, pattern matrix, root = 0 - std::cout << "-- Running test on A1 (directed, non-pattern)" << std::endl; - size_t expected_max_level = 1; size_t root = 0; + std::cout << "-- Running test on A1 (directed, non-pattern, root " + std::to_string(root) + ")" << std::endl; + size_t expected_max_level = 1; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 0 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< size_t > expected_levels { 0, 1, 1, 1 }; - input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, { 0 } }; + std::vector< long > expected_parents { 0, 0, 0, 0 }; + input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -180,16 +208,16 @@ int main( int argc, char ** argv ) { * Directed version, pattern matrix, root = 0 * => 2 step(s) to reach all nodes */ - std::cout << "-- Running test on A2 (directed, pattern)" << std::endl; - size_t expected_max_level = 2; size_t root = 0; + std::cout << "-- Running test on A2 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; + size_t expected_max_level = 2; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< size_t > expected_levels { 0, 1, 1, 2 }; - std::vector< size_t > expected_parents { 0, 0, 0, 2 }; - input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; + std::vector< long > expected_parents { 0, 0, 0, 2 }; + input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -213,17 +241,17 @@ int main( int argc, char ** argv ) { * Directed version, non-pattern matrix, root = 0 * => 3 step(s) to reach all nodes */ - std::cout << "-- Running test on A3 (directed, non-pattern: int)" << std::endl; - size_t expected_max_level = 3; size_t root = 0; + std::cout << "-- Running test on A3 (directed, non-pattern: int, root " + std::to_string(root) + ")" << std::endl; + size_t expected_max_level = 3; grb::Matrix< int > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; std::vector< int > A_values( A_rows.size(), 1 ); grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); std::vector< size_t > expected_levels { 0, 1, 2, 3 }; - std::vector< size_t > expected_parents { 0, 0, 1, 2 }; - input_t< int > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; + std::vector< long > expected_parents { 0, 0, 1, 2 }; + input_t< int > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -239,16 +267,16 @@ int main( int argc, char ** argv ) { * Directed version, pattern matrix, root = 0 * => 3 step(s) to reach all nodes */ - std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; - size_t expected_max_level = 3; size_t root = 0; + std::cout << "-- Running test on A3 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; + size_t expected_max_level = 3; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< size_t > expected_levels { 0, 1, 2, 3 }; - std::vector< size_t > expected_parents { 0, 0, 1, 2 }; - input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; + std::vector< long > expected_parents { 0, 0, 1, 2 }; + input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -264,16 +292,16 @@ int main( int argc, char ** argv ) { * Directed version, pattern matrix, root = 3 * => impossible to reach all nodes */ - std::cout << "-- Running test on A3 (directed, pattern)" << std::endl; - size_t expected_max_level = ULONG_MAX; size_t root = 3; + std::cout << "-- Running test on A3 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; + size_t expected_max_level = ULONG_MAX; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< size_t > expected_levels { ULONG_MAX, ULONG_MAX, ULONG_MAX, 0 }; - std::vector< size_t > expected_parents { ULONG_MAX, ULONG_MAX, ULONG_MAX, 3 }; - input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; + std::vector< long > expected_parents { -1, -1, -1, 3 }; + input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -301,16 +329,16 @@ int main( int argc, char ** argv ) { * Directed version, pattern matrix, root = 0 * => 3 step(s) to reach all nodes */ - std::cout << "-- Running test on A4 (directed, pattern, one cycle)" << std::endl; - size_t expected_max_level = 3; size_t root = 0; + std::cout << "-- Running test on A4 (directed, pattern, one cycle, root " + std::to_string(root) + ")" << std::endl; + size_t expected_max_level = 3; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; std::vector< size_t > A_cols { { 1, 3, 1, 2 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< size_t > expected_levels { 0, 1, 3, 2 }; - std::vector< size_t > expected_parents { 0, 0, 3, 1 }; - input_t< void > input { A, root, expected_max_level, true, stdVectorToGrbVector( expected_levels ), false, stdVectorToGrbVector( expected_parents ) }; + std::vector< long > expected_parents { 0, 0, 3, 1 }; + input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { From bb56fb30accc98a64cf4e20fa5532e31344c0fd2 Mon Sep 17 00:00:00 2001 From: byjtew Date: Wed, 7 Jun 2023 09:52:07 +0200 Subject: [PATCH 15/22] BFS bugfix for nonblocking Switching from an eWiseLambda(Vector) to an eWiseAdd(Vector) --- include/graphblas/algorithms/bfs.hpp | 52 +++++++++++----------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 89098f895..7e5200131 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -155,22 +155,19 @@ namespace grb { } template< typename T > - void printStack( const std::stack< T > & stack, const std::string & name ) { - (void)stack; + void printStdVector( const std::vector< T > & vector, const std::string & name ) { + (void)vector; (void)name; #ifdef _DEBUG std::cout << " [ "; - if( stack.size() > 50 ) { + if( vector.size() > 50 ) { std::cout << "too large to print " << std::endl; } else { - auto tmp = stack; - for( size_t i = 0; i < stack.size(); i++ ) { - std::cout << tmp.top() << " "; - tmp.pop(); - } + for( const T & e : vector ) + std::cout << e << " "; } std::cout << " ] - " - << "Stack \"" << name << "\" (" << stack.size() << ")" << std::endl; + << "Vector \"" << name << "\" (" << vector.size() << ")" << std::endl; #endif } @@ -267,23 +264,19 @@ namespace grb { grb::setElement( parents, root, root ); utils::printSparseVector( parents, "parents" ); - std::vector< size_t > visited; - visited.reserve( nvertices ); - std::stack< size_t > to_visit_current_level, to_visit_next_level; - to_visit_current_level.push( root ); - utils::printStack( to_visit_current_level, "to_visit_current_level" ); + std::vector< bool > visited( nvertices, false ); + std::vector< size_t > to_visit_current_level, to_visit_next_level; + to_visit_current_level.push_back( root ); + utils::printStdVector( to_visit_current_level, "to_visit_current_level" ); for( size_t level = 0; level < nvertices; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); - grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + const grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - do { - size_t visiting = to_visit_current_level.top(); - to_visit_current_level.pop(); - visited.push_back( visiting ); + for( size_t visiting : to_visit_current_level ) { + visited[ visiting ] = true; utils::debugPrint( " Visiting " + std::to_string( visiting ) + "\n" ); - assert( std::find( visited.begin(), visited.end(), visiting ) != visited.end() ); grb::set( x, false ); grb::setElement( x, true, visiting ); @@ -296,21 +289,17 @@ namespace grb { utils::printSparseVector( y, "y" ); // Assign the current level to the newly discovered vertices only - rc = rc ? rc : - grb::eWiseLambda( - [ &parents, y, visiting ]( const size_t i ) { - if( y[ i ] ) - parents[ i ] = parents[ i ] < 0L ? visiting : parents[ i ]; - }, - parents, y ); + const grb::Semiring, grb::operators::max, grb::identities::zero, grb::identities::negative_infinity> assign_if_semiring; + rc = rc ? rc : grb::eWiseAdd( parents, y, parents, visiting, assign_if_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::eWiseAdd( parents, y, parents, visiting, assign_if_semiring, grb::Phase::EXECUTE ); utils::printSparseVector( parents, "parents" ); // Add the newly discovered vertices to the frontier for( std::pair< size_t, bool > pair : y ) - if( pair.second && std::find( visited.begin(), visited.end(), pair.first ) == visited.end() ) - to_visit_next_level.push( pair.first ); - utils::printStack( to_visit_next_level, "to_visit_next_level" ); - } while( ! to_visit_current_level.empty() ); + if( pair.second && ! visited[ pair.first ] ) + to_visit_next_level.push_back( pair.first ); + utils::printStdVector( to_visit_next_level, "to_visit_next_level" ); + } if( to_visit_next_level.empty() ) { // If all vertices are discovered, stop @@ -320,6 +309,7 @@ namespace grb { } std::swap( to_visit_current_level, to_visit_next_level ); + to_visit_next_level.clear(); } // Maximum number of iteration passed, not every vertex has been discovered From 1d03b3dfd8e7f38af723dd10a1002b161995cbd9 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Thu, 8 Jun 2023 11:28:40 +0200 Subject: [PATCH 16/22] BFS_parents optimisation using masked vxm --- include/graphblas/algorithms/bfs.hpp | 56 +++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 7e5200131..62206ba30 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -242,8 +242,12 @@ namespace grb { return rc; } - template< typename D > - grb::RC bfs_parents( const Matrix< D > & A, size_t root, size_t & max_level, grb::Vector< long > & parents ) { + template< typename D = void, typename T = long > + grb::RC bfs_parents( const Matrix< D > & A, + const size_t root, + size_t & max_level, + grb::Vector< T > & parents, + const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value, void > * const = nullptr ) { grb::RC rc = grb::RC::SUCCESS; const size_t nvertices = grb::nrows( A ); @@ -251,59 +255,61 @@ namespace grb { utils::printSparseMatrix( A, "A" ); - max_level = std::numeric_limits< size_t >::max(); + max_level = std::numeric_limits< T >::max(); grb::Vector< bool > x( nvertices ), y( nvertices ); - rc = rc ? rc : grb::set( x, false ); - rc = rc ? rc : grb::set( y, false ); - utils::printSparseVector( x, "x" ); utils::printSparseVector( y, "y" ); rc = rc ? rc : grb::resize( parents, nvertices ); - grb::set( parents, -1L ); - grb::setElement( parents, root, root ); + rc = rc ? rc : grb::set( parents, static_cast< T >( -1 ) ); + rc = rc ? rc : grb::setElement( parents, root, root ); utils::printSparseVector( parents, "parents" ); - std::vector< bool > visited( nvertices, false ); + grb::Vector< bool > not_visited( nvertices ); + rc = rc ? rc : grb::set( not_visited, true ); std::vector< size_t > to_visit_current_level, to_visit_next_level; + to_visit_next_level.reserve( nvertices ); + to_visit_current_level.reserve( nvertices ); to_visit_current_level.push_back( root ); utils::printStdVector( to_visit_current_level, "to_visit_current_level" ); for( size_t level = 0; level < nvertices; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); - const grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - for( size_t visiting : to_visit_current_level ) { - visited[ visiting ] = true; utils::debugPrint( " Visiting " + std::to_string( visiting ) + "\n" ); + rc = rc ? rc : grb::setElement( not_visited, false, visiting ); + utils::printSparseVector( not_visited, "not_visited" ); - grb::set( x, false ); - grb::setElement( x, true, visiting ); - utils::printSparseVector( x, "x" ); - grb::set( y, false ); - // Multiply the current frontier by the adjacency matrix - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); + // Explore from the current vertex only + rc = rc ? rc : grb::setElement( x, true, visiting ); // Explore from the current vertex only + utils::printSparseVector( x, "x" ); + rc = rc ? rc : grb::resize( y, 0 ); // Necessary as vxm is in-place + // Masking vxm to only explore non-explored vertices + const grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::EXECUTE ); + rc = rc ? rc : grb::setElement( x, false, visiting ); // Reset the current vertex to false utils::printSparseVector( y, "y" ); // Assign the current level to the newly discovered vertices only - const grb::Semiring, grb::operators::max, grb::identities::zero, grb::identities::negative_infinity> assign_if_semiring; - rc = rc ? rc : grb::eWiseAdd( parents, y, parents, visiting, assign_if_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::eWiseAdd( parents, y, parents, visiting, assign_if_semiring, grb::Phase::EXECUTE ); + const grb::Monoid< grb::operators::max< T >, grb::identities::negative_infinity > max_monoid; + rc = rc ? rc : grb::foldl( parents, y, visiting, max_monoid, grb::Phase::RESIZE ); + rc = rc ? rc : grb::foldl( parents, y, visiting, max_monoid, grb::Phase::EXECUTE ); utils::printSparseVector( parents, "parents" ); - // Add the newly discovered vertices to the frontier + // Add the newly discovered vertices to the stack + // Optimisation possible if an operator::index was available for( std::pair< size_t, bool > pair : y ) - if( pair.second && ! visited[ pair.first ] ) + if( pair.second ) to_visit_next_level.push_back( pair.first ); utils::printStdVector( to_visit_next_level, "to_visit_next_level" ); } if( to_visit_next_level.empty() ) { // If all vertices are discovered, stop - utils::debugPrint( "Explored " + std::to_string( level + 1 ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); max_level = level; return rc; } From 17da2cc8c6a124b9430f5cb0c47a2e281a30da26 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Thu, 8 Jun 2023 13:39:03 +0200 Subject: [PATCH 17/22] BFS_levels optimisation + bugfix --- include/graphblas/algorithms/bfs.hpp | 56 +++++++++++---------- tests/smoke/bfs.cpp | 74 +++++++++++++++++++--------- 2 files changed, 81 insertions(+), 49 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 62206ba30..e8c89c385 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -178,51 +178,49 @@ namespace grb { } } // namespace utils - template< typename D > - grb::RC bfs_levels( const Matrix< D > & A, size_t root, size_t & max_level, grb::Vector< size_t > & levels ) { + template< typename D = void, typename T = long > + grb::RC bfs_levels( + const Matrix< D > & A, + size_t root, + T & max_level, + grb::Vector< T > & levels, + const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value > * const = nullptr ) + { grb::RC rc = grb::RC::SUCCESS; const size_t nvertices = grb::nrows( A ); std::cout << std::endl << "==== Running BFS (levels) from root " << root << " on " << nvertices << " vertices ====" << std::endl; - max_level = std::numeric_limits< size_t >::max(); + max_level = static_cast< T >( -1 ); grb::Vector< bool > x( nvertices ), y( nvertices ); - rc = rc ? rc : grb::set( x, false ); rc = rc ? rc : grb::setElement( x, true, root ); - rc = rc ? rc : grb::set( y, x ); utils::printSparseMatrix( A, "A" ); utils::printSparseVector( x, "x" ); rc = rc ? rc : grb::resize( levels, nvertices ); - rc = rc ? rc : grb::set( levels, std::numeric_limits< size_t >::max() ); - rc = rc ? rc : grb::setElement( levels, 0UL, root ); + //rc = rc ? rc : grb::set( levels, static_cast< T >( -1 ) ); + rc = rc ? rc : grb::setElement( levels, static_cast< T >( 0 ), root ); utils::printSparseVector( levels, "levels" ); for( size_t level = 0; level < nvertices; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); // Multiply the current frontier by the adjacency matrix + utils::printSparseVector( x, "x" ); grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); - - utils::printSparseVector( x, "x" ); utils::printSparseVector( y, "y" ); // Assign the current level to the newly discovered vertices only - rc = rc ? rc : - grb::eWiseLambda( - [ &levels, &y, level ]( const size_t i ) { - if( y[ i ] ) - levels[ i ] = std::min( levels[ i ], level + 1 ); - }, - levels, y ); + const grb::Monoid, grb::identities::infinity > min_monoid; + rc = rc ? rc : grb::foldl( levels, y, level + 1, min_monoid, grb::Phase::RESIZE ); + rc = rc ? rc : grb::foldl( levels, y, level + 1, min_monoid, grb::Phase::EXECUTE ); utils::printSparseVector( levels, "levels" ); // Check if all vertices have been discovered, equivalent of an std::all on the frontier - bool all_visited = true; - rc = rc ? rc : grb::foldl( all_visited, y, grb::Monoid< grb::operators::logical_and< bool >, grb::identities::logical_true >() ); + bool all_visited = grb::nnz( levels ) == nvertices; if( all_visited ) { // If all vertices are discovered, stop utils::debugPrint( "Explored " + std::to_string( level + 1 ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); @@ -243,9 +241,10 @@ namespace grb { } template< typename D = void, typename T = long > - grb::RC bfs_parents( const Matrix< D > & A, - const size_t root, - size_t & max_level, + grb::RC bfs_parents( + const Matrix< D > & A, + size_t root, + T & max_level, grb::Vector< T > & parents, const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value, void > * const = nullptr ) { grb::RC rc = grb::RC::SUCCESS; @@ -255,7 +254,7 @@ namespace grb { utils::printSparseMatrix( A, "A" ); - max_level = std::numeric_limits< T >::max(); + max_level = static_cast< T >( -1 ); grb::Vector< bool > x( nvertices ), y( nvertices ); utils::printSparseVector( x, "x" ); utils::printSparseVector( y, "y" ); @@ -309,10 +308,15 @@ namespace grb { if( to_visit_next_level.empty() ) { // If all vertices are discovered, stop - utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - max_level = level; - return rc; - } + bool not_all_discovered = false; + rc = rc ? rc : grb::foldl( not_all_discovered, not_visited, grb::Monoid, grb::identities::logical_false>() ); + if( !not_all_discovered ) { + utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + max_level = level; + break; + } + + } std::swap( to_visit_current_level, to_visit_next_level ); to_visit_next_level.clear(); diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 44cf88226..6799740a5 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -35,13 +35,40 @@ grb::Vector< T > stdToGrbVector( const std::vector< T > & in ) { return out; } +template< typename D > +void printSparseVector( const grb::Vector< D > & v, const std::string & name ) { + 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; + auto it = v.cbegin(); + for( size_t i = 0; i < grb::size( v ); i++ ) { + if( nnz_idx < grb::nnz( v ) && i == it->first ) { + std::cout << it->second << " "; + nnz_idx++; + if( nnz_idx < grb::nnz( v ) ) + ++it; + } else { + std::cout << "_ "; + } + } + } + std::cout << " ] - " + << "Vector \"" << name << "\" (" << grb::size( v ) << ")" << std::endl; +} + template< typename T > struct input_t { grb::Matrix< T > A; size_t root; - size_t expected_max_level; + long expected_max_level; bool compute_levels; - const grb::Vector< size_t > & expected_levels; + const grb::Vector< long > & expected_levels; bool compute_parents; const grb::Vector< long > & expected_parents; }; @@ -56,16 +83,16 @@ template< typename T > void grbProgram( const struct input_t< T > & input, struct output_t & output ) { std::cout << std::endl << "Running BFS" << std::endl; grb::utils::Timer timer; - size_t max_level; + long max_level; if( input.compute_levels ) { - grb::Vector< size_t > levels( grb::nrows( input.A ) ); + grb::Vector< long > levels( grb::nrows( input.A ) ); timer.reset(); output.rc = output.rc ? output.rc : grb::algorithms::bfs_levels( input.A, input.root, max_level, levels ); timer.reset(); - if( max_level <= input.expected_max_level ) { + if( max_level == input.expected_max_level ) { std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; } else { std::cerr << "FAILED: expected maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; @@ -79,8 +106,8 @@ void grbProgram( const struct input_t< T > & input, struct output_t & output ) { } else { std::cerr << "FAILED: levels is incorrect" << std::endl; std::cerr << "levels != expected_levels" << std::endl; - for( size_t i = 0; i < grb::nrows( input.A ); i++ ) - std::cerr << std::string( 3, ' ' ) << levels[ i ] << " | " << input.expected_levels[ i ] << std::endl; + printSparseVector( levels, "levels" ); + printSparseVector( input.expected_levels, "expected_levels" ); output.rc = grb::RC::FAILED; return; } @@ -93,7 +120,7 @@ void grbProgram( const struct input_t< T > & input, struct output_t & output ) { output.rc = output.rc ? output.rc : grb::algorithms::bfs_parents( input.A, input.root, max_level, parents ); timer.reset(); - if( max_level <= input.expected_max_level ) { + if( max_level == input.expected_max_level ) { std::cout << "SUCCESS: max_level = " << max_level << " is correct" << std::endl; } else { std::cerr << "FAILED: expected maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; @@ -133,7 +160,7 @@ int main( int argc, char ** argv ) { if( test_on_file ) { // Test on a file std::string file_to_test( argv[ 1 ] ); size_t root = std::stoul( argv[ 2 ] ); - size_t expected_max_level = std::stoul( argv[ 3 ] ); + long expected_max_level = std::stol( argv[ 3 ] ); std::cout << "-- Running test on file " << file_to_test << std::endl; @@ -148,7 +175,7 @@ int main( int argc, char ** argv ) { } std::cout << "Matrix read successfully" << std::endl; - input_t< void > input { A, root, expected_max_level, false, { 0 }, false, { 0 } }; + input_t< void > input { A, root, expected_max_level, false, { 0L }, false, { 0L } }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -174,12 +201,12 @@ int main( int argc, char ** argv ) { { // Directed version, pattern matrix, root = 0 size_t root = 0; std::cout << "-- Running test on A1 (directed, non-pattern, root " + std::to_string(root) + ")" << std::endl; - size_t expected_max_level = 1; + long expected_max_level = 1; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 0 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_levels { 0, 1, 1, 1 }; + std::vector< long > expected_levels { 0, 1, 1, 1 }; std::vector< long > expected_parents { 0, 0, 0, 0 }; input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; @@ -210,12 +237,12 @@ int main( int argc, char ** argv ) { */ size_t root = 0; std::cout << "-- Running test on A2 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; - size_t expected_max_level = 2; + long expected_max_level = 2; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_levels { 0, 1, 1, 2 }; + std::vector< long > expected_levels { 0, 1, 1, 2 }; std::vector< long > expected_parents { 0, 0, 0, 2 }; input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; @@ -243,13 +270,13 @@ int main( int argc, char ** argv ) { */ size_t root = 0; std::cout << "-- Running test on A3 (directed, non-pattern: int, root " + std::to_string(root) + ")" << std::endl; - size_t expected_max_level = 3; + long expected_max_level = 3; grb::Matrix< int > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; std::vector< int > A_values( A_rows.size(), 1 ); grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_levels { 0, 1, 2, 3 }; + std::vector< long > expected_levels { 0, 1, 2, 3 }; std::vector< long > expected_parents { 0, 0, 1, 2 }; input_t< int > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; @@ -269,12 +296,12 @@ int main( int argc, char ** argv ) { */ size_t root = 0; std::cout << "-- Running test on A3 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; - size_t expected_max_level = 3; + long expected_max_level = 3; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_levels { 0, 1, 2, 3 }; + std::vector< long > expected_levels { 0, 1, 2, 3 }; std::vector< long > expected_parents { 0, 0, 1, 2 }; input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; @@ -294,14 +321,15 @@ int main( int argc, char ** argv ) { */ size_t root = 3; std::cout << "-- Running test on A3 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; - size_t expected_max_level = ULONG_MAX; + long expected_max_level = -1; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2 } }; std::vector< size_t > A_cols { { 1, 2, 3 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_levels { ULONG_MAX, ULONG_MAX, ULONG_MAX, 0 }; + grb::Vector< long > expected_levels( A_rows.size() ); + grb::setElement(expected_levels, 0, root); std::vector< long > expected_parents { -1, -1, -1, 3 }; - input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; + input_t< void > input { A, root, expected_max_level, true, expected_levels, true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -331,12 +359,12 @@ int main( int argc, char ** argv ) { */ size_t root = 0; std::cout << "-- Running test on A4 (directed, pattern, one cycle, root " + std::to_string(root) + ")" << std::endl; - size_t expected_max_level = 3; + long expected_max_level = 3; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; std::vector< size_t > A_cols { { 1, 3, 1, 2 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< size_t > expected_levels { 0, 1, 3, 2 }; + std::vector< long > expected_levels { 0, 1, 3, 2 }; std::vector< long > expected_parents { 0, 0, 3, 1 }; input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; From 27a8a4f6b5265d079fc1ebb0b2ca18905b16f20b Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Thu, 8 Jun 2023 15:17:23 +0200 Subject: [PATCH 18/22] BFS signatures improvements + optimisation on BFS_levels --- include/graphblas/algorithms/bfs.hpp | 155 +++++++++++++++------- tests/smoke/bfs.cpp | 189 ++++++++++++++++----------- 2 files changed, 219 insertions(+), 125 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index e8c89c385..f13821cc9 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -178,58 +178,105 @@ namespace grb { } } // namespace utils + /** + * 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 + * + * @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 level at which each vertex is reached + * @param[in] not_find_value Value to use for vertices that have not been reached (default: -1) + * @return grb::SUCCESS A call to this function never fails. + * + * \parblock + * \par Possible output values: + * -# max_level: [0, grb::nrows(A) - 1] + * -# levels: [0, grb::nrows(A) - 1] for reached vertices, not_find_value for unreached vertices + * \endparblock + * + * \warning Level 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 > - grb::RC bfs_levels( - const Matrix< D > & A, + grb::RC bfs_levels( const Matrix< D > & A, size_t root, + bool & explored_all, T & max_level, grb::Vector< T > & levels, - const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value > * const = nullptr ) - { + const T not_find_value = static_cast< T >( -1 ), + const std::enable_if< std::is_integral< T >::value && std::is_signed< T >::value > * const = nullptr ) { grb::RC rc = grb::RC::SUCCESS; const size_t nvertices = grb::nrows( A ); std::cout << std::endl << "==== Running BFS (levels) from root " << root << " on " << nvertices << " vertices ====" << std::endl; - max_level = static_cast< T >( -1 ); - grb::Vector< bool > x( nvertices ), y( nvertices ); - rc = rc ? rc : grb::setElement( x, true, root ); + { + // Frontier vectors + grb::Vector< bool > x( nvertices ), y( nvertices ); + rc = rc ? rc : grb::setElement( x, true, root ); - utils::printSparseMatrix( A, "A" ); - utils::printSparseVector( x, "x" ); + utils::printSparseMatrix( A, "A" ); + utils::printSparseVector( x, "x" ); - rc = rc ? rc : grb::resize( levels, nvertices ); - //rc = rc ? rc : grb::set( levels, static_cast< T >( -1 ) ); - rc = rc ? rc : grb::setElement( levels, static_cast< T >( 0 ), root ); - utils::printSparseVector( levels, "levels" ); + // Output vector containing the minimum level at which each vertex is reached + rc = rc ? rc : grb::resize( levels, nvertices ); + rc = rc ? rc : grb::setElement( levels, static_cast< T >( 0 ), root ); + utils::printSparseVector( levels, "levels" ); - for( size_t level = 0; level < nvertices; level++ ) { - utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); + // Vector of unvisited vertices + grb::Vector< bool > not_visited( nvertices ); + rc = rc ? rc : grb::set( not_visited, true ); + grb::setElement( not_visited, false, root ); - // Multiply the current frontier by the adjacency matrix - utils::printSparseVector( x, "x" ); - grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::vxm( y, x, A, bool_semiring, grb::Phase::EXECUTE ); - utils::printSparseVector( y, "y" ); - - // Assign the current level to the newly discovered vertices only - const grb::Monoid, grb::identities::infinity > min_monoid; - rc = rc ? rc : grb::foldl( levels, y, level + 1, min_monoid, grb::Phase::RESIZE ); - rc = rc ? rc : grb::foldl( levels, y, level + 1, min_monoid, grb::Phase::EXECUTE ); - utils::printSparseVector( levels, "levels" ); + for( size_t level = 1; level <= nvertices; level++ ) { + utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); + max_level = level; - // Check if all vertices have been discovered, equivalent of an std::all on the frontier - bool all_visited = grb::nnz( levels ) == nvertices; - if( all_visited ) { - // If all vertices are discovered, stop - utils::debugPrint( "Explored " + std::to_string( level + 1 ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - max_level = level + 1; - return rc; - } + // Multiply the current frontier by the adjacency matrix + utils::printSparseVector( x, "x" ); + utils::printSparseVector( not_visited, "not_visited" ); + grb::resize( y, 0UL ); + grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; + rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::RESIZE ); + rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::EXECUTE ); + utils::printSparseVector( y, "y" ); + + // Update not_visited vector + for( const std::pair< size_t, bool > e : y ) { + if( e.second ) + grb::setElement( not_visited, false, e.first ); + } - // Swap the frontier, avoid a copy - std::swap( x, y ); + // Assign the current level to the newly discovered vertices only + const grb::Monoid< grb::operators::min< T >, grb::identities::infinity > min_monoid; + rc = rc ? rc : grb::foldl( levels, y, level, min_monoid, grb::Phase::RESIZE ); + rc = rc ? rc : grb::foldl( levels, y, level, min_monoid, grb::Phase::EXECUTE ); + utils::printSparseVector( levels, "levels" ); + + // Check if all vertices have been discovered, equivalent of an std::all on the frontier + explored_all = grb::nnz( levels ) == nvertices; + if( explored_all ) { + // If all vertices are discovered, stop + utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + return rc; + } + bool can_continue = grb::nnz( y ) > 0; + if( ! can_continue ) { + max_level = level - 1; + // If no new vertices are discovered, stop + utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover " + std::to_string( grb::nnz( levels ) ) + " vertices.\n" ); + break; + } + + // Swap the frontier, avoid a copy + std::swap( x, y ); + } } // Maximum number of iteration passed, not every vertex has been discovered @@ -237,15 +284,24 @@ namespace grb { "Some vertices are not reachable from the given root: " + std::to_string( root ) + "\n" ); + // Fill missing values with -1 + std::vector< bool > not_visited( nvertices, true ); + for( const std::pair< size_t, T > & p : levels ) + not_visited[ p.first ] = false; + for( size_t i = 0; i < nvertices; i++ ) + if( not_visited[ i ] ) + rc = rc ? rc : grb::setElement( levels, not_find_value, i ); + return rc; } template< typename D = void, typename T = long > - grb::RC bfs_parents( - const Matrix< D > & A, + grb::RC bfs_parents( const Matrix< D > & A, size_t root, + bool & explored_all, T & max_level, grb::Vector< T > & parents, + const T not_find_value = static_cast< T >( -1 ), const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value, void > * const = nullptr ) { grb::RC rc = grb::RC::SUCCESS; const size_t nvertices = grb::nrows( A ); @@ -254,13 +310,12 @@ namespace grb { utils::printSparseMatrix( A, "A" ); - max_level = static_cast< T >( -1 ); grb::Vector< bool > x( nvertices ), y( nvertices ); utils::printSparseVector( x, "x" ); utils::printSparseVector( y, "y" ); rc = rc ? rc : grb::resize( parents, nvertices ); - rc = rc ? rc : grb::set( parents, static_cast< T >( -1 ) ); + rc = rc ? rc : grb::set( parents, not_find_value ); rc = rc ? rc : grb::setElement( parents, root, root ); utils::printSparseVector( parents, "parents" ); @@ -272,19 +327,20 @@ namespace grb { to_visit_current_level.push_back( root ); utils::printStdVector( to_visit_current_level, "to_visit_current_level" ); - for( size_t level = 0; level < nvertices; level++ ) { + max_level = 0; + for( size_t level = 1; level <= nvertices; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); + max_level++; for( size_t visiting : to_visit_current_level ) { utils::debugPrint( " Visiting " + std::to_string( visiting ) + "\n" ); rc = rc ? rc : grb::setElement( not_visited, false, visiting ); utils::printSparseVector( not_visited, "not_visited" ); - // Explore from the current vertex only rc = rc ? rc : grb::setElement( x, true, visiting ); // Explore from the current vertex only utils::printSparseVector( x, "x" ); - rc = rc ? rc : grb::resize( y, 0 ); // Necessary as vxm is in-place + rc = rc ? rc : grb::resize( y, 0 ); // Necessary as vxm is in-place // Masking vxm to only explore non-explored vertices const grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::RESIZE ); @@ -309,14 +365,15 @@ namespace grb { if( to_visit_next_level.empty() ) { // If all vertices are discovered, stop bool not_all_discovered = false; - rc = rc ? rc : grb::foldl( not_all_discovered, not_visited, grb::Monoid, grb::identities::logical_false>() ); - if( !not_all_discovered ) { + rc = rc ? rc : grb::foldl( not_all_discovered, not_visited, grb::Monoid< grb::operators::logical_or< bool >, grb::identities::logical_false >() ); + max_level--; + if( ! not_all_discovered ) { // If all vertices are discovered, stop utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - max_level = level; + explored_all = true; break; } - - } + explored_all = false; + } std::swap( to_visit_current_level, to_visit_next_level ); to_visit_next_level.clear(); diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 6799740a5..efe41f73c 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -14,18 +14,14 @@ * limitations under the License. */ -#include #include #include -#include - #include #include #include #include -#include template< typename T > grb::Vector< T > stdToGrbVector( const std::vector< T > & in ) { @@ -66,6 +62,7 @@ template< typename T > struct input_t { grb::Matrix< T > A; size_t root; + bool expected_explored_all; long expected_max_level; bool compute_levels; const grb::Vector< long > & expected_levels; @@ -81,21 +78,29 @@ struct output_t { template< typename T > void grbProgram( const struct input_t< T > & input, struct output_t & output ) { - std::cout << std::endl << "Running BFS" << std::endl; grb::utils::Timer timer; long max_level; + bool explored_all; if( input.compute_levels ) { grb::Vector< long > levels( grb::nrows( input.A ) ); timer.reset(); - output.rc = output.rc ? output.rc : grb::algorithms::bfs_levels( input.A, input.root, max_level, levels ); + output.rc = output.rc ? output.rc : grb::algorithms::bfs_levels( input.A, input.root, explored_all, max_level, levels ); timer.reset(); + 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 = grb::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 maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; + std::cerr << "FAILED: expected max_level " << input.expected_max_level << " but got " << max_level << std::endl; output.rc = grb::RC::FAILED; return; } @@ -117,13 +122,21 @@ void grbProgram( const struct input_t< T > & input, struct output_t & output ) { grb::Vector< long > parents( grb::nrows( input.A ) ); timer.reset(); - output.rc = output.rc ? output.rc : grb::algorithms::bfs_parents( input.A, input.root, max_level, parents ); + output.rc = output.rc ? output.rc : grb::algorithms::bfs_parents( input.A, input.root, explored_all, max_level, parents ); timer.reset(); + 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 = grb::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 maximum " << input.expected_max_level << " max_level but got " << max_level << std::endl; + std::cerr << "FAILED: expected max_level " << input.expected_max_level << " but got " << max_level << std::endl; output.rc = grb::RC::FAILED; return; } @@ -134,8 +147,8 @@ void grbProgram( const struct input_t< T > & input, struct output_t & output ) { } else { std::cerr << "FAILED: parents is incorrect" << std::endl; std::cerr << "parents != expected_parents" << std::endl; - for( size_t i = 0; i < grb::nrows( input.A ); i++ ) - std::cerr << std::string( 3, ' ' ) << parents[ i ] << " | " << input.expected_parents[ i ] << std::endl; + printSparseVector( parents, "parents" ); + printSparseVector( input.expected_parents, "expected_parents" ); output.rc = grb::RC::FAILED; return; } @@ -151,16 +164,17 @@ int main( int argc, char ** argv ) { std::cout << "Test executable: " << argv[ 0 ] << std::endl; // Check if we are testing on a file - if( argc != 1 && argc != 4 ) { - std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ ]" << std::endl; + if( argc != 1 && argc != 5 ) { + std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ ]" << std::endl; return 1; } - bool test_on_file = ( argc == 4 ); + bool test_on_file = ( argc == 5 ); if( test_on_file ) { // Test on a file std::string file_to_test( argv[ 1 ] ); size_t root = std::stoul( argv[ 2 ] ); - long expected_max_level = std::stol( argv[ 3 ] ); + bool expected_explored_all = std::stoi( argv[ 3 ] ); + long expected_max_level = std::stol( argv[ 4 ] ); std::cout << "-- Running test on file " << file_to_test << std::endl; @@ -175,7 +189,7 @@ int main( int argc, char ** argv ) { } std::cout << "Matrix read successfully" << std::endl; - input_t< void > input { A, root, expected_max_level, false, { 0L }, false, { 0L } }; + input_t< void > input { A, root, expected_explored_all, expected_max_level, false, { 0L }, false, { 0L } }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -189,18 +203,19 @@ int main( int argc, char ** argv ) { /** Matrix A1: * - * Schema: - * 0 ----- 1 - * | \ - * | \ - * | \ - * 2 3 - * - * => 1 step(s) to reach all nodes + * 2 ───── 0 ───── 1 + * │ + * │ + * │ + * 3 */ - { // Directed version, pattern matrix, root = 0 + { /* + * 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 " + std::to_string(root) + ")" << std::endl; + bool expected_explored_all = true; long expected_max_level = 1; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 0 } }; @@ -208,7 +223,7 @@ int main( int argc, char ** argv ) { grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< long > expected_levels { 0, 1, 1, 1 }; std::vector< long > expected_parents { 0, 0, 0, 0 }; - input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; + input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -223,20 +238,15 @@ int main( int argc, char ** argv ) { /** Matrix A2: * - * Schema: - * 0 ----- 2 ----- 3 - * | - * | - * | - * 1 - * + * 1 ───── 0 ───── 2 ───── 3 */ { /* * Directed version, pattern matrix, root = 0 - * => 2 step(s) to reach all nodes + * => 2 step(s) to explore all nodes */ size_t root = 0; std::cout << "-- Running test on A2 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; + bool expected_explored_all = true; long expected_max_level = 2; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 0, 2 } }; @@ -244,7 +254,7 @@ int main( int argc, char ** argv ) { grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< long > expected_levels { 0, 1, 1, 2 }; std::vector< long > expected_parents { 0, 0, 0, 2 }; - input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; + input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -259,26 +269,25 @@ int main( int argc, char ** argv ) { /** Matrix A3: * - * Schema: - * - * 0 ----- 1 ----- 2 ----- 3 - * + * 0 ───── 1 ───── 2 ───── 3 + * └───────────────────────┘ */ { /* * Directed version, non-pattern matrix, root = 0 - * => 3 step(s) to reach all nodes + * => 3 step(s) to explore all nodes */ size_t root = 0; std::cout << "-- Running test on A3 (directed, non-pattern: int, root " + std::to_string(root) + ")" << std::endl; + bool expected_explored_all = true; long expected_max_level = 3; grb::Matrix< int > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; + std::vector< size_t > A_cols { { 1, 2, 3, 0 } }; std::vector< int > A_values( A_rows.size(), 1 ); grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); std::vector< long > expected_levels { 0, 1, 2, 3 }; std::vector< long > expected_parents { 0, 0, 1, 2 }; - input_t< int > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; + input_t< int > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -290,20 +299,54 @@ int main( int argc, char ** argv ) { } 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 " + std::to_string(root) + ")" << std::endl; + bool expected_explored_all = true; + long expected_max_level = 2; + grb::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 } }; + grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); + std::vector< long > expected_levels { 0, 1, 2, 1 }; + std::vector< long > expected_parents { 0, 0, 3, 0 }; + input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; + output_t output; + grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); + 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 = " << 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 reach all nodes + * => 3 step(s) to explore all nodes */ size_t root = 0; - std::cout << "-- Running test on A3 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; + std::cout << "-- Running test on A4 (directed, pattern, one cycle, root " + std::to_string(root) + ")" << std::endl; + bool expected_explored_all = true; long expected_max_level = 3; grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; + std::vector< size_t > A_cols { { 1, 2, 3, 1 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); std::vector< long > expected_levels { 0, 1, 2, 3 }; std::vector< long > expected_parents { 0, 0, 1, 2 }; - input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; + input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -316,20 +359,20 @@ int main( int argc, char ** argv ) { std::cout << std::endl; } { /* - * Directed version, pattern matrix, root = 3 - * => impossible to reach all nodes + * Directed version, pattern matrix, root = 1 + * => Impossible to reach vertex 0 */ - size_t root = 3; - std::cout << "-- Running test on A3 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; - long expected_max_level = -1; + size_t root = 1; + std::cout << "-- Running test on A4 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; + bool expected_explored_all = false; + long expected_max_level = 2; grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; + std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; + std::vector< size_t > A_cols { { 1, 2, 3, 1 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - grb::Vector< long > expected_levels( A_rows.size() ); - grb::setElement(expected_levels, 0, root); - std::vector< long > expected_parents { -1, -1, -1, 3 }; - input_t< void > input { A, root, expected_max_level, true, expected_levels, true, stdToGrbVector( expected_parents ) }; + std::vector< long > expected_levels { -1, 0, 1, 2 }; + std::vector< long > expected_parents { -1, 1, 1, 2 }; + input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { @@ -342,31 +385,25 @@ int main( int argc, char ** argv ) { std::cout << std::endl; } - /** Matrix A4: + /** Matrix A5: * - * Schema: - * 0 ----- 1 - * / | - * / | - * / | - * 2 ----- 3 - * - * Note: Contains one cycle + * 0 ───── 1 ──x── 2 ───── 3 */ { /* - * Directed version, pattern matrix, root = 0 - * => 3 step(s) to reach all nodes + * Undirected version, pattern matrix, root = 0 + * => Impossible to reach vertices 2 and 3 */ size_t root = 0; - std::cout << "-- Running test on A4 (directed, pattern, one cycle, root " + std::to_string(root) + ")" << std::endl; - long expected_max_level = 3; + std::cout << "-- Running test on A5 (undirected, pattern, root " + std::to_string(root) + ")" << std::endl; + bool expected_explored_all = false; + long expected_max_level = 1; grb::Matrix< void > A( 4, 4 ); std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; - std::vector< size_t > A_cols { { 1, 3, 1, 2 } }; + std::vector< size_t > A_cols { { 1, 0, 3, 2 } }; grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { 0, 1, 3, 2 }; - std::vector< long > expected_parents { 0, 0, 3, 1 }; - input_t< void > input { A, root, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; + std::vector< long > expected_levels { 0, 1, -1, -1 }; + std::vector< long > expected_parents { 0, 0, -1, -1 }; + input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; output_t output; grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); if( bench_rc ) { From 575d0093d040a815a239f21b70a107f8a81d9066 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Tue, 4 Jul 2023 11:26:57 +0200 Subject: [PATCH 19/22] Adapt smoke test + unit-test with small cases --- include/graphblas/algorithms/bfs.hpp | 348 +++++++++++-------- tests/smoke/bfs.cpp | 437 ++++++------------------ tests/smoke/smoketests.sh | 15 +- tests/unit/CMakeLists.txt | 4 + tests/unit/bfs.cpp | 478 +++++++++++++++++++++++++++ tests/unit/unittests.sh | 6 + 6 files changed, 811 insertions(+), 477 deletions(-) create mode 100644 tests/unit/bfs.cpp diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index f13821cc9..7a7de18a7 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -36,7 +36,7 @@ #include -#define _DEBUG +//#define BFS_DEBUG namespace grb { @@ -52,26 +52,25 @@ namespace grb { (void)name; (void)os; -#ifdef _DEBUG - std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; +#ifdef BFS_DEBUG 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; + 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 @@ -86,71 +85,79 @@ namespace grb { (void)name; (void)os; -#ifdef _DEBUG - std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; +#ifdef BFS_DEBUG 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; + 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 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 ); + void printSparseMatrix( const Matrix< D > & mat, const std::string & name ) { + (void)mat; + (void)name; +#ifdef BFS_DEBUG + wait( mat ); + printSparseMatrixIterator( nrows( mat ), ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout ); +#endif } 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 ); + void printSparseMatrix< void >( const Matrix< void > & mat, const std::string & name ) { + (void)mat; + (void)name; +#ifdef BFS_DEBUG + wait( mat ); + printSparsePatternMatrixIterator( nrows( mat ), ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout ); +#endif } template< typename D > - void printSparseVector( const grb::Vector< D > & v, const std::string & name ) { + void printSparseVector( const 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++ ) +#ifdef BFS_DEBUG + if( size( v ) > 50 ) { + 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 < grb::size( v ); i++ ) { - if( nnz_idx < grb::nnz( v ) && i == it->first ) { + 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 < grb::nnz( v ) ) + if( nnz_idx < nnz( v ) ) ++it; } else { std::cout << "_ "; } } } - std::cout << " ] - " - << "Vector \"" << name << "\" (" << grb::size( v ) << ")" << std::endl; + std::cout << "] - " + << "Vector \"" << name << "\" (" << size( v ) << ")" << std::endl; #endif } @@ -158,81 +165,85 @@ namespace grb { void printStdVector( const std::vector< T > & vector, const std::string & name ) { (void)vector; (void)name; -#ifdef _DEBUG - std::cout << " [ "; +#ifdef BFS_DEBUG if( vector.size() > 50 ) { - std::cout << "too large to print " << std::endl; - } else { - for( const T & e : vector ) - std::cout << e << " "; + return; } - std::cout << " ] - " + std::cout << " [ "; + 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 + (void)msg; + (void)os; +#ifdef BFS_DEBUG os << msg; #endif } } // namespace utils - /** - * 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 - * - * @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 level at which each vertex is reached - * @param[in] not_find_value Value to use for vertices that have not been reached (default: -1) - * @return grb::SUCCESS A call to this function never fails. - * - * \parblock - * \par Possible output values: - * -# max_level: [0, grb::nrows(A) - 1] - * -# levels: [0, grb::nrows(A) - 1] for reached vertices, not_find_value for unreached vertices - * \endparblock - * - * \warning Level 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. - */ + /** + * @brief Breadth-first search (BFS) algorithm. + */ + enum AlgorithmBFS { 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 + * + * @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 level at which each vertex is reached + * @param[in] not_find_value Value to use for vertices that have not been reached (default: -1) + * @return SUCCESS A call to this function never fails. + * + * \parblock + * \par Possible output values: + * -# max_level: [0, nrows(A) - 1] + * -# levels: [0, nrows(A) - 1] for reached vertices, not_find_value for unreached vertices + * \endparblock + * + * \warning Level 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 > - grb::RC bfs_levels( const Matrix< D > & A, + RC bfs_levels( const Matrix< D > & A, size_t root, bool & explored_all, T & max_level, - grb::Vector< T > & levels, + Vector< T > & levels, const T not_find_value = static_cast< T >( -1 ), const std::enable_if< std::is_integral< T >::value && std::is_signed< T >::value > * const = nullptr ) { - grb::RC rc = grb::RC::SUCCESS; - const size_t nvertices = grb::nrows( A ); - - std::cout << std::endl << "==== Running BFS (levels) from root " << root << " on " << nvertices << " vertices ====" << std::endl; + RC rc = RC::SUCCESS; + const size_t nvertices = nrows( A ); { // Frontier vectors - grb::Vector< bool > x( nvertices ), y( nvertices ); - rc = rc ? rc : grb::setElement( x, true, root ); + Vector< bool > x( nvertices ), y( nvertices ); + 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 : grb::resize( levels, nvertices ); - rc = rc ? rc : grb::setElement( levels, static_cast< T >( 0 ), root ); + rc = rc ? rc : resize( levels, nvertices ); + rc = rc ? rc : setElement( levels, static_cast< T >( 0 ), root ); utils::printSparseVector( levels, "levels" ); // Vector of unvisited vertices - grb::Vector< bool > not_visited( nvertices ); - rc = rc ? rc : grb::set( not_visited, true ); - grb::setElement( not_visited, false, root ); + Vector< bool > not_visited( nvertices ); + rc = rc ? rc : set( not_visited, true ); + setElement( not_visited, false, root ); for( size_t level = 1; level <= nvertices; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); @@ -241,36 +252,36 @@ namespace grb { // Multiply the current frontier by the adjacency matrix utils::printSparseVector( x, "x" ); utils::printSparseVector( not_visited, "not_visited" ); - grb::resize( y, 0UL ); - grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::EXECUTE ); + resize( y, 0UL ); + Semiring< operators::logical_or< bool >, operators::logical_and< bool >, identities::logical_false, identities::logical_true > bool_semiring; + 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 for( const std::pair< size_t, bool > e : y ) { if( e.second ) - grb::setElement( not_visited, false, e.first ); + setElement( not_visited, false, e.first ); } // Assign the current level to the newly discovered vertices only - const grb::Monoid< grb::operators::min< T >, grb::identities::infinity > min_monoid; - rc = rc ? rc : grb::foldl( levels, y, level, min_monoid, grb::Phase::RESIZE ); - rc = rc ? rc : grb::foldl( levels, y, level, min_monoid, grb::Phase::EXECUTE ); + const Monoid< operators::min< T >, identities::infinity > min_monoid; + 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 = grb::nnz( levels ) == nvertices; + explored_all = nnz( levels ) == nvertices; if( explored_all ) { // If all vertices are discovered, stop utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); return rc; } - bool can_continue = grb::nnz( y ) > 0; + bool can_continue = nnz( y ) > 0; if( ! can_continue ) { max_level = level - 1; // If no new vertices are discovered, stop - utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover " + std::to_string( grb::nnz( levels ) ) + " vertices.\n" ); + utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover " + std::to_string( nnz( levels ) ) + " vertices.\n" ); break; } @@ -290,37 +301,59 @@ namespace grb { not_visited[ p.first ] = false; for( size_t i = 0; i < nvertices; i++ ) if( not_visited[ i ] ) - rc = rc ? rc : grb::setElement( levels, not_find_value, i ); + rc = rc ? rc : setElement( levels, not_find_value, i ); return rc; } + /** + * Breadth-first search (BFS) algorithm. + * This version computes the parents of each vertex. + * + * @tparam D Matrix values type + * @tparam T Level 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] parenst Vector containing the parent from which each vertex is reached + * @param[in] not_find_value Value to use for vertices that have not been reached (default: -1) + * @return SUCCESS A call to this function never fails. + * + * \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 Level 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 > - grb::RC bfs_parents( const Matrix< D > & A, + RC bfs_parents( const Matrix< D > & A, size_t root, bool & explored_all, T & max_level, - grb::Vector< T > & parents, + Vector< T > & parents, const T not_find_value = static_cast< T >( -1 ), const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value, void > * const = nullptr ) { - grb::RC rc = grb::RC::SUCCESS; - const size_t nvertices = grb::nrows( A ); - - std::cout << std::endl << "==== Running BFS (parents) from root " << root << " on " << nvertices << " vertices ====" << std::endl; - + RC rc = RC::SUCCESS; + const size_t nvertices = nrows( A ); utils::printSparseMatrix( A, "A" ); - grb::Vector< bool > x( nvertices ), y( nvertices ); + Vector< bool > x( nvertices ), y( nvertices ); utils::printSparseVector( x, "x" ); utils::printSparseVector( y, "y" ); - rc = rc ? rc : grb::resize( parents, nvertices ); - rc = rc ? rc : grb::set( parents, not_find_value ); - rc = rc ? rc : grb::setElement( parents, root, root ); + rc = rc ? rc : resize( parents, nvertices ); + rc = rc ? rc : set( parents, not_find_value ); + rc = rc ? rc : setElement( parents, root, root ); utils::printSparseVector( parents, "parents" ); - grb::Vector< bool > not_visited( nvertices ); - rc = rc ? rc : grb::set( not_visited, true ); + Vector< bool > not_visited( nvertices ); + rc = rc ? rc : set( not_visited, true ); std::vector< size_t > to_visit_current_level, to_visit_next_level; to_visit_next_level.reserve( nvertices ); to_visit_current_level.reserve( nvertices ); @@ -330,43 +363,52 @@ namespace grb { max_level = 0; for( size_t level = 1; level <= nvertices; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); - max_level++; + bool discovered_one = false; for( size_t visiting : to_visit_current_level ) { - utils::debugPrint( " Visiting " + std::to_string( visiting ) + "\n" ); - rc = rc ? rc : grb::setElement( not_visited, false, visiting ); + if( not not_visited[ visiting ] ) + continue; + + utils::debugPrint( "* Visiting " + std::to_string( visiting ) + "\n" ); + rc = rc ? rc : setElement( not_visited, false, visiting ); utils::printSparseVector( not_visited, "not_visited" ); // Explore from the current vertex only - rc = rc ? rc : grb::setElement( x, true, visiting ); // Explore from the current vertex only + rc = rc ? rc : setElement( x, true, visiting ); // Explore from the current vertex only utils::printSparseVector( x, "x" ); - rc = rc ? rc : grb::resize( y, 0 ); // Necessary as vxm is in-place + rc = rc ? rc : clear( y ); // Necessary as vxm is in-place // Masking vxm to only explore non-explored vertices - const grb::Semiring< grb::operators::logical_or< bool >, grb::operators::logical_and< bool >, grb::identities::logical_false, grb::identities::logical_true > bool_semiring; - rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::RESIZE ); - rc = rc ? rc : grb::vxm( y, not_visited, x, A, bool_semiring, grb::Phase::EXECUTE ); - rc = rc ? rc : grb::setElement( x, false, visiting ); // Reset the current vertex to false + const Semiring< operators::logical_or< bool >, operators::logical_and< bool >, identities::logical_false, identities::logical_true > bool_semiring; + 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 ); + rc = rc ? rc : clear( x ); // Reset the current vertex to false utils::printSparseVector( y, "y" ); // Assign the current level to the newly discovered vertices only - const grb::Monoid< grb::operators::max< T >, grb::identities::negative_infinity > max_monoid; - rc = rc ? rc : grb::foldl( parents, y, visiting, max_monoid, grb::Phase::RESIZE ); - rc = rc ? rc : grb::foldl( parents, y, visiting, max_monoid, grb::Phase::EXECUTE ); + const Monoid< operators::max< T >, identities::negative_infinity > max_monoid; + rc = rc ? rc : foldl( parents, y, visiting, max_monoid, Phase::RESIZE ); + rc = rc ? rc : foldl( parents, y, visiting, max_monoid, Phase::EXECUTE ); utils::printSparseVector( parents, "parents" ); // Add the newly discovered vertices to the stack // Optimisation possible if an operator::index was available - for( std::pair< size_t, bool > pair : y ) - if( pair.second ) + for( std::pair< size_t, bool > pair : y ) { + if( pair.second && not_visited[ pair.first ] ) { to_visit_next_level.push_back( pair.first ); + discovered_one = true; + } + } utils::printStdVector( to_visit_next_level, "to_visit_next_level" ); } + if(discovered_one) { + max_level++; + } + if( to_visit_next_level.empty() ) { // If all vertices are discovered, stop bool not_all_discovered = false; - rc = rc ? rc : grb::foldl( not_all_discovered, not_visited, grb::Monoid< grb::operators::logical_or< bool >, grb::identities::logical_false >() ); - max_level--; + rc = rc ? rc : foldl( not_all_discovered, not_visited, Monoid< operators::logical_or< bool >, identities::logical_false >() ); if( ! not_all_discovered ) { // If all vertices are discovered, stop utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); explored_all = true; @@ -387,6 +429,26 @@ namespace grb { return rc; } + template< typename D = void, typename T = long > + RC bfs( const AlgorithmBFS algorithm, + const Matrix< D > & A, + size_t root, + bool & explored_all, + T & max_level, + Vector< T > & values, + const T not_find_value = static_cast< T >( -1 ), + const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value, void > * const = nullptr ) { + switch( algorithm ) { + case AlgorithmBFS::LEVELS: + return bfs_levels< D, T >( A, root, explored_all, max_level, values, not_find_value ); + case AlgorithmBFS::PARENTS: + return bfs_parents< D, T >( A, root, explored_all, max_level, values, not_find_value ); + default: + std::cerr << "Error: Unknown BFS algorithm" << std::endl; + return RC::ILLEGAL; + } + } + } // namespace algorithms } // namespace grb diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index efe41f73c..7ba0a1e22 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -23,31 +23,28 @@ #include -template< typename T > -grb::Vector< T > stdToGrbVector( const std::vector< T > & in ) { - grb::Vector< T > out( in.size() ); - for( size_t i = 0; i < in.size(); i++ ) - grb::setElement( out, in[ i ], i ); - return out; -} +using namespace grb; + +constexpr bool Verbose = false; template< typename D > -void printSparseVector( const grb::Vector< D > & v, const std::string & name ) { - grb::wait( v ); +void printSparseVector( const Vector< D > & v, const std::string & name ) { + if( size( v ) > 50 ) { + return; + } + 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++ ) + 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 < grb::size( v ); i++ ) { - if( nnz_idx < grb::nnz( v ) && i == it->first ) { + 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 < grb::nnz( v ) ) + if( nnz_idx < nnz( v ) ) ++it; } else { std::cout << "_ "; @@ -55,368 +52,150 @@ void printSparseVector( const grb::Vector< D > & v, const std::string & name ) { } } std::cout << " ] - " - << "Vector \"" << name << "\" (" << grb::size( v ) << ")" << std::endl; + << "Vector \"" << name << "\" (" << size( v ) << ")" << std::endl; } -template< typename T > struct input_t { - grb::Matrix< T > A; + // Input file parameters + std::string filename; + bool direct; + // Algorithm parameters + algorithms::AlgorithmBFS algorithm; size_t root; bool expected_explored_all; long expected_max_level; - bool compute_levels; - const grb::Vector< long > & expected_levels; - bool compute_parents; - const grb::Vector< long > & expected_parents; + 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::AlgorithmBFS algorithm = algorithms::AlgorithmBFS::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 { - grb::RC rc = grb::RC::SUCCESS; - grb::utils::TimerResults times; + RC rc = RC::SUCCESS; + utils::TimerResults times; size_t data_in_local; }; -template< typename T > -void grbProgram( const struct input_t< T > & input, struct output_t & output ) { - grb::utils::Timer timer; +void grbProgram( const struct input_t & input, struct output_t & output ) { + utils::Timer timer; long max_level; bool explored_all; - if( input.compute_levels ) { - grb::Vector< long > levels( grb::nrows( input.A ) ); - - timer.reset(); - output.rc = output.rc ? output.rc : grb::algorithms::bfs_levels( input.A, input.root, explored_all, max_level, levels ); - timer.reset(); - - 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 = grb::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 = grb::RC::FAILED; - return; - } - - // Check levels by comparing it with the expected one - if( std::equal( input.expected_levels.cbegin(), input.expected_levels.cend(), levels.cbegin() ) ) { - std::cout << "SUCCESS: expected_levels is correct" << std::endl; - } else { - std::cerr << "FAILED: levels is incorrect" << std::endl; - std::cerr << "levels != expected_levels" << std::endl; - printSparseVector( levels, "levels" ); - printSparseVector( input.expected_levels, "expected_levels" ); - output.rc = grb::RC::FAILED; - return; - } + // 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(); - if( input.compute_parents ) { - grb::Vector< long > parents( grb::nrows( input.A ) ); + // Allocate output vector + timer.reset(); + Vector< long > values( nrows( A ) ); + output.times.preamble = timer.time(); - timer.reset(); - output.rc = output.rc ? output.rc : grb::algorithms::bfs_parents( input.A, input.root, explored_all, max_level, parents ); - timer.reset(); + // Run the BFS algorithm + timer.reset(); + output.rc = output.rc ? output.rc : algorithms::bfs( input.algorithm, A, input.root, explored_all, max_level, values ); + grb::wait(); + output.times.useful = timer.time(); + { // 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 = grb::RC::FAILED; - return; + output.rc = output.rc ? output.rc : RC::FAILED; } - if( max_level == input.expected_max_level ) { + 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 = grb::RC::FAILED; - return; + output.rc = output.rc ? output.rc : RC::FAILED; } // Check levels by comparing it with the expected one - if( std::equal( input.expected_parents.cbegin(), input.expected_parents.cend(), parents.cbegin() ) ) { - std::cout << "SUCCESS: expected_parents is correct" << std::endl; - } else { - std::cerr << "FAILED: parents is incorrect" << std::endl; - std::cerr << "parents != expected_parents" << std::endl; - printSparseVector( parents, "parents" ); - printSparseVector( input.expected_parents, "expected_parents" ); - output.rc = grb::RC::FAILED; - return; + 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; + printSparseVector( values, "values" ); + printSparseVector( input.expected_values, "expected_values" ); + output.rc = output.rc ? output.rc : RC::FAILED; } + + if( output.rc == RC::SUCCESS && Verbose ) { + printSparseVector( values, "values" ); + } } } int main( int argc, char ** argv ) { (void)argc; (void)argv; - constexpr size_t niterations = 1; - grb::Benchmarker< grb::EXEC_MODE::AUTOMATIC > benchmarker; - std::cout << "Test executable: " << argv[ 0 ] << std::endl; + size_t inner_iterations = 1, outer_iterations = 1; + Benchmarker< EXEC_MODE::AUTOMATIC > benchmarker; - // Check if we are testing on a file - if( argc != 1 && argc != 5 ) { - std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ ]" << std::endl; + if( argc != 6 ) { + std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ outer_iters=1 inner_iters=1 ]" << std::endl; return 1; } - bool test_on_file = ( argc == 5 ); - - if( test_on_file ) { // Test on a file - std::string file_to_test( argv[ 1 ] ); - size_t root = std::stoul( argv[ 2 ] ); - bool expected_explored_all = std::stoi( argv[ 3 ] ); - long expected_max_level = std::stol( argv[ 4 ] ); - - std::cout << "-- Running test on file " << file_to_test << std::endl; - - // Read matrix from file as a pattern matrix (i.e. no values) - grb::utils::MatrixFileReader< void > reader( file_to_test, false, true ); - size_t r = reader.n(), c = reader.m(); - grb::Matrix< void > A( r, c ); - grb::RC rc_build = buildMatrixUnique( A, reader.cbegin( grb::IOMode::SEQUENTIAL ), reader.cend( grb::IOMode::SEQUENTIAL ), grb::IOMode::PARALLEL ); - if( rc_build != grb::RC::SUCCESS ) { - std::cerr << "ERROR during buildMatrixUnique of the pattern matrix: rc = " << rc_build << std::endl; - return 1; - } + std::cout << "Test executable: " << argv[ 0 ] << std::endl; - std::cout << "Matrix read successfully" << std::endl; - input_t< void > input { A, root, expected_explored_all, expected_max_level, false, { 0L }, false, { 0L } }; + 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: AlgorithmBFS::LEVELS + std::cout << std::endl << "-- Running AlgorithmBFS::LEVELS on file " << file_to_test << std::endl; + input_t input( file_to_test, direct, algorithms::AlgorithmBFS::LEVELS, root, expected_explored_all, expected_max_level ); output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - if( bench_rc ) { - std::cerr << "ERROR during execution of file " << file_to_test << ": rc = " << bench_rc << std::endl; - return bench_rc; - } else if( output.rc ) { - std::cerr << "Test failed: rc = " << output.rc << std::endl; - return output.rc; + 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; } - } else { - - /** 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 " + std::to_string(root) + ")" << std::endl; - bool expected_explored_all = true; - long expected_max_level = 1; - grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 0, 0 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { 0, 1, 1, 1 }; - std::vector< long > expected_parents { 0, 0, 0, 0 }; - input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - 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 = " << 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 " + std::to_string(root) + ")" << std::endl; - bool expected_explored_all = true; - long expected_max_level = 2; - grb::Matrix< void > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 0, 2 } }; - std::vector< size_t > A_cols { { 1, 2, 3 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { 0, 1, 1, 2 }; - std::vector< long > expected_parents { 0, 0, 0, 2 }; - input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - 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 = " << 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 " + std::to_string(root) + ")" << std::endl; - bool expected_explored_all = true; - long expected_max_level = 3; - grb::Matrix< int > A( 4, 4 ); - std::vector< size_t > A_rows { { 0, 1, 2, 3 } }; - std::vector< size_t > A_cols { { 1, 2, 3, 0 } }; - std::vector< int > A_values( A_rows.size(), 1 ); - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_values.data(), A_values.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { 0, 1, 2, 3 }; - std::vector< long > expected_parents { 0, 0, 1, 2 }; - input_t< int > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - 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 = " << 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 " + std::to_string(root) + ")" << std::endl; - bool expected_explored_all = true; - long expected_max_level = 2; - grb::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 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { 0, 1, 2, 1 }; - std::vector< long > expected_parents { 0, 0, 3, 0 }; - input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - 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 = " << 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 " + std::to_string(root) + ")" << std::endl; - bool expected_explored_all = true; - long expected_max_level = 3; - grb::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 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { 0, 1, 2, 3 }; - std::vector< long > expected_parents { 0, 0, 1, 2 }; - input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; + if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; } - { /* - * Directed version, pattern matrix, root = 1 - * => Impossible to reach vertex 0 - */ - size_t root = 1; - std::cout << "-- Running test on A4 (directed, pattern, root " + std::to_string(root) + ")" << std::endl; - bool expected_explored_all = false; - long expected_max_level = 2; - grb::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 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { -1, 0, 1, 2 }; - std::vector< long > expected_parents { -1, 1, 1, 2 }; - input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; + } + { // Run the test: AlgorithmBFS::PARENTS + std::cout << std::endl << "-- Running AlgorithmBFS::PARENTS on file " << file_to_test << std::endl; + input_t input( file_to_test, direct, algorithms::AlgorithmBFS::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; } - - /** 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 " + std::to_string(root) + ")" << std::endl; - bool expected_explored_all = false; - long expected_max_level = 1; - grb::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 } }; - grb::buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), grb::IOMode::PARALLEL ); - std::vector< long > expected_levels { 0, 1, -1, -1 }; - std::vector< long > expected_parents { 0, 0, -1, -1 }; - input_t< void > input { A, root, expected_explored_all, expected_max_level, true, stdToGrbVector( expected_levels ), true, stdToGrbVector( expected_parents ) }; - output_t output; - grb::RC bench_rc = benchmarker.exec( &grbProgram, input, output, niterations, 1, true ); - 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 = " << output.rc << std::endl; - return output.rc; - } - std::cout << std::endl; + if( output.rc ) { + std::cerr << "Test failed: rc = " << toString( output.rc ) << std::endl; + return output.rc; } } - std::cout << "Test OK" << std::endl; + std::cout << "Test OK" << std::endl; return 0; } diff --git a/tests/smoke/smoketests.sh b/tests/smoke/smoketests.sh index 5ec560899..1212314d8 100755 --- a/tests/smoke/smoketests.sh +++ b/tests/smoke/smoketests.sh @@ -257,11 +257,16 @@ for BACKEND in ${BACKENDS[@]}; do echo " " fi - echo ">>> [x] [ ] Testing the BFS algorithm" - $runner ${TEST_BIN_DIR}/bfs_${BACKEND} &> ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log - head -1 ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log - grep 'Test OK' ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log || echo "Test FAILED" - echo " " + 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 5 &> ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log + head -1 ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log + grep 'Test OK' ${TEST_OUT_DIR}/bfs_${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 echo ">>> [x] [ ] Testing the BiCGstab algorithm for the 17361 x 17361 input" echo " matrix gyro_m.mtx. This test verifies against a ground-" 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..b6cd8234e --- /dev/null +++ b/tests/unit/bfs.cpp @@ -0,0 +1,478 @@ +/* + * 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; + +grb::Vector< long > stdToGrbVector( const std::vector< long > & in ) { + grb::Vector< long > out( in.size() ); + for( size_t i = 0; i < in.size(); i++ ) { + grb::setElement( out, in[ i ], i ); + } + return out; +} + +grb::Vector< long > createGrbVector( const std::initializer_list< long > & in ) { + grb::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 { + algorithms::AlgorithmBFS algorithm; + 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( algorithms::AlgorithmBFS algorithm = algorithms::AlgorithmBFS::LEVELS, + 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 } ) : + algorithm( algorithm ), + 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 = RC::SUCCESS; +}; + +void grbProgram( const struct input_t & input, struct output_t & output ) { + utils::Timer timer; + long max_level; + bool explored_all; + + // Allocate output vector + Vector< long > values( nrows( input.A ) ); + + // Run the BFS algorithm + output.rc = output.rc ? output.rc : algorithms::bfs( input.algorithm, input.A, input.root, explored_all, max_level, values ); + 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 = 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 = RC::FAILED; + return; + } + + // Check levels by comparing it with the expected one + if( 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; + printSparseVector( values, "values" ); + printSparseVector( input.expected_values, "expected_values" ); + output.rc = 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; + + /** 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 " + std::to_string( 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 } }; + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::SEQUENTIAL ); + grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 1, 1 } ); + grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 0 } ); + + { // Levels + input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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 " + std::to_string( 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 } }; + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::SEQUENTIAL ); + grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 1, 2 } ); + grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 2 } ); + + { // Levels + input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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 " + std::to_string( 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 } }; + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); + grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 3 } ); + grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); + + { // Levels + input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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 " + std::to_string( 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 } }; + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); + grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 1 } ); + grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 3, 0 } ); + + { // Levels + input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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 " + std::to_string( 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 } }; + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); + grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 3 } ); + grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); + + { // Levels + input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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 " + std::to_string( root ) + ")" << std::endl; + bool expected_explored_all = false; + long expected_max_level = 2; + 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 } }; + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); + grb::Vector< long > expected_levels = createGrbVector( { -1, 0, 1, 2 } ); + grb::Vector< long > expected_parents = createGrbVector( { -1, 1, 1, 2 } ); + + { // Levels + input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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 " + std::to_string( root ) + ")" << std::endl; + bool expected_explored_all = false; + long expected_max_level = 1; + 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 } }; + buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); + grb::Vector< long > expected_levels = createGrbVector( { 0, 1, -1, -1 } ); + grb::Vector< long > expected_parents = createGrbVector( { 0, 0, -1, -1 } ); + + { // Levels + input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + output_t output; + RC bench_rc = launcher.exec( &grbProgram, 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 From 97b483c6a11b31f76b3698099db341287fc66dd6 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Wed, 5 Jul 2023 12:02:36 +0200 Subject: [PATCH 20/22] Optimisation for PARENTS + refactor --- include/graphblas/algorithms/bfs.hpp | 224 +++++++++++---------------- tests/smoke/bfs.cpp | 100 +++++++----- tests/unit/bfs.cpp | 156 ++++++++++++------- 3 files changed, 250 insertions(+), 230 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 7a7de18a7..1ec1f176d 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -36,7 +36,7 @@ #include -//#define BFS_DEBUG +// #define BFS_DEBUG namespace grb { @@ -53,7 +53,7 @@ namespace grb { (void)os; #ifdef BFS_DEBUG - if( rows > 50 || cols > 50 ) { + if( rows > 64 || cols > 64 ) { return; } std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; @@ -86,7 +86,7 @@ namespace grb { (void)os; #ifdef BFS_DEBUG - if( rows > 50 || cols > 50 ) { + if( rows > 64 || cols > 64 ) { return; } std::cout << "Matrix \"" << name << "\" (" << rows << "x" << cols << "):" << std::endl << "[" << std::endl; @@ -134,7 +134,7 @@ namespace grb { (void)v; (void)name; #ifdef BFS_DEBUG - if( size( v ) > 50 ) { + if( size( v ) > 64 ) { return; } wait( v ); @@ -166,7 +166,7 @@ namespace grb { (void)vector; (void)name; #ifdef BFS_DEBUG - if( vector.size() > 50 ) { + if( vector.size() > 64 ) { return; } std::cout << " [ "; @@ -198,61 +198,63 @@ namespace grb { * @tparam D Matrix values type * @tparam T Level 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 level at which each vertex is reached - * @param[in] not_find_value Value to use for vertices that have not been reached (default: -1) - * @return SUCCESS A call to this function never fails. + * @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 reached vertices, not_find_value for unreached vertices + * -# levels: [0, nrows(A) - 1] for each reached vertices, empty for unreached vertices * \endparblock * - * \warning Level 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 > + template< typename D = void, typename T = size_t > RC bfs_levels( const Matrix< D > & A, size_t root, bool & explored_all, T & max_level, Vector< T > & levels, - const T not_find_value = static_cast< T >( -1 ), - const std::enable_if< std::is_integral< T >::value && std::is_signed< T >::value > * const = nullptr ) { + Vector< bool > & x, + Vector< bool > & y, + Vector< bool > & not_visited, + const long max_iterations = -1L, + const std::enable_if< std::is_integral< T >::value > * const = nullptr ) { RC rc = RC::SUCCESS; const size_t nvertices = nrows( A ); { // Frontier vectors - Vector< bool > x( nvertices ), y( nvertices ); 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 : resize( levels, nvertices ); rc = rc ? rc : setElement( levels, static_cast< T >( 0 ), root ); utils::printSparseVector( levels, "levels" ); // Vector of unvisited vertices - Vector< bool > not_visited( nvertices ); rc = rc ? rc : set( not_visited, true ); - setElement( not_visited, false, root ); + rc = rc ? rc : setElement( not_visited, false, root ); - for( size_t level = 1; level <= nvertices; level++ ) { + size_t max_iter = max_iterations < 0 ? nvertices : max_iterations; + for( size_t level = 1; level <= max_iter; level++ ) { utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); max_level = level; // Multiply the current frontier by the adjacency matrix utils::printSparseVector( x, "x" ); utils::printSparseVector( not_visited, "not_visited" ); - resize( y, 0UL ); + rc = rc ? rc : resize( y, 0UL ); Semiring< operators::logical_or< bool >, operators::logical_and< bool >, identities::logical_false, identities::logical_true > bool_semiring; 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 ); @@ -295,14 +297,6 @@ namespace grb { "Some vertices are not reachable from the given root: " + std::to_string( root ) + "\n" ); - // Fill missing values with -1 - std::vector< bool > not_visited( nvertices, true ); - for( const std::pair< size_t, T > & p : levels ) - not_visited[ p.first ] = false; - for( size_t i = 0; i < nvertices; i++ ) - if( not_visited[ i ] ) - rc = rc ? rc : setElement( levels, not_find_value, i ); - return rc; } @@ -311,15 +305,18 @@ namespace grb { * This version computes the parents of each vertex. * * @tparam D Matrix values type - * @tparam T Level type + * @tparam T Parent 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] parenst Vector containing the parent from which each vertex is reached - * @param[in] not_find_value Value to use for vertices that have not been reached (default: -1) - * @return SUCCESS A call to this function never fails. + * @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: @@ -327,128 +324,87 @@ namespace grb { * -# parents: [0, nrows(A) - 1] for reached vertices, not_find_value for unreached vertices * \endparblock * - * \warning Level type T must be a signed integer type. + * \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 > + template< + typename D = void, + typename T = long + > RC bfs_parents( const Matrix< D > & A, 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 std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value, void > * const = nullptr ) { + const std::enable_if< std::is_arithmetic< T >::value && std::is_signed::value, void > * const = nullptr + ) { RC rc = RC::SUCCESS; const size_t nvertices = nrows( A ); utils::printSparseMatrix( A, "A" ); - Vector< bool > x( nvertices ), y( nvertices ); + 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" ); - rc = rc ? rc : resize( parents, nvertices ); + 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" ); - Vector< bool > not_visited( nvertices ); - rc = rc ? rc : set( not_visited, true ); - std::vector< size_t > to_visit_current_level, to_visit_next_level; - to_visit_next_level.reserve( nvertices ); - to_visit_current_level.reserve( nvertices ); - to_visit_current_level.push_back( root ); - utils::printStdVector( to_visit_current_level, "to_visit_current_level" ); + const Semiring< operators::min< T >, operators::add< T >, identities::infinity, identities::zero > semiring; + size_t max_iter = max_iterations < 0 ? nvertices : max_iterations; max_level = 0; - for( size_t level = 1; level <= nvertices; level++ ) { + explored_all = false; + for( size_t level = 1; level <= max_iter; level++ ) { + max_level = level; utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); - - bool discovered_one = false; - for( size_t visiting : to_visit_current_level ) { - if( not not_visited[ visiting ] ) - continue; - - utils::debugPrint( "* Visiting " + std::to_string( visiting ) + "\n" ); - rc = rc ? rc : setElement( not_visited, false, visiting ); - utils::printSparseVector( not_visited, "not_visited" ); - - // Explore from the current vertex only - rc = rc ? rc : setElement( x, true, visiting ); // Explore from the current vertex only - utils::printSparseVector( x, "x" ); - rc = rc ? rc : clear( y ); // Necessary as vxm is in-place - // Masking vxm to only explore non-explored vertices - const Semiring< operators::logical_or< bool >, operators::logical_and< bool >, identities::logical_false, identities::logical_true > bool_semiring; - 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 ); - rc = rc ? rc : clear( x ); // Reset the current vertex to false - utils::printSparseVector( y, "y" ); - - // Assign the current level to the newly discovered vertices only - const Monoid< operators::max< T >, identities::negative_infinity > max_monoid; - rc = rc ? rc : foldl( parents, y, visiting, max_monoid, Phase::RESIZE ); - rc = rc ? rc : foldl( parents, y, visiting, max_monoid, Phase::EXECUTE ); - utils::printSparseVector( parents, "parents" ); - - // Add the newly discovered vertices to the stack - // Optimisation possible if an operator::index was available - for( std::pair< size_t, bool > pair : y ) { - if( pair.second && not_visited[ pair.first ] ) { - to_visit_next_level.push_back( pair.first ); - discovered_one = true; - } - } - utils::printStdVector( to_visit_next_level, "to_visit_next_level" ); - } - - if(discovered_one) { - max_level++; - } - - if( to_visit_next_level.empty() ) { - // If all vertices are discovered, stop - bool not_all_discovered = false; - rc = rc ? rc : foldl( not_all_discovered, not_visited, Monoid< operators::logical_or< bool >, identities::logical_false >() ); - if( ! not_all_discovered ) { // If all vertices are discovered, stop - utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - explored_all = true; - break; - } - explored_all = false; - } - - std::swap( to_visit_current_level, to_visit_next_level ); - to_visit_next_level.clear(); + rc = rc ? rc : clear( y ); + // utils::printSparseVector( x, "x - before indexing" ); + 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" ); + + const Monoid< operators::max< T >, identities::negative_infinity > max_monoid; + rc = rc ? rc : foldl( parents, y, max_monoid, Phase::RESIZE ); + rc = rc ? rc : foldl( parents, y, max_monoid, Phase::EXECUTE ); + utils::printSparseVector( parents, "parents" ); + + const Monoid< operators::min< T >, identities::zero > all_assigned_monoid; + T min_parent = std::numeric_limits< T >::max(); + rc = rc ? rc : foldl( min_parent, parents, all_assigned_monoid ); + if( min_parent > not_find_value ) { + explored_all = true; + utils::debugPrint( "Explored " + std::to_string( max_level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); + break; + } + + std::swap( x, y ); } - - // Maximum number of iteration passed, not every vertex has been discovered - utils::debugPrint( "A full exploration is not possible on this graph. " - "Some vertices are not reachable from the given root: " + - std::to_string( root ) + "\n" ); + return rc; } - template< typename D = void, typename T = long > - RC bfs( const AlgorithmBFS algorithm, - const Matrix< D > & A, - size_t root, - bool & explored_all, - T & max_level, - Vector< T > & values, - const T not_find_value = static_cast< T >( -1 ), - const std::enable_if< std::is_arithmetic< T >::value && std::is_signed< T >::value, void > * const = nullptr ) { - switch( algorithm ) { - case AlgorithmBFS::LEVELS: - return bfs_levels< D, T >( A, root, explored_all, max_level, values, not_find_value ); - case AlgorithmBFS::PARENTS: - return bfs_parents< D, T >( A, root, explored_all, max_level, values, not_find_value ); - default: - std::cerr << "Error: Unknown BFS algorithm" << std::endl; - return RC::ILLEGAL; - } - } - } // namespace algorithms } // namespace grb diff --git a/tests/smoke/bfs.cpp b/tests/smoke/bfs.cpp index 7ba0a1e22..cd03014bc 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -25,34 +25,25 @@ using namespace grb; -constexpr bool Verbose = false; - -template< typename D > -void printSparseVector( const Vector< D > & v, const std::string & name ) { - if( size( v ) > 50 ) { - 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 << "_ "; - } +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 > position ) { + return position.first == ( (size_t)e.second ) && position.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; } } - std::cout << " ] - " - << "Vector \"" << name << "\" (" << size( v ) << ")" << std::endl; + return true; } struct input_t { @@ -108,11 +99,45 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { Vector< long > values( nrows( A ) ); output.times.preamble = timer.time(); - // Run the BFS algorithm - timer.reset(); - output.rc = output.rc ? output.rc : algorithms::bfs( input.algorithm, A, input.root, explored_all, max_level, values ); - grb::wait(); - output.times.useful = timer.time(); + switch( input.algorithm ) { + case algorithms::AlgorithmBFS::LEVELS: { + // AlgorithmBFS::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::AlgorithmBFS::PARENTS: { + + // AlgorithmBFS::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 ) { @@ -133,14 +158,13 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { 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; - printSparseVector( values, "values" ); - printSparseVector( input.expected_values, "expected_values" ); output.rc = output.rc ? output.rc : RC::FAILED; } - if( output.rc == RC::SUCCESS && Verbose ) { - printSparseVector( values, "values" ); - } + if( output.rc == RC::SUCCESS && input.algorithm == algorithms::AlgorithmBFS::PARENTS ) { + bool correct = verify_parents( A, values ); + std::cout << "CHECK - parents are correct is: " << std::to_string( correct ) << std::endl; + } } } @@ -168,7 +192,7 @@ int main( int argc, char ** argv ) { inner_iterations = std::stoul( argv[ 7 ] ); { // Run the test: AlgorithmBFS::LEVELS - std::cout << std::endl << "-- Running AlgorithmBFS::LEVELS on file " << file_to_test << std::endl; + std::cout << "-- Running AlgorithmBFS::LEVELS on file " << file_to_test << std::endl; input_t input( file_to_test, direct, algorithms::AlgorithmBFS::LEVELS, root, expected_explored_all, expected_max_level ); output_t output; RC rc = benchmarker.exec( &grbProgram, input, output, inner_iterations, outer_iterations, true ); @@ -182,7 +206,7 @@ int main( int argc, char ** argv ) { } } { // Run the test: AlgorithmBFS::PARENTS - std::cout << std::endl << "-- Running AlgorithmBFS::PARENTS on file " << file_to_test << std::endl; + std::cout << "-- Running AlgorithmBFS::PARENTS on file " << file_to_test << std::endl; input_t input( file_to_test, direct, algorithms::AlgorithmBFS::PARENTS, root, expected_explored_all, expected_max_level ); output_t output; RC rc = benchmarker.exec( &grbProgram, input, output, inner_iterations, outer_iterations, true ); diff --git a/tests/unit/bfs.cpp b/tests/unit/bfs.cpp index b6cd8234e..812e3a7a1 100644 --- a/tests/unit/bfs.cpp +++ b/tests/unit/bfs.cpp @@ -25,14 +25,6 @@ using namespace grb; -grb::Vector< long > stdToGrbVector( const std::vector< long > & in ) { - grb::Vector< long > out( in.size() ); - for( size_t i = 0; i < in.size(); i++ ) { - grb::setElement( out, in[ i ], i ); - } - return out; -} - grb::Vector< long > createGrbVector( const std::initializer_list< long > & in ) { grb::Vector< long > out( in.size() ); for( size_t i = 0; i < in.size(); i++ ) { @@ -69,7 +61,6 @@ void printSparseVector( const Vector< D > & v, const std::string & name ) { } struct input_t { - algorithms::AlgorithmBFS algorithm; const Matrix< void > & A; const size_t root; bool expected_explored_all; @@ -77,13 +68,7 @@ struct input_t { const Vector< long > & expected_values; // Necessary for distributed backends - input_t( algorithms::AlgorithmBFS algorithm = algorithms::AlgorithmBFS::LEVELS, - 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 } ) : - algorithm( algorithm ), + 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 ) {} }; @@ -91,16 +76,19 @@ struct output_t { RC rc = RC::SUCCESS; }; -void grbProgram( const struct input_t & input, struct output_t & output ) { +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 > values( nrows( input.A ) ); + Vector< long > levels( nrows( input.A ) ); + Vector< bool > x( nrows( input.A ), 1UL ); + Vector< bool > y( nrows( input.A ), 0UL ); + Vector< bool > not_visited( nrows( input.A ) ); // Run the BFS algorithm - output.rc = output.rc ? output.rc : algorithms::bfs( input.algorithm, input.A, input.root, explored_all, max_level, values ); + output.rc = output.rc ? output.rc : algorithms::bfs_levels( input.A, input.root, explored_all, max_level, levels, x, y, not_visited ); wait(); { // Check the outputs @@ -121,10 +109,53 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { } // Check levels by comparing it with the expected one - if( 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; - printSparseVector( values, "values" ); + if( not 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 = 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 ); + 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 = 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 = RC::FAILED; + return; + } + + // Check parents by comparing it with the expected one + if( not 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 = RC::FAILED; return; @@ -163,9 +194,9 @@ int main( int argc, char ** argv ) { grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 0 } ); { // Levels - input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -177,9 +208,9 @@ int main( int argc, char ** argv ) { } { // Parents - input_t input( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -211,9 +242,9 @@ int main( int argc, char ** argv ) { grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 2 } ); { // Levels - input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -225,9 +256,9 @@ int main( int argc, char ** argv ) { } { // Parents - input_t input( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -260,9 +291,9 @@ int main( int argc, char ** argv ) { grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); { // Levels - input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -274,9 +305,9 @@ int main( int argc, char ** argv ) { } { // Parents - input_t input( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -300,12 +331,12 @@ int main( int argc, char ** argv ) { std::vector< size_t > A_cols { { 3, 1, 0, 2, 1, 3, 2, 0 } }; buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 1 } ); - grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 3, 0 } ); + grb::Vector< long > expected_parents = createGrbVector( { 1, 0, 1, 0 } ); { // Levels - input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -317,9 +348,9 @@ int main( int argc, char ** argv ) { } { // Parents - input_t input( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -353,9 +384,9 @@ int main( int argc, char ** argv ) { grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); { // Levels - input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -367,9 +398,9 @@ int main( int argc, char ** argv ) { } { // Parents - input_t input( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_parents ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -387,18 +418,21 @@ int main( int argc, char ** argv ) { size_t root = 1; std::cout << "-- Running test on A4 (directed, pattern, root " + std::to_string( root ) + ")" << std::endl; bool expected_explored_all = false; - long expected_max_level = 2; + 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 } }; buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); - grb::Vector< long > expected_levels = createGrbVector( { -1, 0, 1, 2 } ); - grb::Vector< long > expected_parents = createGrbVector( { -1, 1, 1, 2 } ); { // Levels - input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + long expected_max_level = 2; + grb::Vector< long > expected_levels( 4UL, 3UL ); + grb::setElement( expected_levels, 0UL, 1UL ); + grb::setElement( expected_levels, 1UL, 2UL ); + grb::setElement( expected_levels, 2UL, 3UL ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -410,9 +444,11 @@ int main( int argc, char ** argv ) { } { // Parents - input_t input( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + long expected_max_level = 4; + grb::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, input, 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; @@ -435,18 +471,20 @@ int main( int argc, char ** argv ) { size_t root = 0; std::cout << "-- Running test on A5 (undirected, pattern, root " + std::to_string( root ) + ")" << std::endl; bool expected_explored_all = false; - long expected_max_level = 1; + 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 } }; buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); - grb::Vector< long > expected_levels = createGrbVector( { 0, 1, -1, -1 } ); - grb::Vector< long > expected_parents = createGrbVector( { 0, 0, -1, -1 } ); { // Levels - input_t input( algorithms::AlgorithmBFS::LEVELS, A, root, expected_explored_all, expected_max_level, expected_levels ); + long expected_max_level = 1; + grb::Vector< long > expected_levels( 4UL, 2UL ); + grb::setElement( expected_levels, 0UL, 0UL ); + grb::setElement( expected_levels, 1UL, 1UL ); + input_t input( A, root, expected_explored_all, expected_max_level, expected_levels ); output_t output; - RC bench_rc = launcher.exec( &grbProgram, input, 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; @@ -458,9 +496,11 @@ int main( int argc, char ** argv ) { } { // Parents - input_t input( algorithms::AlgorithmBFS::PARENTS, A, root, expected_explored_all, expected_max_level, expected_parents ); + long expected_max_level = 4; + grb::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, input, 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; From b4ceb296b4365db65ea0c8d929981aa2bddc9502 Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Wed, 5 Jul 2023 14:18:43 +0200 Subject: [PATCH 21/22] Fix smoke test parameters --- tests/smoke/smoketests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/smoke/smoketests.sh b/tests/smoke/smoketests.sh index 1212314d8..5dfe0b3b2 100755 --- a/tests/smoke/smoketests.sh +++ b/tests/smoke/smoketests.sh @@ -261,7 +261,7 @@ for BACKEND in ${BACKENDS[@]}; do 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 5 &> ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log + $runner ${TEST_BIN_DIR}/bfs_${BACKEND} ${INPUT_DIR}/west0497.mtx direct 0 0 497 &> ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log head -1 ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log grep 'Test OK' ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log || echo "Test FAILED" else From 640f40637aba09b074f58a0c40161987f067b1ae Mon Sep 17 00:00:00 2001 From: Benjamin Lozes Date: Tue, 18 Jul 2023 16:45:40 +0200 Subject: [PATCH 22/22] Refactor + bugfixes --- include/graphblas/algorithms/bfs.hpp | 396 +++++++++++++++---------- include/graphblas/base/internalops.hpp | 117 ++++++++ include/graphblas/ops.hpp | 50 ++++ tests/smoke/bfs.cpp | 83 ++++-- tests/smoke/smoketests.sh | 13 +- tests/unit/bfs.cpp | 195 +++++++----- 6 files changed, 595 insertions(+), 259 deletions(-) diff --git a/include/graphblas/algorithms/bfs.hpp b/include/graphblas/algorithms/bfs.hpp index 1ec1f176d..b3a92c0ed 100644 --- a/include/graphblas/algorithms/bfs.hpp +++ b/include/graphblas/algorithms/bfs.hpp @@ -18,7 +18,11 @@ /** * @file * - * Implements Breadth-First Search (BFS) algorithms. + * 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 @@ -36,7 +40,7 @@ #include -// #define BFS_DEBUG +// #define _DEBUG namespace grb { @@ -44,7 +48,14 @@ namespace grb { 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 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; @@ -52,7 +63,7 @@ namespace grb { (void)name; (void)os; -#ifdef BFS_DEBUG +#ifdef _DEBUG if( rows > 64 || cols > 64 ) { return; } @@ -61,7 +72,7 @@ namespace grb { 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 ) { + 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 ) @@ -77,7 +88,14 @@ namespace grb { } 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 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; @@ -85,7 +103,7 @@ namespace grb { (void)name; (void)os; -#ifdef BFS_DEBUG +#ifdef _DEBUG if( rows > 64 || cols > 64 ) { return; } @@ -94,9 +112,14 @@ namespace grb { 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; - } ); + 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 @@ -110,30 +133,34 @@ namespace grb { } template< typename D > - void printSparseMatrix( const Matrix< D > & mat, const std::string & name ) { + void printSparseMatrix( const Matrix< D > &mat, const std::string &name ) { (void)mat; (void)name; -#ifdef BFS_DEBUG +#ifdef _DEBUG wait( mat ); - printSparseMatrixIterator( nrows( mat ), ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout ); + 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 printSparseMatrix< void >( const Matrix< void > &mat, const std::string &name ) { (void)mat; (void)name; -#ifdef BFS_DEBUG +#ifdef _DEBUG wait( mat ); - printSparsePatternMatrixIterator( nrows( mat ), ncols( mat ), mat.cbegin(), mat.cend(), name, std::cout ); + 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 printSparseVector( const Vector< D > &v, const std::string &name ) { (void)v; (void)name; -#ifdef BFS_DEBUG +#ifdef _DEBUG if( size( v ) > 64 ) { return; } @@ -162,26 +189,18 @@ namespace grb { } template< typename T > - void printStdVector( const std::vector< T > & vector, const std::string & name ) { + void printStdVector( const std::vector< T > &vector, const std::string &name ) { (void)vector; (void)name; -#ifdef BFS_DEBUG +#ifdef _DEBUG if( vector.size() > 64 ) { return; } std::cout << " [ "; - for( const T & e : vector ) + 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 ) { - (void)msg; - (void)os; -#ifdef BFS_DEBUG - os << msg; #endif } } // namespace utils @@ -189,114 +208,151 @@ namespace grb { /** * @brief Breadth-first search (BFS) algorithm. */ - enum AlgorithmBFS { LEVELS, PARENTS }; + 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 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) + * @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 + * -# 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. + * \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 > - RC bfs_levels( const Matrix< D > & A, - size_t root, - bool & explored_all, - T & max_level, - Vector< T > & levels, - Vector< bool > & x, - Vector< bool > & y, - Vector< bool > & not_visited, + 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 std::enable_if< std::is_integral< T >::value > * const = nullptr ) { - RC rc = RC::SUCCESS; + 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 ); - { - // Frontier vectors - rc = rc ? rc : setElement( x, true, root ); + utils::printSparseMatrix( A, "A" ); + utils::printSparseVector( x, "x" ); - 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" ); - // 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 ); - // 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" ); - size_t max_iter = max_iterations < 0 ? nvertices : max_iterations; - for( size_t level = 1; level <= max_iter; level++ ) { - utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); - max_level = level; + // 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 ); - // Multiply the current frontier by the adjacency matrix - utils::printSparseVector( x, "x" ); - utils::printSparseVector( not_visited, "not_visited" ); - rc = rc ? rc : resize( y, 0UL ); - Semiring< operators::logical_or< bool >, operators::logical_and< bool >, identities::logical_false, identities::logical_true > bool_semiring; - 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 - for( const std::pair< size_t, bool > e : y ) { - if( e.second ) - setElement( not_visited, false, e.first ); - } - - // Assign the current level to the newly discovered vertices only - const Monoid< operators::min< T >, identities::infinity > min_monoid; - 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 ) { - // If all vertices are discovered, stop - utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); - return rc; - } - bool can_continue = nnz( y ) > 0; - if( ! can_continue ) { - max_level = level - 1; - // If no new vertices are discovered, stop - utils::debugPrint( "Explored " + std::to_string( level ) + " levels to discover " + std::to_string( nnz( levels ) ) + " vertices.\n" ); - break; - } + // 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" ); - // Swap the frontier, avoid a copy - std::swap( x, y ); + // 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 - utils::debugPrint( "A full exploration is not possible on this graph. " - "Some vertices are not reachable from the given root: " + - std::to_string( root ) + "\n" ); - +#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; } @@ -304,46 +360,77 @@ namespace grb { * Breadth-first search (BFS) algorithm. * This version computes the parents of each vertex. * - * @tparam D Matrix values type - * @tparam T Parent type + * @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) + * @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 + * -# 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. + * \note Values of the matrix A are ignored, + * hence it is recommended to use a pattern matrix. */ - template< - typename D = void, - typename T = long + 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, - size_t root, - bool & explored_all, - T & max_level, - Vector< T > & parents, - Vector< T > & x, - Vector< T > & y, + 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 std::enable_if< std::is_arithmetic< T >::value && std::is_signed::value, void > * const = nullptr + 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 = RC::SUCCESS; + RC rc = SUCCESS; const size_t nvertices = nrows( A ); utils::printSparseMatrix( A, "A" ); @@ -362,45 +449,44 @@ namespace grb { rc = rc ? rc : setElement( parents, root, root ); utils::printSparseVector( parents, "parents" ); - const Semiring< operators::min< T >, operators::add< T >, identities::infinity, identities::zero > semiring; - 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; - utils::debugPrint( "** Level " + std::to_string( level ) + ":\n" ); + rc = rc ? rc : clear( y ); - // utils::printSparseVector( x, "x - before indexing" ); - rc = rc ? rc : - eWiseLambda( - [ &x ]( const size_t i ) { - x[ i ] = i; - }, - x ); + 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" ); - const Monoid< operators::max< T >, identities::negative_infinity > max_monoid; rc = rc ? rc : foldl( parents, y, max_monoid, Phase::RESIZE ); rc = rc ? rc : foldl( parents, y, max_monoid, Phase::EXECUTE ); utils::printSparseVector( parents, "parents" ); - const Monoid< operators::min< T >, identities::zero > all_assigned_monoid; T min_parent = std::numeric_limits< T >::max(); - rc = rc ? rc : foldl( min_parent, parents, all_assigned_monoid ); + rc = rc ? rc : foldl( min_parent, parents, min_negative_monoid ); if( min_parent > not_find_value ) { explored_all = true; - utils::debugPrint( "Explored " + std::to_string( max_level ) + " levels to discover all of the " + std::to_string( nvertices ) + " vertices.\n" ); +#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; } 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/bfs.cpp b/tests/smoke/bfs.cpp index cd03014bc..1130000dc 100644 --- a/tests/smoke/bfs.cpp +++ b/tests/smoke/bfs.cpp @@ -34,12 +34,17 @@ bool verify_parents( const Matrix< void > & A, Vector< T > parents ) { 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 > position ) { - return position.first == ( (size_t)e.second ) && position.second == e.first; - } ); + 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; + std::cerr << "ERROR: parent " << e.second << " of node " + << e.first << " is not a valid edge" << std::endl; return false; } } @@ -51,7 +56,7 @@ struct input_t { std::string filename; bool direct; // Algorithm parameters - algorithms::AlgorithmBFS algorithm; + algorithms::BFS algorithm; size_t root; bool expected_explored_all; long expected_max_level; @@ -61,13 +66,18 @@ struct input_t { // Necessary for distributed backends input_t( const std::string & filename = "", bool direct = true, - algorithms::AlgorithmBFS algorithm = algorithms::AlgorithmBFS::LEVELS, + 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 ) {} + 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 { @@ -87,9 +97,12 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { 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 ); + 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; + std::cerr << "ERROR during buildMatrixUnique of the pattern matrix: " + << toString( output.rc ) << std::endl; return; } output.times.io = timer.time(); @@ -100,8 +113,8 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { output.times.preamble = timer.time(); switch( input.algorithm ) { - case algorithms::AlgorithmBFS::LEVELS: { - // AlgorithmBFS::LEVELS specific allocations + case algorithms::BFS::LEVELS: { + // BFS::LEVELS specific allocations timer.reset(); Vector< bool > x( nrows( A ), 1UL ); Vector< bool > y( nrows( A ), 0UL ); @@ -110,15 +123,17 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { // 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 ); + 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::AlgorithmBFS::PARENTS: { + case algorithms::BFS::PARENTS: { - // AlgorithmBFS::PARENTS specific allocations + // BFS::PARENTS specific allocations timer.reset(); Vector< long > x( nrows( A ), 1UL ); Vector< long > y( nrows( A ), 0UL ); @@ -126,7 +141,9 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { // Run the algorithm timer.reset(); - output.rc = output.rc ? output.rc : algorithms::bfs_parents( A, input.root, explored_all, max_level, values, x, y ); + 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(); @@ -143,25 +160,31 @@ void grbProgram( const struct input_t & input, struct output_t & output ) { 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; + 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; + 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() ) ) { + 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::AlgorithmBFS::PARENTS ) { + 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; } @@ -176,7 +199,9 @@ int main( int argc, char ** argv ) { Benchmarker< EXEC_MODE::AUTOMATIC > benchmarker; if( argc != 6 ) { - std::cerr << "Usage: \n\t" << argv[ 0 ] << " [ outer_iters=1 inner_iters=1 ]" << std::endl; + 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; @@ -191,9 +216,11 @@ int main( int argc, char ** argv ) { if( argc > 7 ) inner_iterations = std::stoul( argv[ 7 ] ); - { // Run the test: AlgorithmBFS::LEVELS - std::cout << "-- Running AlgorithmBFS::LEVELS on file " << file_to_test << std::endl; - input_t input( file_to_test, direct, algorithms::AlgorithmBFS::LEVELS, root, expected_explored_all, expected_max_level ); + { // 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 ) { @@ -205,9 +232,11 @@ int main( int argc, char ** argv ) { return output.rc; } } - { // Run the test: AlgorithmBFS::PARENTS - std::cout << "-- Running AlgorithmBFS::PARENTS on file " << file_to_test << std::endl; - input_t input( file_to_test, direct, algorithms::AlgorithmBFS::PARENTS, root, expected_explored_all, expected_max_level ); + { // 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 ) { diff --git a/tests/smoke/smoketests.sh b/tests/smoke/smoketests.sh index 5dfe0b3b2..f9330b87b 100755 --- a/tests/smoke/smoketests.sh +++ b/tests/smoke/smoketests.sh @@ -261,12 +261,19 @@ for BACKEND in ${BACKENDS[@]}; do 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_${BACKEND}_${P}_${T}.log - head -1 ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log - grep 'Test OK' ${TEST_OUT_DIR}/bfs_${BACKEND}_${P}_${T}.log || echo "Test FAILED" + $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-" diff --git a/tests/unit/bfs.cpp b/tests/unit/bfs.cpp index 812e3a7a1..cecee1959 100644 --- a/tests/unit/bfs.cpp +++ b/tests/unit/bfs.cpp @@ -25,8 +25,8 @@ using namespace grb; -grb::Vector< long > createGrbVector( const std::initializer_list< long > & in ) { - grb::Vector< long > out( in.size() ); +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 ); } @@ -68,15 +68,27 @@ struct input_t { 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 ) {} + 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 = RC::SUCCESS; + RC rc = SUCCESS; }; -void grbProgram_BFS_Levels( const struct input_t & input, struct output_t & output ) { +void grbProgram_BFS_Levels( + const struct input_t & input, + struct output_t & output +) { utils::Timer timer; long max_level; bool explored_all; @@ -84,43 +96,53 @@ void grbProgram_BFS_Levels( const struct input_t & input, struct output_t & outp // Allocate output vector Vector< long > levels( nrows( input.A ) ); Vector< bool > x( nrows( input.A ), 1UL ); - Vector< bool > y( nrows( input.A ), 0UL ); + Vector< bool > y( nrows( input.A ) ); Vector< bool > not_visited( nrows( input.A ) ); // Run the BFS algorithm - output.rc = output.rc ? output.rc : algorithms::bfs_levels( input.A, input.root, explored_all, max_level, levels, x, y, not_visited ); - wait(); + 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 = RC::FAILED; + 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 = RC::FAILED; + 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( not std::equal( input.expected_values.cbegin(), input.expected_values.cend(), levels.cbegin() ) ) { + 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 = RC::FAILED; + output.rc = FAILED; return; } } } -void grbProgram_BFS_Parents( const struct input_t & input, struct output_t & output ) { +void grbProgram_BFS_Parents( + const struct input_t & input, + struct output_t & output +) { utils::Timer timer; long max_level; bool explored_all; @@ -131,33 +153,37 @@ void grbProgram_BFS_Parents( const struct input_t & input, struct output_t & out 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 ); - wait(); + 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 = RC::FAILED; + 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 = RC::FAILED; + 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( not std::equal( input.expected_values.cbegin(), input.expected_values.cend(), parents.cbegin() ) ) { + 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 = RC::FAILED; + output.rc = FAILED; return; } } @@ -168,7 +194,7 @@ int main( int argc, char ** argv ) { (void)argv; Launcher< EXEC_MODE::AUTOMATIC > launcher; - std::cout << "Test executable: " << argv[ 0 ] << std::endl; + std::cout << "Test executable: " << argv[ 0 ] << std::endl << std::flush; /** Matrix A1: * @@ -183,15 +209,18 @@ int main( int argc, char ** argv ) { * => 1 step(s) to explore all nodes */ size_t root = 0; - std::cout << "-- Running test on A1 (directed, non-pattern, root " + std::to_string( root ) + ")" << std::endl; + 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 } }; - buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::SEQUENTIAL ); - grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 1, 1 } ); - grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 0 } ); + 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 ); @@ -231,15 +260,18 @@ int main( int argc, char ** argv ) { * => 2 step(s) to explore all nodes */ size_t root = 0; - std::cout << "-- Running test on A2 (directed, pattern, root " + std::to_string( root ) + ")" << std::endl; + 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 } }; - buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::SEQUENTIAL ); - grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 1, 2 } ); - grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 0, 2 } ); + 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 ); @@ -280,15 +312,18 @@ int main( int argc, char ** argv ) { * => 3 step(s) to explore all nodes */ size_t root = 0; - std::cout << "-- Running test on A3 (directed, non-pattern: int, root " + std::to_string( root ) + ")" << std::endl; + 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 } }; - buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); - grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 3 } ); - grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); + 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 ); @@ -323,15 +358,18 @@ int main( int argc, char ** argv ) { * => 2 step(s) to explore all nodes */ size_t root = 0; - std::cout << "-- Running test on A3 (undirected, pattern, root " + std::to_string( root ) + ")" << std::endl; + 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 } }; - buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); - grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 1 } ); - grb::Vector< long > expected_parents = createGrbVector( { 1, 0, 1, 0 } ); + 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 ); @@ -373,15 +411,18 @@ int main( int argc, char ** argv ) { * => 3 step(s) to explore all nodes */ size_t root = 0; - std::cout << "-- Running test on A4 (directed, pattern, one cycle, root " + std::to_string( root ) + ")" << std::endl; + 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 } }; - buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); - grb::Vector< long > expected_levels = createGrbVector( { 0, 1, 2, 3 } ); - grb::Vector< long > expected_parents = createGrbVector( { 0, 0, 1, 2 } ); + 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 ); @@ -416,20 +457,23 @@ int main( int argc, char ** argv ) { * => Impossible to reach vertex 0 */ size_t root = 1; - std::cout << "-- Running test on A4 (directed, pattern, root " + std::to_string( root ) + ")" << std::endl; + 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 } }; - buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); + 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; - grb::Vector< long > expected_levels( 4UL, 3UL ); - grb::setElement( expected_levels, 0UL, 1UL ); - grb::setElement( expected_levels, 1UL, 2UL ); - grb::setElement( expected_levels, 2UL, 3UL ); + 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 ); @@ -445,7 +489,7 @@ int main( int argc, char ** argv ) { { // Parents long expected_max_level = 4; - grb::Vector< long > expected_parents = createGrbVector( { -1, 3, 1, 2 } ); + 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 ); @@ -469,19 +513,22 @@ int main( int argc, char ** argv ) { * => Impossible to reach vertices 2 and 3 */ size_t root = 0; - std::cout << "-- Running test on A5 (undirected, pattern, root " + std::to_string( root ) + ")" << std::endl; + 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 } }; - buildMatrixUnique( A, A_rows.data(), A_cols.data(), A_rows.size(), IOMode::PARALLEL ); + 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; - grb::Vector< long > expected_levels( 4UL, 2UL ); - grb::setElement( expected_levels, 0UL, 0UL ); - grb::setElement( expected_levels, 1UL, 1UL ); + 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 ); @@ -497,7 +544,7 @@ int main( int argc, char ** argv ) { { // Parents long expected_max_level = 4; - grb::Vector< long > expected_parents = createGrbVector( { 1, 0, -1, -1 } ); + 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 );