use std::ptr;
use cugraph_sys::{
cugraph_bfs, cugraph_bfs_predicate_result_get_target_distance, cugraph_bfs_predicate_result_get_target_found,
cugraph_bfs_predicate_result_t, cugraph_bfs_with_predicates, cugraph_error_t, cugraph_paths_result_t,
cugraph_type_erased_device_array_view_t,
};
use crate::{
array::DeviceArrayView,
cuda::{DeviceResourceKind, drop_device_resource},
cudf_interop::bfs_result_to_dataframe,
error::{Error, ErrorKind, Result, check_error, require_non_null},
graph::{Forward, Graph},
handle::{EnteredContext, ResourceHandleLease},
params::BfsDepthLimit,
structure::{
VertexSelection,
typed_results::{Distances, VertexIds},
},
traversal::paths::{ExtractPathsResult, PathsResult, extract_paths},
types::{CugraphElement, to_bool_t, try_from_bool_t},
};
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct BfsParams {
pub depth_limit: Option<BfsDepthLimit>,
pub compute_predecessors: bool,
pub do_expensive_check: bool,
}
impl BfsParams {
pub fn with_predecessors(mut self) -> Self {
self.compute_predecessors = true;
self
}
pub fn with_depth_limit(mut self, limit: BfsDepthLimit) -> Self {
self.depth_limit = Some(limit);
self
}
pub fn with_expensive_check(mut self) -> Self {
self.do_expensive_check = true;
self
}
pub fn compute_predecessors(&self) -> bool {
self.compute_predecessors
}
pub fn depth_limit(&self) -> Option<BfsDepthLimit> {
self.depth_limit
}
pub fn do_expensive_check(&self) -> bool {
self.do_expensive_check
}
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct BfsPredicateParams<'a, V: CugraphElement, E: CugraphElement = i32> {
pub include_vertices: Option<&'a DeviceArrayView<'a, V>>,
pub exclude_vertices: Option<&'a DeviceArrayView<'a, V>>,
pub target_vertices: Option<&'a DeviceArrayView<'a, V>>,
pub include_edge_ids: Option<&'a DeviceArrayView<'a, E>>,
pub stop_when_any_target_found: bool,
}
impl<V: CugraphElement, E: CugraphElement> Default for BfsPredicateParams<'_, V, E> {
fn default() -> Self {
Self {
include_vertices: None,
exclude_vertices: None,
target_vertices: None,
include_edge_ids: None,
stop_when_any_target_found: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BfsTargetInfo {
pub target_found: bool,
pub target_distance: Option<i64>,
}
pub(crate) fn bfs<V: CugraphElement>(
handle: &EnteredContext<'_>,
graph: &mut Graph<Forward>,
sources: &DeviceArrayView<'_, V>,
params: &BfsParams,
) -> Result<BfsResult> {
let mut result: *mut cugraph_paths_result_t = ptr::null_mut();
let mut error: *mut cugraph_error_t = ptr::null_mut();
let limit = params.depth_limit.map(usize::from).unwrap_or(i32::MAX as usize);
let graph_ptr = graph.as_ptr(handle, "bfs")?;
unsafe {
let code = cugraph_bfs(
handle.handle_ptr(),
graph_ptr,
sources.as_ptr(),
to_bool_t(false), limit,
to_bool_t(params.compute_predecessors),
to_bool_t(params.do_expensive_check),
&mut result,
&mut error,
);
check_error(code, error, handle.device(), "bfs")?;
}
let paths = unsafe { PathsResult::from_raw(result, handle.device(), "bfs", handle.runtime_bound_lease()) }?;
Ok(BfsResult { paths, target_info: None })
}
fn optional_view_ptr<T: CugraphElement>(
view: Option<&DeviceArrayView<'_, T>>,
) -> *const cugraph_type_erased_device_array_view_t {
view.map(|view| view.as_ptr() as *const _).unwrap_or(ptr::null())
}
fn bfs_depth_limit(params: &BfsParams) -> usize {
params.depth_limit.map(usize::from).unwrap_or(i32::MAX as usize)
}
fn validate_predicates<V: CugraphElement, E: CugraphElement>(
graph: &Graph<Forward>,
predicates: &BfsPredicateParams<'_, V, E>,
) -> Result<()> {
let operation = "Graph::bfs_with_predicates";
if predicates.include_vertices.is_some() && predicates.exclude_vertices.is_some() {
return Err(Error::new(ErrorKind::InvalidInput, operation)
.with_message("include_vertices and exclude_vertices are mutually exclusive"));
}
if predicates.include_edge_ids.is_some() {
if !graph.has_edge_ids() {
return Err(Error::new(ErrorKind::InvalidInput, operation)
.with_message("include_edge_ids requires a graph with edge IDs"));
}
let dtype = E::data_type();
if !dtype.is_supported_index_type() {
return Err(Error::new(ErrorKind::InvalidInput, operation)
.with_message(format!("include_edge_ids dtype must be Int32 or Int64, got {dtype}")));
}
if let Some(edge_id_dtype) = graph.edge_id_dtype()
&& dtype != edge_id_dtype
{
return Err(Error::new(ErrorKind::UnsupportedType, operation).with_message(format!(
"include_edge_ids dtype {dtype} does not match graph edge-id dtype {edge_id_dtype}"
)));
}
}
Ok(())
}
struct BfsPredicateResult {
raw: *mut cugraph_bfs_predicate_result_t,
device: i32,
_resource_handle_lease: Option<ResourceHandleLease>,
}
impl BfsPredicateResult {
unsafe fn from_raw(
raw: *mut cugraph_bfs_predicate_result_t,
device: i32,
resource_handle_lease: Option<ResourceHandleLease>,
) -> Result<Self> {
let raw = require_non_null(raw, "bfs_with_predicates", "predicate_result")?;
Ok(Self { raw, device, _resource_handle_lease: resource_handle_lease })
}
fn target_info(&self) -> Result<BfsTargetInfo> {
let found = unsafe { cugraph_bfs_predicate_result_get_target_found(self.raw) };
let target_found = try_from_bool_t(found).ok_or_else(|| {
Error::new(ErrorKind::InvalidState, "bfs_with_predicates")
.with_message(format!("invalid target_found bool_t value: {}", found.0))
})?;
if !target_found {
return Ok(BfsTargetInfo { target_found, target_distance: None });
}
let distance = unsafe { cugraph_bfs_predicate_result_get_target_distance(self.raw) };
let distance = i64::try_from(distance).map_err(|_| {
Error::new(ErrorKind::InvalidState, "bfs_with_predicates")
.with_message("target_distance does not fit in i64")
.with_context("target_distance", distance.to_string())
})?;
Ok(BfsTargetInfo { target_found, target_distance: Some(distance) })
}
}
impl Drop for BfsPredicateResult {
fn drop(&mut self) {
drop_device_resource(
&mut self.raw,
self.device,
"BfsPredicateResult::drop",
DeviceResourceKind::BfsPredicateResult,
);
}
}
pub(crate) fn bfs_with_predicates<V: CugraphElement, E: CugraphElement>(
handle: &EnteredContext<'_>,
graph: &mut Graph<Forward>,
sources: &DeviceArrayView<'_, V>,
params: &BfsParams,
predicates: &BfsPredicateParams<'_, V, E>,
) -> Result<BfsResult> {
validate_predicates(graph, predicates)?;
let mut result: *mut cugraph_paths_result_t = ptr::null_mut();
let mut predicate_result: *mut cugraph_bfs_predicate_result_t = ptr::null_mut();
let mut error: *mut cugraph_error_t = ptr::null_mut();
let predicate_result_out: *mut *mut cugraph_bfs_predicate_result_t =
if predicates.target_vertices.is_some() { &mut predicate_result } else { ptr::null_mut() };
let graph_ptr = graph.as_ptr(handle, "bfs_with_predicates")?;
unsafe {
let code = cugraph_bfs_with_predicates(
handle.handle_ptr(),
graph_ptr,
sources.as_ptr(),
optional_view_ptr(predicates.include_vertices),
optional_view_ptr(predicates.exclude_vertices),
optional_view_ptr(predicates.target_vertices),
optional_view_ptr(predicates.include_edge_ids),
to_bool_t(false), bfs_depth_limit(params),
to_bool_t(params.compute_predecessors),
to_bool_t(predicates.stop_when_any_target_found),
to_bool_t(params.do_expensive_check),
&mut result,
predicate_result_out,
&mut error,
);
check_error(code, error, handle.device(), "bfs_with_predicates")?;
}
let predicate_result = if predicates.target_vertices.is_some() {
Some(unsafe { BfsPredicateResult::from_raw(predicate_result, handle.device(), handle.runtime_bound_lease()) }?)
} else {
None
};
let target_info = predicate_result.as_ref().map(BfsPredicateResult::target_info).transpose()?;
let paths =
unsafe { PathsResult::from_raw(result, handle.device(), "bfs_with_predicates", handle.runtime_bound_lease()) }?;
Ok(BfsResult { paths, target_info })
}
pub struct BfsResult {
pub(crate) paths: PathsResult,
target_info: Option<BfsTargetInfo>,
}
impl std::fmt::Debug for BfsResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BfsResult").finish_non_exhaustive()
}
}
impl BfsResult {
pub fn vertex_ids(&self) -> Result<VertexIds<'_>> {
self.paths.vertex_ids("BfsResult::vertex_ids")
}
pub fn distances(&self) -> Result<Distances<'_>> {
self.paths.distances("BfsResult::distances")
}
pub fn predecessors(&self) -> Result<Option<VertexIds<'_>>> {
self.paths.predecessors("BfsResult::predecessors")
}
pub fn target_info(&self) -> Option<&BfsTargetInfo> {
self.target_info.as_ref()
}
pub fn extract_paths<'a, V: CugraphElement + 'a>(
&self,
handle: &EnteredContext<'_>,
graph: &mut Graph<Forward>,
sources: impl Into<VertexSelection<'a, V>>,
destinations: impl Into<VertexSelection<'a, V>>,
) -> Result<ExtractPathsResult> {
extract_paths(handle, graph, &self.paths, sources, destinations, "BfsResult::extract_paths")
}
pub fn to_dataframe(&self, runtime: &cudf::Runtime) -> Result<cudf::DataFrame> {
bfs_result_to_dataframe(self, runtime).map_err(|e: Error| e.with_operation("BfsResult::to_dataframe"))
}
}