1use cudf::{AggSpec, Exec, GroupByAgg, Runtime, TypeId, core::Column, frame::factorization::Codebook};
2
3use super::errors::map_cudf_error_with;
4use crate::{
5 centrality::{CentralityResult, EdgeBetweennessCentralityResult, HitsResult},
6 community::{
7 EcgResult, ExtractEgoResult, KTrussSubgraphResult, LeidenResult, LouvainResult, SpectralClusteringResult,
8 TriangleCountResult,
9 },
10 error::{Error, ErrorKind, Result},
11 labeling::LabelingResult,
12 layout::LayoutResult,
13 similarity::{
14 CosineSimilarityResult, JaccardSimilarityResult, OverlapSimilarityResult, SorensenSimilarityResult, VertexPairs,
15 },
16 structure::{
17 CoreNumberResult, KCoreResult,
18 degrees::{DegreeValues, DegreesResult, InDegreesResult, OutDegreesResult},
19 typed_results::{Distances, IntegerValues, ScoreValues, VertexIds},
20 },
21 traversal::{bfs::BfsResult, sssp::SsspResult},
22 tree::MinimumSpanningTreeResult,
23};
24
25#[cfg(test)]
26mod tests;
27
28fn series_from_column(column: Column, operation: &'static str) -> Result<cudf::Series> {
29 cudf::Series::from_column(column).map_err(|error| {
30 map_cudf_error_with(
31 error,
32 operation,
33 "failed to create cudf Series from cugraph result column",
34 std::iter::empty(),
35 )
36 })
37}
38
39fn series_from_column_result(column: Result<Column>, operation: &'static str) -> Result<cudf::Series> {
40 series_from_column(column?, operation)
41}
42
43pub(super) fn dataframe_from_named_series<I, S>(series: I, operation: &'static str) -> Result<cudf::DataFrame>
44where
45 I: IntoIterator<Item = (S, cudf::Series)>,
46 S: Into<String>,
47{
48 let series = series.into_iter();
49 let (series_count, _) = series.size_hint();
50 cudf::DataFrame::from_series(series).map_err(|error| {
51 map_cudf_error_with(
52 error,
53 operation,
54 "failed to build cudf DataFrame from cugraph result columns",
55 [("series_count", series_count.to_string())],
56 )
57 })
58}
59
60impl VertexIds<'_> {
65 pub fn to_series(&self, runtime: &Runtime) -> Result<cudf::Series> {
71 series_from_column_result(
72 match self {
73 Self::I32(v) => v.raw_view().to_cudf_column(runtime),
74 Self::I64(v) => v.raw_view().to_cudf_column(runtime),
75 },
76 "VertexIds::to_series",
77 )
78 }
79}
80
81impl ScoreValues<'_> {
82 pub fn to_series(&self, runtime: &Runtime) -> Result<cudf::Series> {
88 series_from_column_result(
89 match self {
90 Self::F32(v) => v.raw_view().to_cudf_column(runtime),
91 Self::F64(v) => v.raw_view().to_cudf_column(runtime),
92 },
93 "ScoreValues::to_series",
94 )
95 }
96}
97
98impl Distances<'_> {
99 pub fn to_series(&self, runtime: &Runtime) -> Result<cudf::Series> {
105 series_from_column_result(
106 match self {
107 Self::I32(v) => v.raw_view().to_cudf_column(runtime),
108 Self::I64(v) => v.raw_view().to_cudf_column(runtime),
109 Self::F32(v) => v.raw_view().to_cudf_column(runtime),
110 Self::F64(v) => v.raw_view().to_cudf_column(runtime),
111 },
112 "Distances::to_series",
113 )
114 }
115}
116
117impl DegreeValues<'_> {
118 pub fn to_series(&self, runtime: &Runtime) -> Result<cudf::Series> {
124 series_from_column_result(
125 match self {
126 Self::I32(v) => v.raw_view().to_cudf_column(runtime),
127 Self::I64(v) => v.raw_view().to_cudf_column(runtime),
128 },
129 "DegreeValues::to_series",
130 )
131 }
132}
133
134impl IntegerValues<'_> {
135 pub fn to_series(&self, runtime: &Runtime) -> Result<cudf::Series> {
141 series_from_column_result(
142 match self {
143 Self::I32(v) => v.raw_view().to_cudf_column(runtime),
144 Self::I64(v) => v.raw_view().to_cudf_column(runtime),
145 },
146 "IntegerValues::to_series",
147 )
148 }
149}
150
151pub(crate) fn centrality_result_to_dataframe(result: &CentralityResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
156 let operation = "centrality_result_to_dataframe";
157 dataframe_from_named_series(
158 [("vertex", result.vertex_ids()?.to_series(runtime)?), ("value", result.scores()?.to_series(runtime)?)],
159 operation,
160 )
161}
162
163pub(crate) fn hits_result_to_dataframe(result: &HitsResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
164 let operation = "hits_result_to_dataframe";
165 dataframe_from_named_series(
166 [
167 ("vertex", result.vertex_ids()?.to_series(runtime)?),
168 ("hub", result.hub_scores()?.to_series(runtime)?),
169 ("authority", result.authority_scores()?.to_series(runtime)?),
170 ],
171 operation,
172 )
173}
174
175pub(crate) fn edge_betweenness_centrality_result_to_dataframe(
176 result: &EdgeBetweennessCentralityResult,
177 runtime: &Runtime,
178) -> Result<cudf::DataFrame> {
179 let operation = "edge_betweenness_centrality_result_to_dataframe";
180 let source_series = result.sources()?.to_series(runtime)?;
181 let destination_series = result.destinations()?.to_series(runtime)?;
182 let edge_id_series = result.edge_ids()?.map(|values| values.to_series(runtime)).transpose()?;
183 let score_series = result.scores()?.to_series(runtime)?;
184
185 let mut series = Vec::with_capacity(4);
186 series.push(("source", source_series));
187 series.push(("destination", destination_series));
188 if let Some(edge_id) = edge_id_series {
189 series.push(("edge_id", edge_id));
190 }
191 series.push(("score", score_series));
192
193 dataframe_from_named_series(series, operation)
194}
195
196pub(crate) fn louvain_result_to_dataframe(result: &LouvainResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
197 let operation = "louvain_result_to_dataframe";
198 dataframe_from_named_series(
199 [("vertex", result.vertex_ids()?.to_series(runtime)?), ("partition", result.clusters()?.to_series(runtime)?)],
200 operation,
201 )
202}
203
204pub(crate) fn leiden_result_to_dataframe(result: &LeidenResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
205 let operation = "leiden_result_to_dataframe";
206 dataframe_from_named_series(
207 [("vertex", result.vertex_ids()?.to_series(runtime)?), ("partition", result.clusters()?.to_series(runtime)?)],
208 operation,
209 )
210}
211
212pub(crate) fn ecg_result_to_dataframe(result: &EcgResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
213 let operation = "ecg_result_to_dataframe";
214 dataframe_from_named_series(
215 [("vertex", result.vertex_ids()?.to_series(runtime)?), ("partition", result.clusters()?.to_series(runtime)?)],
216 operation,
217 )
218}
219
220pub(crate) fn spectral_clustering_result_to_dataframe(
221 result: &SpectralClusteringResult,
222 runtime: &Runtime,
223) -> Result<cudf::DataFrame> {
224 let operation = "spectral_clustering_result_to_dataframe";
225 dataframe_from_named_series(
226 [("vertex", result.vertex_ids()?.to_series(runtime)?), ("partition", result.clusters()?.to_series(runtime)?)],
227 operation,
228 )
229}
230
231pub(crate) fn bfs_result_to_dataframe(result: &BfsResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
232 let operation = "bfs_result_to_dataframe";
233 let vertex_series = result.vertex_ids()?.to_series(runtime)?;
234 let distance_series = result.distances()?.to_series(runtime)?;
235
236 match result.predecessors()? {
237 Some(predecessors) => {
238 let predecessor_series = predecessors.to_series(runtime)?;
239 dataframe_from_named_series(
240 [("vertex", vertex_series), ("distance", distance_series), ("predecessor", predecessor_series)],
241 operation,
242 )
243 },
244 None => dataframe_from_named_series([("vertex", vertex_series), ("distance", distance_series)], operation),
245 }
246}
247
248pub(crate) fn sssp_result_to_dataframe(result: &SsspResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
249 let operation = "sssp_result_to_dataframe";
250 let vertex_series = result.vertex_ids()?.to_series(runtime)?;
251 let distance_series = result.distances()?.to_series(runtime)?;
252
253 match result.predecessors()? {
254 Some(predecessors) => {
255 let predecessor_series = predecessors.to_series(runtime)?;
256 dataframe_from_named_series(
257 [("vertex", vertex_series), ("distance", distance_series), ("predecessor", predecessor_series)],
258 operation,
259 )
260 },
261 None => dataframe_from_named_series([("vertex", vertex_series), ("distance", distance_series)], operation),
262 }
263}
264
265pub(crate) fn layout_result_to_dataframe(result: &LayoutResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
266 let operation = "layout_result_to_dataframe";
267 let vertex_series = result.vertex_ids()?.to_series(runtime)?;
268 let x_series = result.x()?.to_series(runtime)?;
269 let y_series = result.y()?.to_series(runtime)?;
270
271 dataframe_from_named_series([("vertex", vertex_series), ("x", x_series), ("y", y_series)], operation)
272}
273
274pub(crate) fn degrees_result_to_dataframe(result: &DegreesResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
275 let operation = "degrees_result_to_dataframe";
276 dataframe_from_named_series(
277 [
278 ("vertex", result.vertex_ids()?.to_series(runtime)?),
279 ("in_degree", result.in_degrees()?.to_series(runtime)?),
280 ("out_degree", result.out_degrees()?.to_series(runtime)?),
281 ],
282 operation,
283 )
284}
285
286pub(crate) fn in_degrees_result_to_dataframe(result: &InDegreesResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
287 let operation = "in_degrees_result_to_dataframe";
288 dataframe_from_named_series(
289 [("vertex", result.vertex_ids()?.to_series(runtime)?), ("in_degree", result.in_degrees()?.to_series(runtime)?)],
290 operation,
291 )
292}
293
294pub(crate) fn out_degrees_result_to_dataframe(result: &OutDegreesResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
295 let operation = "out_degrees_result_to_dataframe";
296 dataframe_from_named_series(
297 [
298 ("vertex", result.vertex_ids()?.to_series(runtime)?),
299 ("out_degree", result.out_degrees()?.to_series(runtime)?),
300 ],
301 operation,
302 )
303}
304
305pub(crate) fn labeling_result_to_dataframe(result: &LabelingResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
306 let operation = "labeling_result_to_dataframe";
307 let frame = dataframe_from_named_series(
308 [("vertex", result.vertex_ids()?.to_series(runtime)?), ("label", result.labels()?.to_series(runtime)?)],
309 operation,
310 )?;
311 canonicalize_component_labels(frame, operation)
312}
313
314fn canonicalize_component_labels(frame: cudf::DataFrame, operation: &'static str) -> Result<cudf::DataFrame> {
322 if frame.num_rows() == 0 {
323 return Ok(frame);
324 }
325 for role in ["vertex", "label"] {
326 let dtype = frame.series(role).and_then(|series| series.dtype()).map_err(|error| {
327 map_cudf_error_with(error, operation, "failed to inspect component integer dtype", std::iter::empty())
328 })?;
329 if !matches!(dtype.id(), TypeId::Int32 | TypeId::Int64) {
330 return Err(Error::new(ErrorKind::UnsupportedType, operation)
331 .with_message(format!("unexpected component {role} dtype: {:?}", dtype.id())));
332 }
333 }
334 let mapping = frame
335 .groupby(["label"])
336 .on(Exec::RETAINED)
337 .agg(&[AggSpec::new("vertex", GroupByAgg::Min).with_alias("canonical")])
338 .map_err(|error| {
339 map_cudf_error_with(error, operation, "failed to compute component minimum vertex ids", std::iter::empty())
340 })?;
341 let mapping_keys = mapping.series("label").map_err(|error| {
342 map_cudf_error_with(error, operation, "failed to read component label mapping", std::iter::empty())
343 })?;
344 let codebook = Codebook::from_keys(mapping_keys).map_err(|error| {
345 map_cudf_error_with(error, operation, "failed to index component labels", std::iter::empty())
346 })?;
347 let label = frame.series("label").map_err(|error| {
348 map_cudf_error_with(error, operation, "failed to read component label column", std::iter::empty())
349 })?;
350 let codes = codebook.encode(&label, Exec::RETAINED).map_err(|error| {
351 map_cudf_error_with(error, operation, "failed to locate component minimum vertex ids", std::iter::empty())
352 })?;
353 let indices = codes.on(Exec::RETAINED).fillna(i32::MAX).map_err(|error| {
356 map_cudf_error_with(error, operation, "failed to preserve null component labels", std::iter::empty())
357 })?;
358 let canonical =
359 mapping.series("canonical").and_then(|values| values.on(Exec::RETAINED).gather(&indices)).map_err(|error| {
360 map_cudf_error_with(error, operation, "failed to gather canonical component labels", std::iter::empty())
361 })?;
362 let vertex = frame.series("vertex").map_err(|error| {
363 map_cudf_error_with(error, operation, "failed to read component vertex column", std::iter::empty())
364 })?;
365 dataframe_from_named_series([("vertex", vertex), ("label", canonical)], operation)
366}
367
368pub(crate) fn core_number_result_to_dataframe(result: &CoreNumberResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
369 let operation = "core_number_result_to_dataframe";
370 dataframe_from_named_series(
371 [
372 ("vertex", result.vertex_ids()?.to_series(runtime)?),
373 ("core_number", result.core_numbers()?.to_series(runtime)?),
374 ],
375 operation,
376 )
377}
378
379pub(crate) fn triangle_count_result_to_dataframe(
380 result: &TriangleCountResult,
381 runtime: &Runtime,
382) -> Result<cudf::DataFrame> {
383 let operation = "triangle_count_result_to_dataframe";
384 dataframe_from_named_series(
385 [
386 ("vertex", result.vertex_ids()?.to_series(runtime)?),
387 ("triangle_count", result.counts()?.to_series(runtime)?),
388 ],
389 operation,
390 )
391}
392
393fn edge_result_to_dataframe(
394 sources: VertexIds<'_>,
395 destinations: VertexIds<'_>,
396 weights: Option<ScoreValues<'_>>,
397 edge_ids: Option<IntegerValues<'_>>,
398 edge_type_ids: Option<IntegerValues<'_>>,
399 runtime: &Runtime,
400 operation: &'static str,
401) -> Result<cudf::DataFrame> {
402 let source_series = sources.to_series(runtime)?;
403 let destination_series = destinations.to_series(runtime)?;
404 let weight_series = weights.map(|values| values.to_series(runtime)).transpose()?;
405 let edge_id_series = edge_ids.map(|values| values.to_series(runtime)).transpose()?;
406 let edge_type_series = edge_type_ids.map(|values| values.to_series(runtime)).transpose()?;
407
408 let mut series = Vec::with_capacity(5);
409 series.push(("source", source_series));
410 series.push(("destination", destination_series));
411 if let Some(weight) = weight_series {
412 series.push(("weight", weight));
413 }
414 if let Some(edge_id) = edge_id_series {
415 series.push(("edge_id", edge_id));
416 }
417 if let Some(edge_type) = edge_type_series {
418 series.push(("edge_type", edge_type));
419 }
420
421 dataframe_from_named_series(series, operation)
422}
423
424pub(crate) fn k_core_result_to_dataframe(result: &KCoreResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
425 edge_result_to_dataframe(
426 result.sources()?,
427 result.destinations()?,
428 result.weights()?,
429 None,
430 None,
431 runtime,
432 "k_core_result_to_dataframe",
433 )
434}
435
436pub(crate) fn minimum_spanning_tree_result_to_dataframe(
437 result: &MinimumSpanningTreeResult,
438 runtime: &Runtime,
439) -> Result<cudf::DataFrame> {
440 edge_result_to_dataframe(
441 result.sources()?,
442 result.destinations()?,
443 result.weights()?,
444 result.edge_ids()?,
445 result.edge_type_ids()?,
446 runtime,
447 "minimum_spanning_tree_result_to_dataframe",
448 )
449}
450
451pub(crate) fn k_truss_subgraph_result_to_dataframe(
452 result: &KTrussSubgraphResult,
453 runtime: &Runtime,
454) -> Result<cudf::DataFrame> {
455 edge_result_to_dataframe(
456 result.sources()?,
457 result.destinations()?,
458 result.weights()?,
459 result.edge_ids()?,
460 result.edge_type_ids()?,
461 runtime,
462 "k_truss_subgraph_result_to_dataframe",
463 )
464}
465
466pub(crate) fn extract_ego_result_to_dataframe(result: &ExtractEgoResult, runtime: &Runtime) -> Result<cudf::DataFrame> {
467 edge_result_to_dataframe(
468 result.sources()?,
469 result.destinations()?,
470 result.weights()?,
471 result.edge_ids()?,
472 result.edge_type_ids()?,
473 runtime,
474 "extract_ego_result_to_dataframe",
475 )
476}
477
478pub(crate) fn vertex_pairs_to_dataframe(result: &VertexPairs, runtime: &Runtime) -> Result<cudf::DataFrame> {
479 let operation = "vertex_pairs_to_dataframe";
480 dataframe_from_named_series(
481 [("first", result.first()?.to_series(runtime)?), ("second", result.second()?.to_series(runtime)?)],
482 operation,
483 )
484}
485
486fn similarity_result_to_dataframe(
487 first: VertexIds<'_>,
488 second: VertexIds<'_>,
489 scores: ScoreValues<'_>,
490 runtime: &Runtime,
491 operation: &'static str,
492) -> Result<cudf::DataFrame> {
493 dataframe_from_named_series(
494 [
495 ("first", first.to_series(runtime)?),
496 ("second", second.to_series(runtime)?),
497 ("similarity", scores.to_series(runtime)?),
498 ],
499 operation,
500 )
501}
502
503pub(crate) fn jaccard_similarity_result_to_dataframe(
504 result: &JaccardSimilarityResult,
505 runtime: &Runtime,
506) -> Result<cudf::DataFrame> {
507 similarity_result_to_dataframe(
508 result.first()?,
509 result.second()?,
510 result.scores()?,
511 runtime,
512 "jaccard_similarity_result_to_dataframe",
513 )
514}
515
516pub(crate) fn sorensen_similarity_result_to_dataframe(
517 result: &SorensenSimilarityResult,
518 runtime: &Runtime,
519) -> Result<cudf::DataFrame> {
520 similarity_result_to_dataframe(
521 result.first()?,
522 result.second()?,
523 result.scores()?,
524 runtime,
525 "sorensen_similarity_result_to_dataframe",
526 )
527}
528
529pub(crate) fn overlap_similarity_result_to_dataframe(
530 result: &OverlapSimilarityResult,
531 runtime: &Runtime,
532) -> Result<cudf::DataFrame> {
533 similarity_result_to_dataframe(
534 result.first()?,
535 result.second()?,
536 result.scores()?,
537 runtime,
538 "overlap_similarity_result_to_dataframe",
539 )
540}
541
542pub(crate) fn cosine_similarity_result_to_dataframe(
543 result: &CosineSimilarityResult,
544 runtime: &Runtime,
545) -> Result<cudf::DataFrame> {
546 similarity_result_to_dataframe(
547 result.first()?,
548 result.second()?,
549 result.scores()?,
550 runtime,
551 "cosine_similarity_result_to_dataframe",
552 )
553}