1use std::{marker::PhantomData, num::NonZeroUsize, ptr};
4
5use cugraph_sys::{
6 cugraph_core_number, cugraph_core_result_get_core_numbers, cugraph_core_result_get_vertices, cugraph_core_result_t,
7 cugraph_error_t, cugraph_k_core, cugraph_k_core_degree_type_t, cugraph_k_core_degree_type_t_K_CORE_DEGREE_TYPE_IN,
8 cugraph_k_core_degree_type_t_K_CORE_DEGREE_TYPE_INOUT, cugraph_k_core_degree_type_t_K_CORE_DEGREE_TYPE_OUT,
9 cugraph_k_core_result_get_dst_vertices, cugraph_k_core_result_get_src_vertices, cugraph_k_core_result_get_weights,
10 cugraph_k_core_result_t, cugraph_type_erased_device_array_view_t,
11};
12
13use crate::{
14 array::RawDeviceArrayView,
15 cuda::{DeviceResourceKind, drop_device_resource},
16 cudf_interop::{core_number_result_to_dataframe, k_core_result_to_dataframe},
17 error::{Error, Result, check_error, require_non_null},
18 graph::Graph,
19 handle::{EnteredContext, ResourceHandleLease},
20 structure::typed_results::{IntegerValues, ScoreValues, VertexIds},
21 types::to_bool_t,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
26pub enum KCoreDegreeType {
27 In,
29 Out,
31 #[default]
33 InOut,
34}
35
36impl From<KCoreDegreeType> for cugraph_k_core_degree_type_t {
37 fn from(value: KCoreDegreeType) -> Self {
38 match value {
39 KCoreDegreeType::In => cugraph_k_core_degree_type_t_K_CORE_DEGREE_TYPE_IN,
40 KCoreDegreeType::Out => cugraph_k_core_degree_type_t_K_CORE_DEGREE_TYPE_OUT,
41 KCoreDegreeType::InOut => cugraph_k_core_degree_type_t_K_CORE_DEGREE_TYPE_INOUT,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, Default)]
48#[must_use]
49pub struct CoreNumberParams {
50 pub degree_type: KCoreDegreeType,
52 pub do_expensive_check: bool,
54}
55
56#[derive(Debug, Clone, Copy)]
58#[must_use]
59pub struct KCoreParams {
60 pub k: NonZeroUsize,
62 pub degree_type: KCoreDegreeType,
64 pub do_expensive_check: bool,
66}
67
68impl Default for KCoreParams {
69 fn default() -> Self {
70 Self {
71 k: unsafe { NonZeroUsize::new_unchecked(2) },
73 degree_type: KCoreDegreeType::default(),
74 do_expensive_check: false,
75 }
76 }
77}
78
79pub(crate) fn core_number<T>(
84 handle: &EnteredContext<'_>,
85 graph: &mut Graph<T>,
86 params: &CoreNumberParams,
87) -> Result<CoreNumberResult> {
88 let mut result: *mut cugraph_core_result_t = ptr::null_mut();
89 let mut error: *mut cugraph_error_t = ptr::null_mut();
90 let graph_ptr = graph.as_ptr(handle, "core_number")?;
91
92 unsafe {
95 let code = cugraph_core_number(
96 handle.handle_ptr(),
97 graph_ptr,
98 params.degree_type.into(),
99 to_bool_t(params.do_expensive_check),
100 &mut result,
101 &mut error,
102 );
103 check_error(code, error, handle.device(), "core_number")?;
104 }
105
106 let result = require_non_null(result, "core_number", "core_result")?;
107
108 unsafe { CoreNumberResult::from_raw(result, handle.device(), handle.runtime_bound_lease()) }
110}
111
112pub(crate) fn k_core<T>(
114 handle: &EnteredContext<'_>,
115 graph: &mut Graph<T>,
116 params: &KCoreParams,
117 core_numbers: Option<&CoreNumberResult>,
118) -> Result<KCoreResult> {
119 let mut result: *mut cugraph_k_core_result_t = ptr::null_mut();
120 let mut error: *mut cugraph_error_t = ptr::null_mut();
121 let core_result_ptr = core_numbers.map(CoreNumberResult::as_ptr).unwrap_or(ptr::null());
122 let graph_ptr = graph.as_ptr(handle, "k_core")?;
123
124 unsafe {
127 let code = cugraph_k_core(
128 handle.handle_ptr(),
129 graph_ptr,
130 params.k.get(),
131 params.degree_type.into(),
132 core_result_ptr,
133 to_bool_t(params.do_expensive_check),
134 &mut result,
135 &mut error,
136 );
137 check_error(code, error, handle.device(), "k_core")?;
138 }
139
140 let result = require_non_null(result, "k_core", "k_core_result")?;
141
142 unsafe { KCoreResult::from_raw(result, handle.device(), handle.runtime_bound_lease()) }
144}
145
146pub struct CoreNumberResult {
151 raw: *mut cugraph_core_result_t,
152 device: i32,
153 _resource_handle_lease: Option<ResourceHandleLease>,
155 _nosend_nosync: PhantomData<*mut ()>,
156}
157
158impl std::fmt::Debug for CoreNumberResult {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.debug_struct("CoreNumberResult").finish_non_exhaustive()
161 }
162}
163
164impl CoreNumberResult {
165 pub(crate) unsafe fn from_raw(
172 raw: *mut cugraph_core_result_t,
173 device: i32,
174 resource_handle_lease: Option<ResourceHandleLease>,
175 ) -> Result<Self> {
176 let raw = require_non_null(raw, "CoreNumberResult::from_raw", "core_result")?;
177 Ok(Self { raw, device, _resource_handle_lease: resource_handle_lease, _nosend_nosync: PhantomData })
178 }
179
180 pub(crate) fn as_ptr(&self) -> *const cugraph_core_result_t {
181 self.raw.cast_const()
182 }
183
184 fn raw_view(
185 &self,
186 getter: unsafe extern "C" fn(
187 *mut cugraph_core_result_t,
188 ) -> *mut cugraph_sys::cugraph_type_erased_device_array_view_t,
189 operation: &'static str,
190 pointer_name: &'static str,
191 ) -> Result<RawDeviceArrayView<'_>> {
192 let view_ptr = require_non_null(
193 unsafe { getter(self.raw) },
195 operation,
196 pointer_name,
197 )?;
198 unsafe { RawDeviceArrayView::from_owned_view(view_ptr, Some(self.device), operation, pointer_name) }
201 }
202
203 pub fn vertex_ids(&self) -> Result<VertexIds<'_>> {
205 VertexIds::from_raw(self.raw_view(
206 cugraph_core_result_get_vertices,
207 "CoreNumberResult::vertex_ids",
208 "vertices",
209 )?)
210 }
211
212 pub fn core_numbers(&self) -> Result<IntegerValues<'_>> {
214 IntegerValues::from_raw(self.raw_view(
215 cugraph_core_result_get_core_numbers,
216 "CoreNumberResult::core_numbers",
217 "core_numbers",
218 )?)
219 }
220
221 pub fn to_dataframe(&self, runtime: &cudf::Runtime) -> Result<cudf::DataFrame> {
225 core_number_result_to_dataframe(self, runtime)
226 .map_err(|e: Error| e.with_operation("CoreNumberResult::to_dataframe"))
227 }
228}
229
230impl Drop for CoreNumberResult {
231 fn drop(&mut self) {
232 drop_device_resource(&mut self.raw, self.device, "CoreNumberResult::drop", DeviceResourceKind::CoreResult);
233 }
234}
235
236pub struct KCoreResult {
241 raw: *mut cugraph_k_core_result_t,
242 device: i32,
243 _resource_handle_lease: Option<ResourceHandleLease>,
245 _nosend_nosync: PhantomData<*mut ()>,
246}
247
248impl std::fmt::Debug for KCoreResult {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 f.debug_struct("KCoreResult").finish_non_exhaustive()
251 }
252}
253
254impl KCoreResult {
255 pub(crate) unsafe fn from_raw(
262 raw: *mut cugraph_k_core_result_t,
263 device: i32,
264 resource_handle_lease: Option<ResourceHandleLease>,
265 ) -> Result<Self> {
266 let raw = require_non_null(raw, "KCoreResult::from_raw", "k_core_result")?;
267 Ok(Self { raw, device, _resource_handle_lease: resource_handle_lease, _nosend_nosync: PhantomData })
268 }
269
270 fn raw_view(
271 &self,
272 getter: unsafe extern "C" fn(*mut cugraph_k_core_result_t) -> *mut cugraph_type_erased_device_array_view_t,
273 operation: &'static str,
274 pointer_name: &'static str,
275 ) -> Result<RawDeviceArrayView<'_>> {
276 let view_ptr = require_non_null(
277 unsafe { getter(self.raw) },
279 operation,
280 pointer_name,
281 )?;
282 unsafe { RawDeviceArrayView::from_owned_view(view_ptr, Some(self.device), operation, pointer_name) }
284 }
285
286 fn optional_raw_view(
287 &self,
288 getter: unsafe extern "C" fn(*mut cugraph_k_core_result_t) -> *mut cugraph_type_erased_device_array_view_t,
289 operation: &'static str,
290 ) -> Result<Option<RawDeviceArrayView<'_>>> {
291 let view_ptr = unsafe { getter(self.raw) };
293 if view_ptr.is_null() {
294 return Ok(None);
295 }
296
297 unsafe { RawDeviceArrayView::from_owned_view(view_ptr, Some(self.device), operation, "optional_view") }
300 .map(Some)
301 }
302
303 pub fn sources(&self) -> Result<VertexIds<'_>> {
305 VertexIds::from_raw(self.raw_view(cugraph_k_core_result_get_src_vertices, "KCoreResult::sources", "sources")?)
306 }
307
308 pub fn destinations(&self) -> Result<VertexIds<'_>> {
310 VertexIds::from_raw(self.raw_view(
311 cugraph_k_core_result_get_dst_vertices,
312 "KCoreResult::destinations",
313 "destinations",
314 )?)
315 }
316
317 pub fn weights(&self) -> Result<Option<ScoreValues<'_>>> {
319 self.optional_raw_view(cugraph_k_core_result_get_weights, "KCoreResult::weights")?
320 .map(ScoreValues::from_raw)
321 .transpose()
322 }
323
324 pub fn to_dataframe(&self, runtime: &cudf::Runtime) -> Result<cudf::DataFrame> {
329 k_core_result_to_dataframe(self, runtime).map_err(|e: Error| e.with_operation("KCoreResult::to_dataframe"))
330 }
331}
332
333impl Drop for KCoreResult {
334 fn drop(&mut self) {
335 drop_device_resource(&mut self.raw, self.device, "KCoreResult::drop", DeviceResourceKind::KCoreResult);
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use std::ptr;
342
343 use super::*;
344 use crate::error::ErrorKind;
345
346 #[test]
347 fn core_number_result_from_raw_rejects_null() {
348 let err = match unsafe { CoreNumberResult::from_raw(ptr::null_mut(), 0, None) } {
350 Err(err) => err,
351 Ok(_) => panic!("null must be rejected"),
352 };
353 assert_eq!(err.kind(), ErrorKind::Unexpected);
354 assert_eq!(err.operation(), "CoreNumberResult::from_raw");
355 }
356
357 #[test]
358 fn k_core_result_from_raw_rejects_null() {
359 let err = match unsafe { KCoreResult::from_raw(ptr::null_mut(), 0, None) } {
361 Err(err) => err,
362 Ok(_) => panic!("null must be rejected"),
363 };
364 assert_eq!(err.kind(), ErrorKind::Unexpected);
365 assert_eq!(err.operation(), "KCoreResult::from_raw");
366 }
367}