use std::{num::NonZeroUsize, ptr};
use cugraph_sys::{
cugraph_analyze_clustering_edge_cut, cugraph_analyze_clustering_modularity, cugraph_analyze_clustering_ratio_cut,
cugraph_clustering_result_get_clusters, cugraph_clustering_result_get_vertices, cugraph_clustering_result_t,
cugraph_error_t, cugraph_spectral_modularity_maximization,
};
use crate::{
array::{DeviceArrayView, RawDeviceArrayView},
cuda::{DeviceResourceKind, drop_device_resource},
error::{Error, ErrorKind, Result, check_error, require_non_null},
graph::{Forward, Graph},
handle::{EnteredContext, ResourceHandleLease},
params::{ClusterCount, HalfOpenUnitF64},
random::RngState,
structure::VertexIds,
types::{CugraphElement, to_bool_t},
};
const IGNORED_ITERATION_LIMIT: i32 = 100;
const IGNORED_K_MEANS_TOLERANCE: f64 = 0.001;
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct SpectralClusteringParams {
pub n_clusters: ClusterCount,
pub n_eigenvectors: NonZeroUsize,
pub evs_tolerance: HalfOpenUnitF64,
pub do_expensive_check: bool,
}
impl Default for SpectralClusteringParams {
fn default() -> Self {
Self {
n_clusters: ClusterCount::new(2).expect("literal is at least 2"),
n_eigenvectors: NonZeroUsize::new(2).expect("literal is non-zero"),
evs_tolerance: HalfOpenUnitF64::new(0.01).expect("literal is in [0, 1)"),
do_expensive_check: false,
}
}
}
pub(crate) struct ClusteringResult {
raw: *mut cugraph_clustering_result_t,
device: i32,
_resource_handle_lease: Option<ResourceHandleLease>,
_nosend_nosync: std::marker::PhantomData<*mut ()>,
}
impl ClusteringResult {
pub(crate) unsafe fn from_raw(
raw: *mut cugraph_clustering_result_t,
device: i32,
resource_handle_lease: Option<ResourceHandleLease>,
) -> Result<Self> {
let raw = require_non_null(raw, "ClusteringResult::from_raw", "clustering_result")?;
Ok(Self {
raw,
device,
_resource_handle_lease: resource_handle_lease,
_nosend_nosync: std::marker::PhantomData,
})
}
fn raw_view(
&self,
getter: unsafe extern "C" fn(
*mut cugraph_clustering_result_t,
) -> *mut cugraph_sys::cugraph_type_erased_device_array_view_t,
operation: &'static str,
pointer_name: &'static str,
) -> Result<RawDeviceArrayView<'_>> {
let view_ptr = require_non_null(
unsafe { getter(self.raw) },
operation,
pointer_name,
)?;
unsafe { RawDeviceArrayView::from_owned_view(view_ptr, Some(self.device), operation, pointer_name) }
}
fn vertices(&self, operation: &'static str) -> Result<VertexIds<'_>> {
VertexIds::from_raw(self.raw_view(cugraph_clustering_result_get_vertices, operation, "vertices")?)
}
fn clusters(&self, operation: &'static str) -> Result<VertexIds<'_>> {
VertexIds::from_raw(self.raw_view(cugraph_clustering_result_get_clusters, operation, "clusters")?)
}
}
impl Drop for ClusteringResult {
fn drop(&mut self) {
drop_device_resource(
&mut self.raw,
self.device,
"ClusteringResult::drop",
DeviceResourceKind::ClusteringResult,
);
}
}
macro_rules! define_clustering_result {
($name:ident, $df_fn:ident) => {
pub struct $name(ClusteringResult);
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($name)).finish_non_exhaustive()
}
}
impl $name {
pub(crate) unsafe fn from_raw(
raw: *mut cugraph_clustering_result_t,
device: i32,
resource_handle_lease: Option<ResourceHandleLease>,
) -> Result<Self> {
Ok(Self(unsafe { ClusteringResult::from_raw(raw, device, resource_handle_lease) }?))
}
pub fn vertex_ids(&self) -> Result<VertexIds<'_>> {
self.0.vertices(concat!(stringify!($name), "::vertex_ids"))
}
pub fn clusters(&self) -> Result<VertexIds<'_>> {
self.0.clusters(concat!(stringify!($name), "::clusters"))
}
pub fn to_dataframe(&self, runtime: &cudf::Runtime) -> Result<cudf::DataFrame> {
crate::cudf_interop::$df_fn(self, runtime)
.map_err(|e: Error| e.with_operation(concat!(stringify!($name), "::to_dataframe")))
}
}
};
}
define_clustering_result!(SpectralClusteringResult, spectral_clustering_result_to_dataframe);
pub(crate) fn spectral_modularity_maximization(
handle: &EnteredContext<'_>,
rng_state: &mut RngState,
graph: &mut Graph<Forward>,
params: &SpectralClusteringParams,
) -> Result<SpectralClusteringResult> {
let raw = run_clustering(
handle,
rng_state,
graph,
params,
cugraph_spectral_modularity_maximization,
"spectral_modularity_maximization",
)?;
unsafe { SpectralClusteringResult::from_raw(raw, handle.device(), handle.runtime_bound_lease()) }
}
type ClusteringFn = unsafe extern "C" fn(
*const cugraph_sys::cugraph_resource_handle_t,
*mut cugraph_sys::cugraph_rng_state_t,
*mut cugraph_sys::cugraph_graph_t,
usize,
usize,
f64,
i32,
f64,
i32,
cugraph_sys::bool_t,
*mut *mut cugraph_clustering_result_t,
*mut *mut cugraph_error_t,
) -> cugraph_sys::cugraph_error_code_t;
fn run_clustering(
handle: &EnteredContext<'_>,
rng_state: &mut RngState,
graph: &mut Graph<Forward>,
params: &SpectralClusteringParams,
function: ClusteringFn,
operation: &'static str,
) -> Result<*mut cugraph_clustering_result_t> {
let mut result: *mut cugraph_clustering_result_t = ptr::null_mut();
let mut error: *mut cugraph_error_t = ptr::null_mut();
let graph_ptr = graph.as_ptr(handle, operation)?;
unsafe {
let code = function(
handle.handle_ptr(),
rng_state.as_ptr(),
graph_ptr,
params.n_clusters.get(),
params.n_eigenvectors.get(),
params.evs_tolerance.get(),
IGNORED_ITERATION_LIMIT,
IGNORED_K_MEANS_TOLERANCE,
IGNORED_ITERATION_LIMIT,
to_bool_t(params.do_expensive_check),
&mut result,
&mut error,
);
check_error(code, error, handle.device(), operation)?;
}
require_non_null(result, operation, "clustering_result")
}
pub(crate) fn analyze_clustering_modularity<V: CugraphElement>(
handle: &EnteredContext<'_>,
graph: &mut Graph<Forward>,
n_clusters: NonZeroUsize,
vertices: &DeviceArrayView<'_, V>,
clusters: &DeviceArrayView<'_, V>,
) -> Result<f64> {
analyze_clustering(
handle,
graph,
n_clusters,
vertices,
clusters,
cugraph_analyze_clustering_modularity,
"analyze_clustering_modularity",
)
}
pub(crate) fn analyze_clustering_edge_cut<V: CugraphElement>(
handle: &EnteredContext<'_>,
graph: &mut Graph<Forward>,
n_clusters: NonZeroUsize,
vertices: &DeviceArrayView<'_, V>,
clusters: &DeviceArrayView<'_, V>,
) -> Result<f64> {
analyze_clustering(
handle,
graph,
n_clusters,
vertices,
clusters,
cugraph_analyze_clustering_edge_cut,
"analyze_clustering_edge_cut",
)
}
pub(crate) fn analyze_clustering_ratio_cut<V: CugraphElement>(
handle: &EnteredContext<'_>,
graph: &mut Graph<Forward>,
n_clusters: NonZeroUsize,
vertices: &DeviceArrayView<'_, V>,
clusters: &DeviceArrayView<'_, V>,
) -> Result<f64> {
analyze_clustering(
handle,
graph,
n_clusters,
vertices,
clusters,
cugraph_analyze_clustering_ratio_cut,
"analyze_clustering_ratio_cut",
)
}
type AnalyzeFn = unsafe extern "C" fn(
*const cugraph_sys::cugraph_resource_handle_t,
*mut cugraph_sys::cugraph_graph_t,
usize,
*const cugraph_sys::cugraph_type_erased_device_array_view_t,
*const cugraph_sys::cugraph_type_erased_device_array_view_t,
*mut f64,
*mut *mut cugraph_error_t,
) -> cugraph_sys::cugraph_error_code_t;
fn analyze_clustering<V: CugraphElement>(
handle: &EnteredContext<'_>,
graph: &mut Graph<Forward>,
n_clusters: NonZeroUsize,
vertices: &DeviceArrayView<'_, V>,
clusters: &DeviceArrayView<'_, V>,
function: AnalyzeFn,
operation: &'static str,
) -> Result<f64> {
graph.validate_vertex_input_dtype(V::data_type(), operation, "vertices")?;
if vertices.len() != clusters.len() {
return Err(Error::new(ErrorKind::InvalidInput, operation)
.with_message("vertices and clusters must have the same length")
.with_context("vertices_len", vertices.len().to_string())
.with_context("clusters_len", clusters.len().to_string()));
}
let mut score = 0.0f64;
let mut error: *mut cugraph_error_t = ptr::null_mut();
let graph_ptr = graph.as_ptr(handle, operation)?;
unsafe {
let code = function(
handle.handle_ptr(),
graph_ptr,
n_clusters.get(),
vertices.as_ptr(),
clusters.as_ptr(),
&mut score,
&mut error,
);
check_error(code, error, handle.device(), operation)?;
}
Ok(score)
}
#[cfg(test)]
mod tests {
use std::ptr;
use super::*;
#[test]
fn clustering_result_from_raw_rejects_null() {
let err = match unsafe { ClusteringResult::from_raw(ptr::null_mut(), 0, None) } {
Err(err) => err,
Ok(_) => panic!("null must be rejected"),
};
assert_eq!(err.kind(), ErrorKind::Unexpected);
assert_eq!(err.operation(), "ClusteringResult::from_raw");
}
}