Skip to main content

cugraph/community/
triangle_count.rs

1//! Triangle-counting algorithm.
2
3use std::{marker::PhantomData, ptr};
4
5use cugraph_sys::{
6    cugraph_error_t, cugraph_triangle_count, cugraph_triangle_count_result_get_counts,
7    cugraph_triangle_count_result_get_vertices, cugraph_triangle_count_result_t,
8    cugraph_type_erased_device_array_view_t,
9};
10
11use crate::{
12    array::{DeviceArrayView, RawDeviceArrayView},
13    cuda::{DeviceResourceKind, drop_device_resource},
14    cudf_interop::triangle_count_result_to_dataframe,
15    error::{Error, Result, check_error, require_non_null},
16    graph::Graph,
17    handle::{EnteredContext, ResourceHandleLease},
18    structure::typed_results::{IntegerValues, VertexIds},
19    types::{CugraphElement, to_bool_t},
20};
21
22/// Parameters for [`Graph::triangle_count`](crate::graph::Graph::triangle_count).
23#[derive(Debug, Clone, Copy, Default)]
24#[must_use]
25pub struct TriangleCountParams {
26    /// Whether to run expensive input validation in the C library.
27    pub do_expensive_check: bool,
28}
29
30/// Compute triangle counts for all vertices or a selected start list.
31///
32/// Prefer using [`Graph::triangle_count`](crate::graph::Graph::triangle_count),
33/// which calls this function.
34pub(crate) fn triangle_count<T, V: CugraphElement>(
35    handle: &EnteredContext<'_>,
36    graph: &mut Graph<T>,
37    start: Option<&DeviceArrayView<'_, V>>,
38    params: &TriangleCountParams,
39) -> Result<TriangleCountResult> {
40    let mut result: *mut cugraph_triangle_count_result_t = ptr::null_mut();
41    let mut error: *mut cugraph_error_t = ptr::null_mut();
42    let start_ptr = start.map(|v| v.as_ptr().cast_const()).unwrap_or(ptr::null());
43    let graph_ptr = graph.as_ptr(handle, "triangle_count")?;
44
45    // SAFETY: `start_ptr` is either null or borrowed from a live device view,
46    // and `result`/`error` are valid out-pointers.
47    unsafe {
48        let code = cugraph_triangle_count(
49            handle.handle_ptr(),
50            graph_ptr,
51            start_ptr,
52            to_bool_t(params.do_expensive_check),
53            &mut result,
54            &mut error,
55        );
56        check_error(code, error, handle.device(), "triangle_count")?;
57    }
58
59    let result = require_non_null(result, "triangle_count", "triangle_count_result")?;
60
61    // SAFETY: on success, cuGraph transfers ownership of the result pointer.
62    unsafe { TriangleCountResult::from_raw(result, handle.device(), handle.runtime_bound_lease()) }
63}
64
65/// Result of a triangle-count computation.
66///
67/// Provides vertex ids and per-vertex triangle counts. Freed on drop via
68/// `cugraph_triangle_count_result_free`.
69pub struct TriangleCountResult {
70    raw: *mut cugraph_triangle_count_result_t,
71    device: i32,
72    // Keeps a runtime-bound cuGraph handle alive until after Drop frees `raw`.
73    _resource_handle_lease: Option<ResourceHandleLease>,
74    _nosend_nosync: PhantomData<*mut ()>,
75}
76
77impl std::fmt::Debug for TriangleCountResult {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.debug_struct("TriangleCountResult").finish_non_exhaustive()
80    }
81}
82
83impl TriangleCountResult {
84    /// Wrap a raw triangle-count result pointer.
85    ///
86    /// # Safety
87    ///
88    /// `raw` must be a valid, non-null pointer returned by
89    /// `cugraph_triangle_count`. Ownership is transferred to this struct.
90    pub(crate) unsafe fn from_raw(
91        raw: *mut cugraph_triangle_count_result_t,
92        device: i32,
93        resource_handle_lease: Option<ResourceHandleLease>,
94    ) -> Result<Self> {
95        let raw = require_non_null(raw, "TriangleCountResult::from_raw", "triangle_count_result")?;
96        Ok(Self { raw, device, _resource_handle_lease: resource_handle_lease, _nosend_nosync: PhantomData })
97    }
98
99    fn raw_view(
100        &self,
101        getter: unsafe extern "C" fn(
102            *mut cugraph_triangle_count_result_t,
103        ) -> *mut cugraph_type_erased_device_array_view_t,
104        operation: &'static str,
105        pointer_name: &'static str,
106    ) -> Result<RawDeviceArrayView<'_>> {
107        let view_ptr = require_non_null(
108            // SAFETY: `self.raw` is a live triangle-count result owned by this wrapper.
109            unsafe { getter(self.raw) },
110            operation,
111            pointer_name,
112        )?;
113        // SAFETY: cuGraph allocates this view metadata; this wrapper frees it, and the result retains its backing arrays.
114        unsafe { RawDeviceArrayView::from_owned_view(view_ptr, Some(self.device), operation, pointer_name) }
115    }
116
117    /// Get vertex ids as a runtime-typed device array view.
118    pub fn vertex_ids(&self) -> Result<VertexIds<'_>> {
119        VertexIds::from_raw(self.raw_view(
120            cugraph_triangle_count_result_get_vertices,
121            "TriangleCountResult::vertex_ids",
122            "vertices",
123        )?)
124    }
125
126    /// Get triangle counts as a runtime-typed integer device array view.
127    pub fn counts(&self) -> Result<IntegerValues<'_>> {
128        IntegerValues::from_raw(self.raw_view(
129            cugraph_triangle_count_result_get_counts,
130            "TriangleCountResult::counts",
131            "counts",
132        )?)
133    }
134
135    /// Convert this result to a cudf DataFrame with `"vertex"` and
136    /// `"triangle_count"` columns.
137    pub fn to_dataframe(&self, runtime: &cudf::Runtime) -> Result<cudf::DataFrame> {
138        triangle_count_result_to_dataframe(self, runtime)
139            .map_err(|e: Error| e.with_operation("TriangleCountResult::to_dataframe"))
140    }
141}
142
143impl Drop for TriangleCountResult {
144    fn drop(&mut self) {
145        drop_device_resource(
146            &mut self.raw,
147            self.device,
148            "TriangleCountResult::drop",
149            DeviceResourceKind::TriangleCountResult,
150        );
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use std::ptr;
157
158    use super::*;
159    use crate::error::ErrorKind;
160
161    #[test]
162    fn triangle_count_result_from_raw_rejects_null() {
163        // SAFETY: this exercises the constructor's null guard only.
164        let err = match unsafe { TriangleCountResult::from_raw(ptr::null_mut(), 0, None) } {
165            Err(err) => err,
166            Ok(_) => panic!("null must be rejected"),
167        };
168        assert_eq!(err.kind(), ErrorKind::Unexpected);
169        assert_eq!(err.operation(), "TriangleCountResult::from_raw");
170    }
171}