-
Notifications
You must be signed in to change notification settings - Fork 310
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
Changes from 4 commits
895eba3
7d8bb7c
edf272f
148df0d
c3bc393
c93ddd4
303390a
cfa35b1
b48637e
5fee4b8
e6cfaeb
354ba8a
031ecd8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
|
@@ -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 | ||
|
@@ -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, | ||
size_t source, | ||
double cutoff, | ||
bool_t do_expensive_check, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the C++ API, There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
*/ | ||
|
@@ -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 | ||
|
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; | ||
} |
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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:
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 thepython
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.