1use std::ptr;
4
5use cugraph_sys::{
6 cugraph_centrality_result_t, cugraph_error_t, cugraph_pagerank_allow_nonconvergence,
7 cugraph_personalized_pagerank_allow_nonconvergence,
8};
9
10use super::CentralityResult;
11use crate::{
12 array::DeviceArrayView,
13 cuda::{DeviceResourceKind, drop_device_resource},
14 error::{Error, ErrorKind, Result, check_error, require_non_null},
15 graph::{Graph, Transposed},
16 handle::EnteredContext,
17 params::{ClosedUnitF64, IterationCount, NonNegativeF64, defaults},
18 types::{CugraphElement, to_bool_t},
19};
20
21#[derive(Debug, Clone, Copy)]
44#[must_use]
45pub struct PageRankParams {
46 pub alpha: ClosedUnitF64,
48 pub epsilon: NonNegativeF64,
51 pub max_iterations: IterationCount,
53 pub do_expensive_check: bool,
56}
57
58impl Default for PageRankParams {
59 fn default() -> Self {
60 Self {
61 alpha: defaults::PAGERANK_ALPHA,
62 epsilon: defaults::PAGERANK_EPSILON,
63 max_iterations: defaults::PAGERANK_MAX_ITERATIONS,
64 do_expensive_check: false,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy)]
75#[must_use]
76pub struct PersonalizedPageRankParams {
77 pub alpha: ClosedUnitF64,
79 pub epsilon: NonNegativeF64,
81 pub max_iterations: IterationCount,
83 pub do_expensive_check: bool,
86}
87
88impl Default for PersonalizedPageRankParams {
89 fn default() -> Self {
90 Self {
91 alpha: defaults::PAGERANK_ALPHA,
92 epsilon: defaults::PAGERANK_EPSILON,
93 max_iterations: defaults::PAGERANK_MAX_ITERATIONS,
94 do_expensive_check: false,
95 }
96 }
97}
98
99#[derive(Debug)]
104pub struct PrecomputedWeights<'a, V: CugraphElement = i32, W: CugraphElement = f32> {
105 pub vertices: &'a DeviceArrayView<'a, V>,
107 pub sums: &'a DeviceArrayView<'a, W>,
109}
110
111#[derive(Debug)]
117pub struct InitialGuess<'a, V: CugraphElement = i32, W: CugraphElement = f32> {
118 pub vertices: &'a DeviceArrayView<'a, V>,
120 pub values: &'a DeviceArrayView<'a, W>,
122}
123
124#[derive(Debug)]
126pub struct PersonalizationVector<'a, V: CugraphElement = i32, W: CugraphElement = f32> {
127 pub vertices: &'a DeviceArrayView<'a, V>,
129 pub values: &'a DeviceArrayView<'a, W>,
131}
132
133#[derive(Debug, Clone, Copy)]
134enum PageRankCall {
135 Standard,
136 Personalized,
137}
138
139pub(crate) fn pagerank<V: CugraphElement, W: CugraphElement>(
145 handle: &EnteredContext<'_>,
146 graph: &mut Graph<Transposed>,
147 params: &PageRankParams,
148 precomputed_weights: Option<&PrecomputedWeights<'_, V, W>>,
149 initial_guess: Option<&InitialGuess<'_, V, W>>,
150) -> Result<CentralityResult> {
151 pagerank_impl(
152 handle,
153 graph,
154 params.alpha,
155 params.epsilon,
156 params.max_iterations,
157 params.do_expensive_check,
158 precomputed_weights,
159 initial_guess,
160 None,
161 PageRankCall::Standard,
162 "pagerank",
163 )
164}
165
166pub(crate) fn personalized_pagerank<V: CugraphElement, W: CugraphElement>(
167 handle: &EnteredContext<'_>,
168 graph: &mut Graph<Transposed>,
169 params: &PersonalizedPageRankParams,
170 personalization: &PersonalizationVector<'_, V, W>,
171 precomputed_weights: Option<&PrecomputedWeights<'_, V, W>>,
172 initial_guess: Option<&InitialGuess<'_, V, W>>,
173) -> Result<CentralityResult> {
174 pagerank_impl(
175 handle,
176 graph,
177 params.alpha,
178 params.epsilon,
179 params.max_iterations,
180 params.do_expensive_check,
181 precomputed_weights,
182 initial_guess,
183 Some(personalization),
184 PageRankCall::Personalized,
185 "personalized_pagerank",
186 )
187}
188
189#[expect(clippy::too_many_arguments, reason = "all parameters required to invoke PageRank FFI")]
190fn pagerank_impl<V: CugraphElement, W: CugraphElement>(
191 handle: &EnteredContext<'_>,
192 graph: &mut Graph<Transposed>,
193 alpha: ClosedUnitF64,
194 epsilon: NonNegativeF64,
195 max_iterations: IterationCount,
196 do_expensive_check: bool,
197 precomputed_weights: Option<&PrecomputedWeights<'_, V, W>>,
198 initial_guess: Option<&InitialGuess<'_, V, W>>,
199 personalization: Option<&PersonalizationVector<'_, V, W>>,
200 call: PageRankCall,
201 operation: &'static str,
202) -> Result<CentralityResult> {
203 if let Some(pw) = precomputed_weights {
204 if pw.vertices.len() != pw.sums.len() {
205 return Err(Error::new(ErrorKind::InvalidInput, operation)
206 .with_message("precomputed weights: vertices and sums must have the same length")
207 .with_context("vertices_len", pw.vertices.len().to_string())
208 .with_context("sums_len", pw.sums.len().to_string()));
209 }
210 validate_vertex_dtype::<V>(graph, operation, "precomputed weights vertices")?;
211 validate_weight_dtype::<W>(operation, "precomputed weights sums")?;
212 }
213
214 if let Some(ig) = initial_guess {
215 if ig.vertices.len() != ig.values.len() {
216 return Err(Error::new(ErrorKind::InvalidInput, operation)
217 .with_message("initial guess: vertices and values must have the same length")
218 .with_context("vertices_len", ig.vertices.len().to_string())
219 .with_context("values_len", ig.values.len().to_string()));
220 }
221 validate_vertex_dtype::<V>(graph, operation, "initial guess vertices")?;
222 validate_weight_dtype::<W>(operation, "initial guess values")?;
223 }
224
225 if let Some(personalization) = personalization {
226 if personalization.vertices.len() != personalization.values.len() {
227 return Err(Error::new(ErrorKind::InvalidInput, operation)
228 .with_message("personalization: vertices and values must have the same length")
229 .with_context("vertices_len", personalization.vertices.len().to_string())
230 .with_context("values_len", personalization.values.len().to_string()));
231 }
232 validate_vertex_dtype::<V>(graph, operation, "personalization vertices")?;
233 validate_weight_dtype::<W>(operation, "personalization values")?;
234 }
235
236 let (pw_verts_ptr, pw_sums_ptr) = match precomputed_weights {
237 Some(pw) => (pw.vertices.as_ptr() as *const _, pw.sums.as_ptr() as *const _),
238 None => (ptr::null(), ptr::null()),
239 };
240
241 let (ig_verts_ptr, ig_vals_ptr) = match initial_guess {
242 Some(ig) => (ig.vertices.as_ptr() as *const _, ig.values.as_ptr() as *const _),
243 None => (ptr::null(), ptr::null()),
244 };
245
246 let (personalization_verts_ptr, personalization_vals_ptr) = match personalization {
247 Some(p) => (p.vertices.as_ptr() as *const _, p.values.as_ptr() as *const _),
248 None => (ptr::null(), ptr::null()),
249 };
250 let do_expensive_check =
251 do_expensive_check || precomputed_weights.is_some() || initial_guess.is_some() || personalization.is_some();
252
253 let mut result: *mut cugraph_centrality_result_t = ptr::null_mut();
254 let mut error: *mut cugraph_error_t = ptr::null_mut();
255 let graph_ptr = graph.as_ptr(handle, operation)?;
256
257 unsafe {
262 let code = match call {
263 PageRankCall::Standard => cugraph_pagerank_allow_nonconvergence(
264 handle.handle_ptr(),
265 graph_ptr,
266 pw_verts_ptr,
267 pw_sums_ptr,
268 ig_verts_ptr,
269 ig_vals_ptr,
270 alpha.get(),
271 epsilon.get(),
272 usize::from(max_iterations),
273 to_bool_t(do_expensive_check),
274 &mut result,
275 &mut error,
276 ),
277 PageRankCall::Personalized => cugraph_personalized_pagerank_allow_nonconvergence(
278 handle.handle_ptr(),
279 graph_ptr,
280 pw_verts_ptr,
281 pw_sums_ptr,
282 ig_verts_ptr,
283 ig_vals_ptr,
284 personalization_verts_ptr,
285 personalization_vals_ptr,
286 alpha.get(),
287 epsilon.get(),
288 usize::from(max_iterations),
289 to_bool_t(do_expensive_check),
290 &mut result,
291 &mut error,
292 ),
293 };
294 if let Err(err) = check_error(code, error, handle.device(), operation) {
295 if !result.is_null() {
296 drop_device_resource(&mut result, handle.device(), operation, DeviceResourceKind::CentralityResult);
297 }
298 return Err(err);
299 }
300 }
301
302 let result = require_non_null(result, operation, "centrality_result")?;
303
304 unsafe { CentralityResult::from_raw(result, handle.device(), handle.runtime_bound_lease()) }
306}
307
308fn validate_vertex_dtype<V: CugraphElement>(
309 graph: &Graph<Transposed>,
310 operation: &'static str,
311 input_name: &str,
312) -> Result<()> {
313 let dtype = V::data_type();
314 if !dtype.is_supported_index_type() {
315 return Err(Error::new(ErrorKind::UnsupportedType, operation)
316 .with_message(format!("{input_name} require Int32 or Int64 vertex ids, got {dtype}")));
317 }
318 if dtype != graph.vertex_dtype() {
319 return Err(Error::new(ErrorKind::UnsupportedType, operation).with_message(format!(
320 "{input_name} vertex dtype {dtype} does not match graph vertex dtype {}",
321 graph.vertex_dtype()
322 )));
323 }
324 Ok(())
325}
326
327fn validate_weight_dtype<W: CugraphElement>(operation: &'static str, input_name: &str) -> Result<()> {
328 let dtype = W::data_type();
329 if !dtype.is_float() {
330 return Err(Error::new(ErrorKind::UnsupportedType, operation)
331 .with_message(format!("{input_name} require Float32 or Float64 values, got {dtype}")));
332 }
333 Ok(())
334}