use std::ptr;
use cugraph_sys::{cugraph_error_t, cugraph_hits_allow_nonconvergence, cugraph_hits_result_t};
use super::{HitsResult, non_convergence_error};
use crate::{
array::DeviceArrayView,
error::{Error, ErrorKind, Result, check_error, require_non_null},
graph::{Graph, Transposed},
handle::EnteredContext,
params::{IterationCount, NonNegativeF64},
types::{CugraphElement, to_bool_t},
};
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct HitsParams {
pub epsilon: NonNegativeF64,
pub max_iterations: IterationCount,
pub normalize: bool,
pub do_expensive_check: bool,
}
impl Default for HitsParams {
fn default() -> Self {
Self {
epsilon: NonNegativeF64::new(1e-5).expect("literal is non-negative"),
max_iterations: IterationCount::new(100).expect("literal is non-zero"),
normalize: false,
do_expensive_check: false,
}
}
}
#[derive(Debug)]
pub struct HitsInitialHubs<'a, V: CugraphElement = i32, W: CugraphElement = f32> {
pub vertices: &'a DeviceArrayView<'a, V>,
pub values: &'a DeviceArrayView<'a, W>,
}
pub(crate) fn hits<V: CugraphElement, W: CugraphElement>(
handle: &EnteredContext<'_>,
graph: &mut Graph<Transposed>,
params: &HitsParams,
initial_hubs: Option<&HitsInitialHubs<'_, V, W>>,
) -> Result<HitsResult> {
if let Some(initial_hubs) = initial_hubs {
if initial_hubs.vertices.len() != initial_hubs.values.len() {
return Err(Error::new(ErrorKind::InvalidInput, "hits")
.with_message("initial hubs: vertices and values must have the same length")
.with_context("vertices_len", initial_hubs.vertices.len().to_string())
.with_context("values_len", initial_hubs.values.len().to_string()));
}
graph.validate_vertex_input_dtype(V::data_type(), "hits", "initial hubs vertices")?;
let dtype = W::data_type();
if !dtype.is_float() {
return Err(Error::new(ErrorKind::UnsupportedType, "hits")
.with_message(format!("initial hubs values require Float32 or Float64 values, got {dtype}")));
}
}
let (initial_vertices_ptr, initial_values_ptr) = match initial_hubs {
Some(initial_hubs) => (initial_hubs.vertices.as_ptr() as *const _, initial_hubs.values.as_ptr() as *const _),
None => (ptr::null(), ptr::null()),
};
let do_expensive_check = params.do_expensive_check || initial_hubs.is_some();
let mut result: *mut cugraph_hits_result_t = ptr::null_mut();
let mut error: *mut cugraph_error_t = ptr::null_mut();
let graph_ptr = graph.as_ptr(handle, "hits")?;
unsafe {
let code = cugraph_hits_allow_nonconvergence(
handle.handle_ptr(),
graph_ptr,
params.epsilon.get(),
usize::from(params.max_iterations),
initial_vertices_ptr,
initial_values_ptr,
to_bool_t(params.normalize),
to_bool_t(do_expensive_check),
&mut result,
&mut error,
);
check_error(code, error, handle.device(), "hits")?;
}
let result = require_non_null(result, "hits", "hits_result")?;
let result = unsafe { HitsResult::from_raw(result, handle.device(), handle.runtime_bound_lease())? };
if !result.converged()? {
return Err(non_convergence_error(
"hits",
result.number_of_iterations(),
params.max_iterations,
params.epsilon,
"raise max_iterations or epsilon".to_string(),
));
}
Ok(result)
}