Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SSSP API, test and implementation #2016

Merged
merged 13 commits into from
Jan 22, 2022
Merged
3 changes: 2 additions & 1 deletion cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#=============================================================================
# Copyright (c) 2018-2021, NVIDIA CORPORATION.
# Copyright (c) 2018-2022, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -359,6 +359,7 @@ add_library(cugraph_c SHARED
src/c_api/graph_mg.cpp
src/c_api/pagerank.cpp
src/c_api/bfs.cpp
src/c_api/sssp.cpp
src/c_api/extract_paths.cpp
)
add_library(cugraph::cugraph_c ALIAS cugraph_c)
Expand Down
47 changes: 40 additions & 7 deletions cpp/include/cugraph_c/algorithms.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, NVIDIA CORPORATION.
* Copyright (c) 2021-2022, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -201,7 +201,10 @@ void cugraph_paths_result_free(cugraph_paths_result_t* result);
* input graphs.
* @param depth_limit Sets the maximum number of breadth-first search iterations. Any vertices
* farther than @p depth_limit hops from @p source_vertex will be marked as unreachable.
* @param do_expensive_check A flag to run expensive checks for input arguments (if set to `true`).
* @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to
* `true`).
* @param [in] compute_predecessors A flag to indicate whether to compute the predecessors in the
* result
* @param [out] result Opaque pointer to paths results
* @param [out] error Pointer to an error object storing details of any error. Will
* be populated if error code is not CUGRAPH_SUCCESS
Expand All @@ -219,6 +222,36 @@ cugraph_error_code_t cugraph_bfs(
cugraph_paths_result_t** result,
cugraph_error_t** error);

/**
* @brief Perform single-source shortest-path to compute the minimum distances
* (and predecessors) from the source vertex.
*
* This function computes the distances (minimum edge weight sums) from the source
* vertex. If @p predecessors is not NULL, this function calculates the predecessor of each
* vertex (parent vertex in the breadth-first search tree) as well.
*
* @param [in] handle Handle for accessing resources
* @param [in] graph Pointer to graph
* @param [in] source Source vertex id
* @param [in] cutoff Maximum edge weight sum to consider
* @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to
* `true`).
* @param [in] compute_predecessors A flag to indicate whether to compute the predecessors in the
* result
* @param [out] result Opaque pointer to paths results
* @param [out] error Pointer to an error object storing details of any error. Will
* be populated if error code is not CUGRAPH_SUCCESS
* @return error code
*/
cugraph_error_code_t cugraph_sssp(const cugraph_resource_handle_t* handle,
cugraph_graph_t* graph,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't graph here be const?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C api will transpose the graph if the input is opposite from what the algorithm requires. This will modify the graph.

Copy link
Contributor

@rlratzel rlratzel Jan 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C api will transpose the graph if the input is opposite from what the algorithm requires. This will modify the graph.

Would it be better to set/return an error in that case? I'm envisioning a case where we or a user has a bug that isn't transposing data if an algo requires it, it gets silently transposed anyway, then we wonder why our benchmarks are slower than expected.

Copy link
Collaborator Author

@ChuckHastings ChuckHastings Jan 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair question. I think we've discussed this (or else I had this argument with myself... entirely possible).

From an API perspective, we have two choices:

  • Do what the user is asking us to do regardless of the performance ramifications. The documentation should provide sufficient detail for the user to correct their bad choices.
  • Require the user to understand the performance ramifications and make all of the right calls explicitly

The C++ layer has always opted for the latter and I believe it should. Thus far I have opted for the former in the C API. Now that @rlratzel is building the python layer it would make sense for us to have this conversation. Perhaps the python layer should worry about providing the intelligence and the C layer should (like the C++ layer) simply fail fast if the input is in the wrong orientation.

That would be an easy change, although I think it should not be rushed into this release.

size_t source,
double cutoff,
bool_t do_expensive_check,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the C++ API, do_expensive_check is the last parameter, any reason to deviate from this convention?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that when we did bfs :-(. The fact that we can't default parameters in the C API made me a little careless on this issue.

The current C API "convention" has been to mimic the input and have the last two parameters be the return type and the error code. I can swap do_expensive_check and compute_predecessors to make that more consistent. In the C++ API, compute_predecessors is not a necessary parameter since we pass in the predecessors array and pass in a null pointer if we don't want to compute predecessors.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapped these in latest push.

bool_t compute_predecessors,
cugraph_paths_result_t** result,
cugraph_error_t** error);

/**
* @brief Opaque extract_paths result type
*/
Expand All @@ -227,12 +260,12 @@ typedef struct {
} cugraph_extract_paths_result_t;

/**
* @brief Extract BFS paths from a BFS result
* @brief Extract BFS or SSSP paths from a cugraph_paths_result_t
*
* This function extracts paths from the BFS output. BFS outputs distances
* and predecessors. The path from a vertex v back to the original source vertex
* can be extracted by recursively looking up the predecessor vertex until you arrive
* back at the original source vertex.
* This function extracts paths from the BFS or SSSP output. BFS and SSSP output
* distances and predecessors. The path from a vertex v back to the original
* source vertex can be extracted by recursively looking up the predecessor
* vertex until you arrive back at the original source vertex.
*
* @param [in] handle Handle for accessing resources
* @param [in] graph Pointer to graph. NOTE: Graph might be modified if the storage
Expand Down
186 changes: 186 additions & 0 deletions cpp/src/c_api/sssp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION.
*
* 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 <cugraph_c/algorithms.h>

#include <c_api/abstract_functor.hpp>
#include <c_api/graph.hpp>
#include <c_api/paths_result.hpp>

#include <cugraph/algorithms.hpp>
#include <cugraph/detail/utility_wrappers.hpp>
#include <cugraph/graph_functions.hpp>
#include <cugraph/visitors/generic_cascaded_dispatch.hpp>

#include <raft/handle.hpp>

namespace cugraph {
namespace c_api {

struct sssp_functor : public abstract_functor {
raft::handle_t const& handle_;
cugraph_graph_t* graph_;
size_t source_;
double cutoff_;
bool do_expensive_check_;
bool compute_predecessors_;
cugraph_paths_result_t* result_{};

sssp_functor(raft::handle_t const& handle,
cugraph_graph_t* graph,
size_t source,
double cutoff,
bool do_expensive_check,
bool compute_predecessors)
: abstract_functor(),
handle_(handle),
graph_(graph),
source_(source),
cutoff_(cutoff),
do_expensive_check_(do_expensive_check),
compute_predecessors_(compute_predecessors)
{
}

template <typename vertex_t,
typename edge_t,
typename weight_t,
bool store_transposed,
bool multi_gpu>
void operator()()
{
// FIXME: Think about how to handle SG vice MG
if constexpr (!cugraph::is_candidate<vertex_t, edge_t, weight_t>::value) {
unsupported();
} else {
// SSSP expects store_transposed == false
if constexpr (store_transposed) {
error_code_ = cugraph::c_api::
transpose_storage<vertex_t, edge_t, weight_t, store_transposed, multi_gpu>(
handle_, graph_, error_.get());
if (error_code_ != CUGRAPH_SUCCESS) return;
}

auto graph =
reinterpret_cast<cugraph::graph_t<vertex_t, edge_t, weight_t, false, multi_gpu>*>(
graph_->graph_);

auto graph_view = graph->view();

auto number_map = reinterpret_cast<rmm::device_uvector<vertex_t>*>(graph_->number_map_);

rmm::device_uvector<vertex_t> source_ids(1, handle_.get_stream());
rmm::device_uvector<vertex_t> vertex_ids(graph->get_number_of_vertices(),
handle_.get_stream());
rmm::device_uvector<weight_t> distances(graph->get_number_of_vertices(),
handle_.get_stream());
rmm::device_uvector<vertex_t> predecessors(0, handle_.get_stream());

if (compute_predecessors_) {
predecessors.resize(graph->get_number_of_vertices(), handle_.get_stream());
}

vertex_t src = static_cast<vertex_t>(source_);
raft::update_device(source_ids.data(), &src, 1, handle_.get_stream());

//
// Need to renumber sources
//
renumber_ext_vertices<vertex_t, multi_gpu>(handle_,
source_ids.data(),
1,
number_map->data(),
graph_view.get_local_vertex_first(),
graph_view.get_local_vertex_last(),
do_expensive_check_);

raft::update_host(&src, source_ids.data(), 1, handle_.get_stream());

cugraph::sssp<vertex_t, edge_t, weight_t, multi_gpu>(
handle_,
graph_view,
distances.data(),
compute_predecessors_ ? predecessors.data() : nullptr,
src,
static_cast<weight_t>(cutoff_),
do_expensive_check_);

raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream());

if (compute_predecessors_) {
std::vector<vertex_t> vertex_partition_lasts = graph_view.get_vertex_partition_lasts();

unrenumber_int_vertices<vertex_t, multi_gpu>(handle_,
predecessors.data(),
predecessors.size(),
number_map->data(),
vertex_partition_lasts,
do_expensive_check_);
}

result_ = new cugraph_paths_result_t{
new cugraph_type_erased_device_array_t(vertex_ids, graph_->vertex_type_),
new cugraph_type_erased_device_array_t(distances, graph_->weight_type_),
new cugraph_type_erased_device_array_t(predecessors, graph_->vertex_type_)};
}
}
};

} // namespace c_api
} // namespace cugraph

extern "C" cugraph_error_code_t cugraph_sssp(const cugraph_resource_handle_t* handle,
cugraph_graph_t* graph,
size_t source,
double cutoff,
bool_t do_expensive_check,
bool_t compute_predecessors,
cugraph_paths_result_t** result,
cugraph_error_t** error)
{
*result = nullptr;
*error = nullptr;

try {
auto p_handle = reinterpret_cast<raft::handle_t const*>(handle);
auto p_graph = reinterpret_cast<cugraph::c_api::cugraph_graph_t*>(graph);

cugraph::c_api::sssp_functor functor(
*p_handle, p_graph, source, cutoff, do_expensive_check, compute_predecessors);

// FIXME: This seems like a recurring pattern. Can I encapsulate
// The vertex_dispatcher and error handling calls into a reusable function?
// After all, we're in C++ here.
cugraph::dispatch::vertex_dispatcher(cugraph::c_api::dtypes_mapping[p_graph->vertex_type_],
cugraph::c_api::dtypes_mapping[p_graph->edge_type_],
cugraph::c_api::dtypes_mapping[p_graph->weight_type_],
p_graph->store_transposed_,
p_graph->multi_gpu_,
functor);

if (functor.error_code_ != CUGRAPH_SUCCESS) {
*error = reinterpret_cast<cugraph_error_t*>(functor.error_.release());
return functor.error_code_;
}

*result = reinterpret_cast<cugraph_paths_result_t*>(functor.result_);
} catch (std::exception const& ex) {
*error = reinterpret_cast<cugraph_error_t*>(new cugraph::c_api::cugraph_error_t{ex.what()});
return CUGRAPH_UNKNOWN_ERROR;
}

return CUGRAPH_SUCCESS;
}
3 changes: 2 additions & 1 deletion cpp/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2019-2021, NVIDIA CORPORATION.
# Copyright (c) 2019-2022, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -593,6 +593,7 @@ ConfigureCTest(CAPI_CREATE_GRAPH_TEST c_api/create_graph_test.c)
ConfigureCTest(CAPI_RANDOM_WALKS_TEST c_api/random_walks_test.c)
ConfigureCTest(CAPI_PAGERANK_TEST c_api/pagerank_test.c)
ConfigureCTest(CAPI_BFS_TEST c_api/bfs_test.c)
ConfigureCTest(CAPI_SSSP_TEST c_api/sssp_test.c)
ConfigureCTest(CAPI_EXTRACT_PATHS_TEST c_api/extract_paths_test.c)

###################################################################################################
Expand Down
Loading