#![allow(non_snake_case)]
#[cfg(feature = "storage")]
use crate::python::graph::disk_graph::PyDiskGraph;
use crate::{
algorithms::{
alternating_mask::alternating_mask as alternating_mask_rs,
bipartite::max_weight_matching::{max_weight_matching as mwm, Matching},
centrality::{
betweenness::betweenness_centrality as betweenness_rs,
degree_centrality::degree_centrality as degree_centrality_rs, hits::hits as hits_rs,
pagerank::unweighted_page_rank,
},
community_detection::{
label_propagation::label_propagation as label_propagation_rs,
louvain::louvain as louvain_rs, modularity::ModularityUnDir,
},
components,
cores::k_core::k_core_set,
dynamics::temporal::epidemics::{temporal_SEIR as temporal_SEIR_rs, SeedError},
embeddings::fast_rp::fast_rp as fast_rp_rs,
layout::{
cohesive_fruchterman_reingold::cohesive_fruchterman_reingold as cohesive_fruchterman_reingold_rs,
fruchterman_reingold::fruchterman_reingold_unbounded as fruchterman_reingold_rs,
},
metrics::{
balance::balance as balance_rs,
clustering_coefficient::{
global_clustering_coefficient::global_clustering_coefficient as global_clustering_coefficient_rs,
local_clustering_coefficient::local_clustering_coefficient as local_clustering_coefficient_rs,
local_clustering_coefficient_batch::local_clustering_coefficient_batch as local_clustering_coefficient_batch_rs,
},
degree::{
average_degree as average_degree_rs, max_degree as max_degree_rs,
max_in_degree as max_in_degree_rs, max_out_degree as max_out_degree_rs,
min_degree as min_degree_rs, min_in_degree as min_in_degree_rs,
min_out_degree as min_out_degree_rs,
},
directed_graph_density::directed_graph_density as directed_graph_density_rs,
reciprocity::{
all_local_reciprocity as all_local_reciprocity_rs,
global_reciprocity as global_reciprocity_rs,
},
},
motifs::{
global_temporal_three_node_motifs::{
global_temporal_three_node_motif as global_temporal_three_node_motif_rs,
temporal_three_node_motif_multi as global_temporal_three_node_motif_general_rs,
},
local_temporal_three_node_motifs::temporal_three_node_motif as local_three_node_rs,
local_triangle_count::local_triangle_count as local_triangle_count_rs,
temporal_rich_club_coefficient::temporal_rich_club_coefficient as temporal_rich_club_rs,
},
pathing::{
dijkstra::dijkstra_single_source_shortest_paths as dijkstra_single_source_shortest_paths_rs,
single_source_shortest_path::single_source_shortest_path as single_source_shortest_path_rs,
temporal_reachability::temporally_reachable_nodes as temporal_reachability_rs,
},
projections::temporal_bipartite_projection::temporal_bipartite_projection as temporal_bipartite_rs,
},
db::{
api::{
state::{ops::filter::NO_FILTER, Index, OutputTypedNodeState},
view::internal::DynamicGraph,
},
graph::nodes::Nodes,
},
errors::GraphError,
prelude::Graph,
python::{
filter::filter_expr::PyFilterExpr,
graph::{node::PyNode, views::graph_view::PyGraphView},
utils::PyNodeRef,
},
};
#[cfg(feature = "storage")]
use pometry_storage::algorithms::connected_components::connected_components as connected_components_rs;
use pyo3::{prelude::*, types::PyList};
use rand::{prelude::StdRng, SeedableRng};
use raphtory_api::core::{storage::timeindex::EventTime, Direction};
use raphtory_storage::core_ops::CoreGraphOps;
fn process_node_param(param: &Bound<PyAny>) -> PyResult<Vec<PyNodeRef>> {
if param.is_none() {
return Ok(vec![]);
}
if let Ok(single_node) = param.extract::<PyNodeRef>() {
return Ok(vec![single_node]);
}
if let Ok(py_list) = param.downcast::<PyList>() {
let mut nodes = Vec::new();
for item in py_list.iter() {
let num = item.extract::<PyNodeRef>()?;
nodes.push(num);
}
return Ok(nodes);
}
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"Expected None, a number, or a list of numbers",
))
}
#[pyfunction]
#[pyo3(signature = (graph, v))]
pub fn local_triangle_count(graph: &PyGraphView, v: PyNodeRef) -> Option<usize> {
local_triangle_count_rs(&graph.graph, v)
}
#[pyfunction]
#[pyo3(signature = (graph))]
pub fn weakly_connected_components(
graph: &PyGraphView,
) -> OutputTypedNodeState<'static, DynamicGraph> {
components::weakly_connected_components(&graph.graph).to_output_nodestate()
}
#[pyfunction]
#[pyo3(signature = (graph))]
pub fn strongly_connected_components(
graph: &PyGraphView,
) -> OutputTypedNodeState<'static, DynamicGraph> {
components::strongly_connected_components(&graph.graph).to_output_nodestate()
}
#[cfg(feature = "storage")]
#[pyfunction]
#[pyo3(signature = (graph))]
pub fn connected_components(graph: &PyDiskGraph) -> Vec<usize> {
connected_components_rs(graph.0.as_ref())
}
#[pyfunction]
#[pyo3(signature = (graph, filter = None))]
pub fn in_components(
graph: &PyGraphView,
filter: Option<PyFilterExpr>,
) -> Result<OutputTypedNodeState<'static, DynamicGraph>, GraphError> {
match filter {
Some(f) => components::in_components_filtered(&graph.graph, None, f)
.map(|result| result.to_output_nodestate()),
None => Ok(components::in_components(&graph.graph, None).to_output_nodestate()),
}
}
#[pyfunction]
#[pyo3(signature = (node, filter = None))]
pub fn in_component(
node: &PyNode,
filter: Option<PyFilterExpr>,
) -> Result<OutputTypedNodeState<'static, DynamicGraph>, GraphError> {
match filter {
Some(f) => components::in_component_filtered(node.node.clone(), f)
.map(|result| result.to_output_nodestate()),
None => Ok(components::in_component(node.node.clone()).to_output_nodestate()),
}
}
#[pyfunction]
#[pyo3(signature = (graph, filter = None))]
pub fn out_components(
graph: &PyGraphView,
filter: Option<PyFilterExpr>,
) -> Result<OutputTypedNodeState<'static, DynamicGraph>, GraphError> {
match filter {
Some(f) => components::out_components_filtered(&graph.graph, None, f)
.map(|result| result.to_output_nodestate()),
None => Ok(components::out_components(&graph.graph, None).to_output_nodestate()),
}
}
#[pyfunction]
#[pyo3(signature = (node, filter = None))]
pub fn out_component(
node: &PyNode,
filter: Option<PyFilterExpr>,
) -> Result<OutputTypedNodeState<'static, DynamicGraph>, GraphError> {
match filter {
Some(f) => components::out_component_filtered(node.node.clone(), f)
.map(|result| result.to_output_nodestate()),
None => Ok(components::out_component(node.node.clone()).to_output_nodestate()),
}
}
#[pyfunction]
#[pyo3(signature = (graph, iter_count=20, max_diff=None, use_l2_norm=true, damping_factor=0.85))]
pub fn pagerank(
graph: &PyGraphView,
iter_count: usize,
max_diff: Option<f64>,
use_l2_norm: bool,
damping_factor: Option<f64>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
unweighted_page_rank(
&graph.graph,
Some(iter_count),
None,
max_diff,
use_l2_norm,
damping_factor,
)
.to_output_nodestate()
}
#[pyfunction]
#[pyo3(signature = (graph, max_hops, start_time, seed_nodes, stop_nodes=None))]
pub fn temporally_reachable_nodes(
graph: &PyGraphView,
max_hops: usize,
start_time: i64,
seed_nodes: Vec<PyNodeRef>,
stop_nodes: Option<Vec<PyNodeRef>>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
temporal_reachability_rs(
&graph.graph,
None,
max_hops,
start_time,
seed_nodes,
stop_nodes,
)
.to_output_nodestate()
}
#[pyfunction]
pub fn local_clustering_coefficient(graph: &PyGraphView, v: PyNodeRef) -> Option<f64> {
local_clustering_coefficient_rs(&graph.graph, v)
}
#[pyfunction]
#[pyo3(signature = (graph, v=None))]
pub fn local_clustering_coefficient_batch(
graph: &PyGraphView,
v: Option<&Bound<PyAny>>,
) -> PyResult<OutputTypedNodeState<'static, DynamicGraph>> {
if v.is_some() {
let v = process_node_param(v.unwrap())?;
return Ok(local_clustering_coefficient_batch_rs(&graph.graph, v).to_output_nodestate());
}
Ok(local_clustering_coefficient_batch_rs(&graph.graph, vec![0; 0]).to_output_nodestate())
}
#[pyfunction]
pub fn directed_graph_density(graph: &PyGraphView) -> f64 {
directed_graph_density_rs(&graph.graph)
}
#[pyfunction]
pub fn average_degree(graph: &PyGraphView) -> f64 {
average_degree_rs(&graph.graph)
}
#[pyfunction]
pub fn max_out_degree(graph: &PyGraphView) -> usize {
max_out_degree_rs(&graph.graph)
}
#[pyfunction]
pub fn max_in_degree(graph: &PyGraphView) -> usize {
max_in_degree_rs(&graph.graph)
}
#[pyfunction]
pub fn min_out_degree(graph: &PyGraphView) -> usize {
min_out_degree_rs(&graph.graph)
}
#[pyfunction]
pub fn min_in_degree(graph: &PyGraphView) -> usize {
min_in_degree_rs(&graph.graph)
}
#[pyfunction]
pub fn global_reciprocity(graph: &PyGraphView) -> f64 {
global_reciprocity_rs(&graph.graph)
}
#[pyfunction]
pub fn all_local_reciprocity(graph: &PyGraphView) -> OutputTypedNodeState<'static, DynamicGraph> {
all_local_reciprocity_rs(&graph.graph).to_output_nodestate()
}
#[pyfunction]
pub fn triplet_count(graph: &PyGraphView) -> usize {
crate::algorithms::motifs::triplet_count::triplet_count(&graph.graph, None)
}
#[pyfunction]
pub fn global_clustering_coefficient(graph: &PyGraphView) -> f64 {
global_clustering_coefficient_rs(&graph.graph)
}
#[pyfunction]
#[pyo3(signature = (graph, delta, threads=None))]
pub fn global_temporal_three_node_motif(
graph: &PyGraphView,
delta: i64,
threads: Option<usize>,
) -> [usize; 40] {
global_temporal_three_node_motif_rs(&graph.graph, delta, threads)
}
#[pyfunction]
#[pyo3(signature = (graph, delta, pivot_type))]
pub fn temporal_bipartite_graph_projection(
graph: &PyGraphView,
delta: i64,
pivot_type: String,
) -> Graph {
temporal_bipartite_rs(&graph.graph, delta, pivot_type)
}
#[pyfunction]
#[pyo3(signature = (graph, deltas, threads=None))]
pub fn global_temporal_three_node_motif_multi(
graph: &PyGraphView,
deltas: Vec<i64>,
threads: Option<usize>,
) -> Vec<[usize; 40]> {
global_temporal_three_node_motif_general_rs(&graph.graph, deltas, threads)
}
#[pyfunction]
#[pyo3(signature = (graph, delta, threads=None))]
pub fn local_temporal_three_node_motifs(
graph: &PyGraphView,
delta: i64,
threads: Option<usize>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
local_three_node_rs(&graph.graph, delta, threads).to_output_nodestate()
}
#[pyfunction]
#[pyo3(signature = (graph, iter_count=20, threads=None))]
pub fn hits(
graph: &PyGraphView,
iter_count: usize,
threads: Option<usize>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
hits_rs(&graph.graph, iter_count, threads).to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature = (graph, name="weight".to_string(), direction=Direction::BOTH)]]
pub fn balance(
graph: &PyGraphView,
name: String,
direction: Direction,
) -> Result<OutputTypedNodeState<'static, DynamicGraph>, GraphError> {
balance_rs(&graph.graph, name.clone(), direction).map(|result| result.to_output_nodestate())
}
#[pyfunction]
#[pyo3[signature = (graph)]]
pub fn degree_centrality(graph: &PyGraphView) -> OutputTypedNodeState<'static, DynamicGraph> {
degree_centrality_rs(&graph.graph).to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature = (graph)]]
pub fn max_degree(graph: &PyGraphView) -> usize {
max_degree_rs(&graph.graph)
}
#[pyfunction]
#[pyo3[signature = (graph)]]
pub fn min_degree(graph: &PyGraphView) -> usize {
min_degree_rs(&graph.graph)
}
#[pyfunction]
#[pyo3[signature = (graph, source, cutoff=None)]]
pub fn single_source_shortest_path(
graph: &PyGraphView,
source: PyNodeRef,
cutoff: Option<usize>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
single_source_shortest_path_rs(&graph.graph, source, cutoff).to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature = (graph, source, targets, direction=Direction::BOTH, weight="weight")]]
pub fn dijkstra_single_source_shortest_paths(
graph: &PyGraphView,
source: PyNodeRef,
targets: Vec<PyNodeRef>,
direction: Direction,
weight: Option<&str>,
) -> Result<OutputTypedNodeState<'static, DynamicGraph>, GraphError> {
dijkstra_single_source_shortest_paths_rs(&graph.graph, source, targets, weight, direction)
.map(|result| result.to_output_nodestate())
}
#[pyfunction]
#[pyo3[signature = (graph, k=None, normalized=true)]]
pub fn betweenness_centrality(
graph: &PyGraphView,
k: Option<usize>,
normalized: bool,
) -> OutputTypedNodeState<'static, DynamicGraph> {
betweenness_rs(&graph.graph, k, normalized).to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature = (graph, iter_count=20, seed=None)]]
pub fn label_propagation(
graph: &PyGraphView,
iter_count: usize,
seed: Option<[u8; 32]>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
label_propagation_rs(&graph.graph, iter_count, seed, None).to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature = (graph, k, iter_count, threads=None)]]
pub fn k_core(
graph: &PyGraphView,
k: usize,
iter_count: usize,
threads: Option<usize>,
) -> Nodes<'static, DynamicGraph> {
let v_set = k_core_set(&graph.graph, k, iter_count, threads);
let index = if v_set.len() == graph.graph.unfiltered_num_nodes() {
None
} else {
Some(Index::from_iter(v_set))
};
Nodes::new_filtered(graph.graph.clone(), graph.graph.clone(), NO_FILTER, index)
}
#[pyfunction(name = "temporal_SEIR")]
#[pyo3(signature = (graph, seeds, infection_prob, initial_infection, recovery_rate=None, incubation_rate=None, rng_seed=None))]
pub fn temporal_SEIR(
graph: &PyGraphView,
seeds: crate::python::algorithm::epidemics::PySeed,
infection_prob: f64,
initial_infection: EventTime,
recovery_rate: Option<f64>,
incubation_rate: Option<f64>,
rng_seed: Option<u64>,
) -> Result<OutputTypedNodeState<'static, DynamicGraph>, SeedError> {
let mut rng = match rng_seed {
None => StdRng::from_entropy(),
Some(seed) => StdRng::seed_from_u64(seed),
};
temporal_SEIR_rs(
&graph.graph,
recovery_rate,
incubation_rate,
infection_prob,
initial_infection,
seeds,
&mut rng,
)
.map(|result| result.to_output_nodestate())
}
#[pyfunction]
#[pyo3[signature=(graph, resolution=1.0, weight_prop=None, tol=None)]]
pub fn louvain(
graph: &PyGraphView,
resolution: f64,
weight_prop: Option<&str>,
tol: Option<f64>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
louvain_rs::<ModularityUnDir, _>(&graph.graph, resolution, weight_prop, tol)
.to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature=(graph, iterations=100, scale=1.0, node_start_size=1.0, cooloff_factor=0.95, dt=0.1)]]
pub fn fruchterman_reingold(
graph: &PyGraphView,
iterations: u64,
scale: f32,
node_start_size: f32,
cooloff_factor: f32,
dt: f32,
) -> OutputTypedNodeState<'static, DynamicGraph> {
fruchterman_reingold_rs(
&graph.graph,
iterations,
scale,
node_start_size,
cooloff_factor,
dt,
)
.to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature=(graph, iter_count=100, scale=1.0, node_start_size=1.0, cooloff_factor=0.95, dt=0.1)]]
pub fn cohesive_fruchterman_reingold(
graph: &PyGraphView,
iter_count: u64,
scale: f32,
node_start_size: f32,
cooloff_factor: f32,
dt: f32,
) -> OutputTypedNodeState<'static, DynamicGraph> {
cohesive_fruchterman_reingold_rs(
&graph.graph,
iter_count,
scale,
node_start_size,
cooloff_factor,
dt,
)
.to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature = (graph, views, k, window_size)]]
pub fn temporal_rich_club_coefficient(
graph: &PyGraphView,
views: &Bound<PyAny>,
k: usize,
window_size: usize,
) -> PyResult<f64> {
let py_iterator = views.try_iter()?;
let views = py_iterator
.map(|view| view.and_then(|view| Ok(view.downcast::<PyGraphView>()?.get().graph.clone())))
.collect::<PyResult<Vec<_>>>()?;
Ok(temporal_rich_club_rs(&graph.graph, views, k, window_size))
}
#[pyfunction]
#[pyo3(signature = (graph, weight_prop=None, max_cardinality=true, verify_optimum_flag=false))]
pub fn max_weight_matching(
graph: &PyGraphView,
weight_prop: Option<&str>,
max_cardinality: bool,
verify_optimum_flag: bool,
) -> Matching<DynamicGraph> {
mwm(
&graph.graph,
weight_prop,
max_cardinality,
verify_optimum_flag,
)
}
#[pyfunction]
#[pyo3[signature = (graph, embedding_dim, normalization_strength, iter_weights, seed=None, threads=None)]]
pub fn fast_rp(
graph: &PyGraphView,
embedding_dim: usize,
normalization_strength: f64,
iter_weights: Vec<f64>,
seed: Option<u64>,
threads: Option<usize>,
) -> OutputTypedNodeState<'static, DynamicGraph> {
fast_rp_rs(
&graph.graph,
embedding_dim,
normalization_strength,
iter_weights,
seed,
threads,
)
.to_output_nodestate()
}
#[pyfunction]
#[pyo3[signature = (graph)]]
pub fn alternating_mask(graph: &PyGraphView) -> OutputTypedNodeState<'static, DynamicGraph> {
alternating_mask_rs(&graph.graph).to_output_nodestate()
}