use std::{
cell::{Cell, RefCell},
marker::PhantomData,
ptr,
};
use cugraph_sys::{
cugraph_error_t, cugraph_graph_create_with_times_sg, cugraph_graph_t, cugraph_type_erased_device_array_view_t,
cugraph_type_erased_device_array_view_type,
};
use super::{Graph, Transposition};
use crate::{
array::{DeviceArrayView, RawDeviceArrayView},
error::{Error, ErrorKind, Result, check_error, require_non_null},
handle::EnteredContext,
types::{CugraphElement, DataType, GraphProperties, to_bool_t},
};
#[must_use]
#[derive(Debug)]
pub struct GraphBuilder<'a> {
properties: GraphProperties,
src: *mut cugraph_type_erased_device_array_view_t,
src_len: usize,
dst: *mut cugraph_type_erased_device_array_view_t,
dst_len: usize,
weights: Option<(*mut cugraph_type_erased_device_array_view_t, usize)>,
edge_ids: Option<(*mut cugraph_type_erased_device_array_view_t, usize)>,
edge_type_ids: Option<(*mut cugraph_type_erased_device_array_view_t, usize)>,
vertices: Option<(*mut cugraph_type_erased_device_array_view_t, usize)>,
edge_start_time_ids: Option<(*mut cugraph_type_erased_device_array_view_t, usize)>,
edge_end_time_ids: Option<(*mut cugraph_type_erased_device_array_view_t, usize)>,
pub(super) vertex_dtype: DataType,
pub(super) weight_dtype: DataType,
renumber: bool,
drop_self_loops: bool,
drop_multi_edges: bool,
symmetrize: bool,
do_expensive_check: bool,
_lifetime: PhantomData<&'a ()>,
}
impl Clone for GraphBuilder<'_> {
fn clone(&self) -> Self {
*self
}
}
impl Copy for GraphBuilder<'_> {}
impl<'a> GraphBuilder<'a> {
pub fn new<V: CugraphElement>(src: &'a DeviceArrayView<'a, V>, dst: &'a DeviceArrayView<'a, V>) -> Self {
Self {
properties: GraphProperties::default(),
src: src.as_ptr(),
src_len: src.len(),
dst: dst.as_ptr(),
dst_len: dst.len(),
weights: None,
edge_ids: None,
edge_type_ids: None,
vertices: None,
edge_start_time_ids: None,
edge_end_time_ids: None,
vertex_dtype: V::data_type(),
weight_dtype: DataType::Float32,
renumber: false,
drop_self_loops: false,
drop_multi_edges: false,
symmetrize: false,
do_expensive_check: false,
_lifetime: PhantomData,
}
}
pub fn properties(mut self, properties: GraphProperties) -> Self {
self.properties = properties;
self
}
pub fn weights<W: CugraphElement>(mut self, weights: &'a DeviceArrayView<'a, W>) -> Self {
self.weights = Some((weights.as_ptr(), weights.len()));
self.weight_dtype = W::data_type();
self
}
pub fn edge_ids<E: CugraphElement>(mut self, edge_ids: &'a DeviceArrayView<'a, E>) -> Self {
self.edge_ids = Some((edge_ids.as_ptr(), edge_ids.len()));
self
}
pub fn edge_type_ids<E: CugraphElement>(mut self, edge_type_ids: &'a DeviceArrayView<'a, E>) -> Self {
self.edge_type_ids = Some((edge_type_ids.as_ptr(), edge_type_ids.len()));
self
}
pub fn vertices<V: CugraphElement>(mut self, vertices: &'a DeviceArrayView<'a, V>) -> Self {
self.vertices = Some((vertices.as_ptr(), vertices.len()));
self
}
pub fn edge_start_time_ids<E: CugraphElement>(mut self, times: &'a DeviceArrayView<'a, E>) -> Self {
self.edge_start_time_ids = Some((times.as_ptr(), times.len()));
self
}
pub fn edge_end_time_ids<E: CugraphElement>(mut self, times: &'a DeviceArrayView<'a, E>) -> Self {
self.edge_end_time_ids = Some((times.as_ptr(), times.len()));
self
}
pub fn renumber(mut self) -> Self {
self.renumber = true;
self
}
pub fn drop_self_loops(mut self) -> Self {
self.drop_self_loops = true;
self
}
pub fn drop_multi_edges(mut self) -> Self {
self.drop_multi_edges = true;
self
}
pub fn symmetrize(mut self) -> Self {
self.symmetrize = true;
self
}
pub fn do_expensive_check(mut self) -> Self {
self.do_expensive_check = true;
self
}
pub fn build<T: Transposition>(&self, handle: &EnteredContext<'_>) -> Result<Graph<T>> {
self.build_inner(handle, T::store_transposed())
}
pub(crate) fn build_inner<T>(&self, handle: &EnteredContext<'_>, store_transposed: bool) -> Result<Graph<T>> {
self.validate()?;
let edge_id_dtype = self.edge_ids.map(|(ptr, _)| Self::view_dtype(ptr)).transpose()?;
let c_props = self.properties.into();
let vertices_ptr = self.vertices.map(|(p, _)| p as *const _).unwrap_or(ptr::null());
let weights_ptr = self.weights.map(|(p, _)| p as *const _).unwrap_or(ptr::null());
let edge_ids_ptr = self.edge_ids.map(|(p, _)| p as *const _).unwrap_or(ptr::null());
let edge_type_ids_ptr = self.edge_type_ids.map(|(p, _)| p as *const _).unwrap_or(ptr::null());
let start_times_ptr = self.edge_start_time_ids.map(|(p, _)| p as *const _).unwrap_or(ptr::null());
let end_times_ptr = self.edge_end_time_ids.map(|(p, _)| p as *const _).unwrap_or(ptr::null());
let mut graph: *mut cugraph_graph_t = ptr::null_mut();
let mut error: *mut cugraph_error_t = ptr::null_mut();
unsafe {
let code = cugraph_graph_create_with_times_sg(
handle.handle_ptr(),
&c_props,
vertices_ptr,
self.src,
self.dst,
weights_ptr,
edge_ids_ptr,
edge_type_ids_ptr,
start_times_ptr,
end_times_ptr,
to_bool_t(store_transposed),
to_bool_t(self.renumber),
to_bool_t(self.drop_self_loops),
to_bool_t(self.drop_multi_edges),
to_bool_t(self.symmetrize),
to_bool_t(self.do_expensive_check),
&mut graph,
&mut error,
);
check_error(code, error, handle.device(), "GraphBuilder::build")?;
}
let graph = require_non_null(graph, "GraphBuilder::build", "graph")?;
Ok(Graph {
raw: graph,
device: handle.device(),
properties: self.properties,
vertex_dtype: self.vertex_dtype,
weight_dtype: self.weight_dtype,
has_weights: self.weights.is_some(),
edge_id_dtype,
pending_input_completion: None,
resource_handle_leases: RefCell::new(handle.runtime_bound_lease().into_iter().collect()),
ordered_binding: Cell::new(handle.runtime_bound_lease().map(|lease| lease.runtime().binding_id())),
untracked_resource_handle_use: Cell::new(false),
_nosend_nosync: PhantomData,
_transposition: PhantomData,
})
}
pub(crate) fn from_raw_views(
properties: GraphProperties,
src: &'a RawDeviceArrayView<'a>,
dst: &'a RawDeviceArrayView<'a>,
) -> Result<Self> {
Ok(Self {
properties,
src: src.as_ptr(),
src_len: src.len(),
dst: dst.as_ptr(),
dst_len: dst.len(),
weights: None,
edge_ids: None,
edge_type_ids: None,
vertices: None,
edge_start_time_ids: None,
edge_end_time_ids: None,
vertex_dtype: src.dtype()?,
weight_dtype: DataType::Float32,
renumber: false,
drop_self_loops: false,
drop_multi_edges: false,
symmetrize: false,
do_expensive_check: false,
_lifetime: PhantomData,
})
}
pub(crate) fn weights_raw(mut self, raw: &'a RawDeviceArrayView<'a>) -> Result<Self> {
self.weights = Some((raw.as_ptr(), raw.len()));
self.weight_dtype = raw.dtype()?;
Ok(self)
}
pub(crate) fn edge_ids_raw(mut self, raw: &'a RawDeviceArrayView<'a>) -> Result<Self> {
self.edge_ids = Some((raw.as_ptr(), raw.len()));
Ok(self)
}
pub(crate) fn renumber_flag(mut self, renumber: bool) -> Self {
self.renumber = renumber;
self
}
fn validate(&self) -> Result<()> {
let edge_count = self.src_len;
if self.dst_len != edge_count {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message("src and dst must have the same number of elements")
.with_context("src_len", edge_count.to_string())
.with_context("dst_len", self.dst_len.to_string()));
}
Self::check_optional_len(self.weights, edge_count, "weights")?;
Self::check_optional_len(self.edge_ids, edge_count, "edge_ids")?;
Self::check_optional_len(self.edge_type_ids, edge_count, "edge_type_ids")?;
Self::check_optional_len(self.edge_start_time_ids, edge_count, "edge_start_time_ids")?;
Self::check_optional_len(self.edge_end_time_ids, edge_count, "edge_end_time_ids")?;
if self.symmetrize && self.edge_ids.is_some() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message("symmetrize cannot be combined with edge_ids"));
}
if self.symmetrize && self.edge_type_ids.is_some() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message("symmetrize cannot be combined with edge_type_ids"));
}
let has_start = self.edge_start_time_ids.is_some();
let has_end = self.edge_end_time_ids.is_some();
if has_start != has_end {
let missing = if has_start { "edge_end_time_ids" } else { "edge_start_time_ids" };
let present = if has_start { "edge_start_time_ids" } else { "edge_end_time_ids" };
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build").with_message(format!(
"{present} was provided but {missing} was not; both must be provided or both omitted"
)));
}
self.validate_dtypes()?;
Ok(())
}
fn validate_dtypes(&self) -> Result<()> {
let src_dtype = Self::view_dtype(self.src)?;
let dst_dtype = Self::view_dtype(self.dst)?;
if src_dtype != dst_dtype {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message("src and dst must have the same dtype")
.with_context("src_dtype", src_dtype.to_string())
.with_context("dst_dtype", dst_dtype.to_string()));
}
if !src_dtype.is_supported_index_type() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("src/dst dtype must be Int32 or Int64, got {src_dtype}")));
}
if let Some((ptr, _)) = self.weights {
let dtype = Self::view_dtype(ptr)?;
if !dtype.is_float() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("weights dtype must be Float32 or Float64, got {dtype}")));
}
}
if let Some((ptr, _)) = self.edge_ids {
let dtype = Self::view_dtype(ptr)?;
if !dtype.is_supported_index_type() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("edge_ids dtype must be Int32 or Int64, got {dtype}")));
}
if dtype != src_dtype {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(
"edge_ids must have the same dtype as src/dst to prevent silent truncation by the C API",
)
.with_context("edge_ids_dtype", dtype.to_string())
.with_context("src_dst_dtype", src_dtype.to_string()));
}
}
if let Some((ptr, _)) = self.edge_type_ids {
let dtype = Self::view_dtype(ptr)?;
if dtype != DataType::Int32 {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("edge_type_ids dtype must be Int32, got {dtype}")));
}
}
if let Some((ptr, _)) = self.edge_start_time_ids {
let dtype = Self::view_dtype(ptr)?;
if !dtype.is_supported_index_type() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("edge_start_time_ids dtype must be Int32 or Int64, got {dtype}")));
}
}
if let Some((ptr, _)) = self.edge_end_time_ids {
let dtype = Self::view_dtype(ptr)?;
if !dtype.is_supported_index_type() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("edge_end_time_ids dtype must be Int32 or Int64, got {dtype}")));
}
}
if let (Some((start_ptr, _)), Some((end_ptr, _))) = (self.edge_start_time_ids, self.edge_end_time_ids) {
let start_dtype = Self::view_dtype(start_ptr)?;
let end_dtype = Self::view_dtype(end_ptr)?;
if start_dtype != end_dtype {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message("edge_start_time_ids and edge_end_time_ids must have the same dtype")
.with_context("start_dtype", start_dtype.to_string())
.with_context("end_dtype", end_dtype.to_string()));
}
}
if let Some((ptr, _)) = self.vertices {
let dtype = Self::view_dtype(ptr)?;
if !dtype.is_supported_index_type() {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("vertices dtype must be Int32 or Int64, got {dtype}")));
}
if dtype != src_dtype {
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(
"vertices must have the same dtype as src/dst to prevent silent truncation by the C API",
)
.with_context("vertices_dtype", dtype.to_string())
.with_context("src_dst_dtype", src_dtype.to_string()));
}
}
Ok(())
}
fn view_dtype(ptr: *mut cugraph_type_erased_device_array_view_t) -> Result<DataType> {
let c_type = unsafe { cugraph_type_erased_device_array_view_type(ptr) };
DataType::try_from(c_type).map_err(|err| err.with_operation("view_dtype"))
}
fn check_optional_len(
array: Option<(*mut cugraph_type_erased_device_array_view_t, usize)>,
expected: usize,
name: &'static str,
) -> Result<()> {
if let Some((_, len)) = array
&& len != expected
{
return Err(Error::new(ErrorKind::InvalidInput, "GraphBuilder::build")
.with_message(format!("{name} must have the same number of elements as src/dst"))
.with_context("src_len", expected.to_string())
.with_context(name, len.to_string()));
}
Ok(())
}
}