Skip to content

Commit c210fed

Browse files
authored
impl aggs for temporal properties, add tests (#2294)
* impl aggs for temporal properties, add tests * add review suggestions
1 parent 7aa8840 commit c210fed

File tree

17 files changed

+1539
-516
lines changed

17 files changed

+1539
-516
lines changed

python/python/raphtory/__init__.pyi

Lines changed: 475 additions & 174 deletions
Large diffs are not rendered by default.

python/python/raphtory/algorithms/__init__.pyi

Lines changed: 149 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Algorithmic functions that can be run on Raphtory graphs
33
"""
4+
45
from __future__ import annotations
56

67
###############################################################################
@@ -25,8 +26,60 @@ from os import PathLike
2526
import networkx as nx # type: ignore
2627
import pyvis # type: ignore
2728

28-
__all__ = ['dijkstra_single_source_shortest_paths', 'global_reciprocity', 'betweenness_centrality', 'all_local_reciprocity', 'triplet_count', 'local_triangle_count', 'average_degree', 'directed_graph_density', 'degree_centrality', 'max_degree', 'min_degree', 'max_out_degree', 'max_in_degree', 'min_out_degree', 'min_in_degree', 'pagerank', 'single_source_shortest_path', 'global_clustering_coefficient', 'temporally_reachable_nodes', 'temporal_bipartite_graph_projection', 'local_clustering_coefficient', 'local_clustering_coefficient_batch', 'weakly_connected_components', 'strongly_connected_components', 'in_components', 'in_component', 'out_components', 'out_component', 'fast_rp', 'global_temporal_three_node_motif', 'global_temporal_three_node_motif_multi', 'local_temporal_three_node_motifs', 'hits', 'balance', 'label_propagation', 'k_core', 'temporal_SEIR', 'louvain', 'fruchterman_reingold', 'cohesive_fruchterman_reingold', 'max_weight_matching', 'Matching', 'Infected']
29-
def dijkstra_single_source_shortest_paths(graph: GraphView, source: NodeInput, targets: list[NodeInput], direction: Direction = "both", weight: str = 'weight') -> NodeStateWeightedSP:
29+
__all__ = [
30+
"dijkstra_single_source_shortest_paths",
31+
"global_reciprocity",
32+
"betweenness_centrality",
33+
"all_local_reciprocity",
34+
"triplet_count",
35+
"local_triangle_count",
36+
"average_degree",
37+
"directed_graph_density",
38+
"degree_centrality",
39+
"max_degree",
40+
"min_degree",
41+
"max_out_degree",
42+
"max_in_degree",
43+
"min_out_degree",
44+
"min_in_degree",
45+
"pagerank",
46+
"single_source_shortest_path",
47+
"global_clustering_coefficient",
48+
"temporally_reachable_nodes",
49+
"temporal_bipartite_graph_projection",
50+
"local_clustering_coefficient",
51+
"local_clustering_coefficient_batch",
52+
"weakly_connected_components",
53+
"strongly_connected_components",
54+
"in_components",
55+
"in_component",
56+
"out_components",
57+
"out_component",
58+
"fast_rp",
59+
"global_temporal_three_node_motif",
60+
"global_temporal_three_node_motif_multi",
61+
"local_temporal_three_node_motifs",
62+
"hits",
63+
"balance",
64+
"label_propagation",
65+
"k_core",
66+
"temporal_SEIR",
67+
"louvain",
68+
"fruchterman_reingold",
69+
"cohesive_fruchterman_reingold",
70+
"max_weight_matching",
71+
"Matching",
72+
"Infected",
73+
"connected_components",
74+
]
75+
76+
def dijkstra_single_source_shortest_paths(
77+
graph: GraphView,
78+
source: NodeInput,
79+
targets: list[NodeInput],
80+
direction: Direction = "both",
81+
weight: str = "weight",
82+
) -> NodeStateWeightedSP:
3083
"""
3184
Finds the shortest paths from a single source to multiple targets in a graph.
3285
@@ -56,7 +109,9 @@ def global_reciprocity(graph: GraphView) -> float:
56109
float: reciprocity of the graph between 0 and 1.
57110
"""
58111

59-
def betweenness_centrality(graph: GraphView, k: Optional[int] = None, normalized: bool = True) -> NodeStateF64:
112+
def betweenness_centrality(
113+
graph: GraphView, k: Optional[int] = None, normalized: bool = True
114+
) -> NodeStateF64:
60115
"""
61116
Computes the betweenness centrality for nodes in a given graph.
62117
@@ -224,7 +279,13 @@ def min_in_degree(graph: GraphView) -> int:
224279
int: value of the smallest indegree
225280
"""
226281

227-
def pagerank(graph: GraphView, iter_count: int = 20, max_diff: Optional[float] = None, use_l2_norm: bool = True, damping_factor: float = 0.85) -> NodeStateF64:
282+
def pagerank(
283+
graph: GraphView,
284+
iter_count: int = 20,
285+
max_diff: Optional[float] = None,
286+
use_l2_norm: bool = True,
287+
damping_factor: float = 0.85,
288+
) -> NodeStateF64:
228289
"""
229290
Pagerank -- pagerank centrality value of the nodes in a graph
230291
@@ -245,7 +306,9 @@ def pagerank(graph: GraphView, iter_count: int = 20, max_diff: Optional[float] =
245306
NodeStateF64: Mapping of nodes to their pagerank value.
246307
"""
247308

248-
def single_source_shortest_path(graph: GraphView, source: NodeInput, cutoff: Optional[int] = None) -> NodeStateNodes:
309+
def single_source_shortest_path(
310+
graph: GraphView, source: NodeInput, cutoff: Optional[int] = None
311+
) -> NodeStateNodes:
249312
"""
250313
Calculates the single source shortest paths from a given source node.
251314
@@ -276,7 +339,13 @@ def global_clustering_coefficient(graph: GraphView) -> float:
276339
[`Triplet Count`](triplet_count)
277340
"""
278341

279-
def temporally_reachable_nodes(graph: GraphView, max_hops: int, start_time: int, seed_nodes: list[NodeInput], stop_nodes: Optional[list[NodeInput]] = None) -> NodeStateReachability:
342+
def temporally_reachable_nodes(
343+
graph: GraphView,
344+
max_hops: int,
345+
start_time: int,
346+
seed_nodes: list[NodeInput],
347+
stop_nodes: Optional[list[NodeInput]] = None,
348+
) -> NodeStateReachability:
280349
"""
281350
Temporally reachable nodes -- the nodes that are reachable by a time respecting path followed out from a set of seed nodes at a starting time.
282351
@@ -295,7 +364,9 @@ def temporally_reachable_nodes(graph: GraphView, max_hops: int, start_time: int,
295364
NodeStateReachability: Mapping of nodes to their reachability history.
296365
"""
297366

298-
def temporal_bipartite_graph_projection(graph: GraphView, delta: int, pivot_type: str) -> Graph:
367+
def temporal_bipartite_graph_projection(
368+
graph: GraphView, delta: int, pivot_type: str
369+
) -> Graph:
299370
"""
300371
Projects a temporal bipartite graph into an undirected temporal graph over the pivot node type. Let `G` be a bipartite graph with node types `A` and `B`. Given `delta > 0`, the projection graph `G'` pivoting over type `B` nodes,
301372
will make a connection between nodes `n1` and `n2` (of type `A`) at time `(t1 + t2)/2` if they respectively have an edge at time `t1`, `t2` with the same node of type `B` in `G`, and `|t2-t1| < delta`.
@@ -323,9 +394,7 @@ def local_clustering_coefficient(graph: GraphView, v: NodeInput) -> float:
323394
float: the local clustering coefficient of node v in graph.
324395
"""
325396

326-
def local_clustering_coefficient_batch(graph, v):
327-
...
328-
397+
def local_clustering_coefficient_batch(graph, v): ...
329398
def weakly_connected_components(graph: GraphView) -> NodeStateUsize:
330399
"""
331400
Weakly connected components -- partitions the graph into node sets which are mutually reachable by an undirected path
@@ -397,7 +466,14 @@ def out_component(node: Node) -> NodeStateUsize:
397466
NodeStateUsize: A NodeState mapping the nodes in the out-component to their distance from the starting node.
398467
"""
399468

400-
def fast_rp(graph: GraphView, embedding_dim: int, normalization_strength: float, iter_weights: list[float], seed: Optional[int] = None, threads: Optional[int] = None) -> NodeStateListF64:
469+
def fast_rp(
470+
graph: GraphView,
471+
embedding_dim: int,
472+
normalization_strength: float,
473+
iter_weights: list[float],
474+
seed: Optional[int] = None,
475+
threads: Optional[int] = None,
476+
) -> NodeStateListF64:
401477
"""
402478
Computes embedding vectors for each vertex of an undirected/bidirectional graph according to the Fast RP algorithm.
403479
Original Paper: https://doi.org/10.48550/arXiv.1908.11512
@@ -413,7 +489,9 @@ def fast_rp(graph: GraphView, embedding_dim: int, normalization_strength: float,
413489
NodeStateListF64: Mapping from nodes to embedding vectors.
414490
"""
415491

416-
def global_temporal_three_node_motif(graph: GraphView, delta: int, threads: Optional[int] = None) -> list[int]:
492+
def global_temporal_three_node_motif(
493+
graph: GraphView, delta: int, threads: Optional[int] = None
494+
) -> list[int]:
417495
"""
418496
Computes the number of three edge, up-to-three node delta-temporal motifs in the graph, using the algorithm of Paranjape et al, Motifs in Temporal Networks (2017).
419497
We point the reader to this reference for more information on the algorithm and background, but provide a short summary below.
@@ -462,7 +540,9 @@ def global_temporal_three_node_motif(graph: GraphView, delta: int, threads: Opti
462540
463541
"""
464542

465-
def global_temporal_three_node_motif_multi(graph: GraphView, deltas: list[int], threads: Optional[int] = None) -> list[list[int]]:
543+
def global_temporal_three_node_motif_multi(
544+
graph: GraphView, deltas: list[int], threads: Optional[int] = None
545+
) -> list[list[int]]:
466546
"""
467547
Computes the global counts of three-edge up-to-three node temporal motifs for a range of timescales. See `global_temporal_three_node_motif` for an interpretation of each row returned.
468548
@@ -475,7 +555,9 @@ def global_temporal_three_node_motif_multi(graph: GraphView, deltas: list[int],
475555
list[list[int]]: A list of 40d arrays, each array is the motif count for a particular value of delta, returned in the order that the deltas were given as input.
476556
"""
477557

478-
def local_temporal_three_node_motifs(graph: GraphView, delta: int, threads=None) -> NodeStateMotifs:
558+
def local_temporal_three_node_motifs(
559+
graph: GraphView, delta: int, threads=None
560+
) -> NodeStateMotifs:
479561
"""
480562
Computes the number of each type of motif that each node participates in. See global_temporal_three_node_motifs for a summary of the motifs involved.
481563
@@ -491,7 +573,9 @@ def local_temporal_three_node_motifs(graph: GraphView, delta: int, threads=None)
491573
the motif. For two node motifs, both constituent nodes count the motif. For triangles, all three constituent nodes count the motif.
492574
"""
493575

494-
def hits(graph: GraphView, iter_count: int = 20, threads: Optional[int] = None) -> NodeStateHits:
576+
def hits(
577+
graph: GraphView, iter_count: int = 20, threads: Optional[int] = None
578+
) -> NodeStateHits:
495579
"""
496580
HITS (Hubs and Authority) Algorithm:
497581
@@ -510,7 +594,9 @@ def hits(graph: GraphView, iter_count: int = 20, threads: Optional[int] = None)
510594
NodeStateHits: A mapping from nodes their hub and authority scores
511595
"""
512596

513-
def balance(graph: GraphView, name: str = "weight", direction: Direction = "both") -> NodeStateF64:
597+
def balance(
598+
graph: GraphView, name: str = "weight", direction: Direction = "both"
599+
) -> NodeStateF64:
514600
"""
515601
Sums the weights of edges in the graph based on the specified direction.
516602
@@ -529,7 +615,9 @@ def balance(graph: GraphView, name: str = "weight", direction: Direction = "both
529615
530616
"""
531617

532-
def label_propagation(graph: GraphView, seed: Optional[bytes] = None) -> list[set[Node]]:
618+
def label_propagation(
619+
graph: GraphView, seed: Optional[bytes] = None
620+
) -> list[set[Node]]:
533621
"""
534622
Computes components using a label propagation algorithm
535623
@@ -542,7 +630,9 @@ def label_propagation(graph: GraphView, seed: Optional[bytes] = None) -> list[se
542630
543631
"""
544632

545-
def k_core(graph: GraphView, k: int, iter_count: int, threads: Optional[int] = None) -> list[Node]:
633+
def k_core(
634+
graph: GraphView, k: int, iter_count: int, threads: Optional[int] = None
635+
) -> list[Node]:
546636
"""
547637
Determines which nodes are in the k-core for a given value of k
548638
@@ -557,7 +647,15 @@ def k_core(graph: GraphView, k: int, iter_count: int, threads: Optional[int] = N
557647
558648
"""
559649

560-
def temporal_SEIR(graph: GraphView, seeds: int | float | list[NodeInput], infection_prob: float, initial_infection: int | str | datetime, recovery_rate: float | None = None, incubation_rate: float | None = None, rng_seed: int | None = None) -> NodeStateSEIR:
650+
def temporal_SEIR(
651+
graph: GraphView,
652+
seeds: int | float | list[NodeInput],
653+
infection_prob: float,
654+
initial_infection: int | str | datetime,
655+
recovery_rate: float | None = None,
656+
incubation_rate: float | None = None,
657+
rng_seed: int | None = None,
658+
) -> NodeStateSEIR:
561659
"""
562660
Simulate an SEIR dynamic on the network
563661
@@ -587,7 +685,12 @@ def temporal_SEIR(graph: GraphView, seeds: int | float | list[NodeInput], infect
587685
588686
"""
589687

590-
def louvain(graph: GraphView, resolution: float = 1.0, weight_prop: str | None = None, tol: None | float = None) -> NodeStateUsize:
688+
def louvain(
689+
graph: GraphView,
690+
resolution: float = 1.0,
691+
weight_prop: str | None = None,
692+
tol: None | float = None,
693+
) -> NodeStateUsize:
591694
"""
592695
Louvain algorithm for community detection
593696
@@ -601,7 +704,14 @@ def louvain(graph: GraphView, resolution: float = 1.0, weight_prop: str | None =
601704
NodeStateUsize: Mapping of nodes to their community assignment
602705
"""
603706

604-
def fruchterman_reingold(graph: GraphView, iterations: int | None = 100, scale: float | None = 1.0, node_start_size: float | None = 1.0, cooloff_factor: float | None = 0.95, dt: float | None = 0.1) -> NodeLayout:
707+
def fruchterman_reingold(
708+
graph: GraphView,
709+
iterations: int | None = 100,
710+
scale: float | None = 1.0,
711+
node_start_size: float | None = 1.0,
712+
cooloff_factor: float | None = 0.95,
713+
dt: float | None = 0.1,
714+
) -> NodeLayout:
605715
"""
606716
Fruchterman Reingold layout algorithm
607717
@@ -617,7 +727,14 @@ def fruchterman_reingold(graph: GraphView, iterations: int | None = 100, scale:
617727
NodeLayout: A mapping from nodes to their [x, y] positions
618728
"""
619729

620-
def cohesive_fruchterman_reingold(graph: GraphView, iter_count: int = 100, scale: float = 1.0, node_start_size: float = 1.0, cooloff_factor: float = 0.95, dt: float = 0.1) -> NodeLayout:
730+
def cohesive_fruchterman_reingold(
731+
graph: GraphView,
732+
iter_count: int = 100,
733+
scale: float = 1.0,
734+
node_start_size: float = 1.0,
735+
cooloff_factor: float = 0.95,
736+
dt: float = 0.1,
737+
) -> NodeLayout:
621738
"""
622739
Cohesive version of `fruchterman_reingold` that adds virtual edges between isolated nodes
623740
Arguments:
@@ -633,7 +750,12 @@ def cohesive_fruchterman_reingold(graph: GraphView, iter_count: int = 100, scale
633750
634751
"""
635752

636-
def max_weight_matching(graph: GraphView, weight_prop: Optional[str] = None, max_cardinality: bool = True, verify_optimum_flag: bool = False) -> Matching:
753+
def max_weight_matching(
754+
graph: GraphView,
755+
weight_prop: Optional[str] = None,
756+
max_cardinality: bool = True,
757+
verify_optimum_flag: bool = False,
758+
) -> Matching:
637759
"""
638760
Compute a maximum-weighted matching in the general undirected weighted
639761
graph given by "edges". If `max_cardinality` is true, only
@@ -670,7 +792,7 @@ def max_weight_matching(graph: GraphView, weight_prop: Optional[str] = None, max
670792
Matching: The matching
671793
"""
672794

673-
class Matching(object):
795+
class Matching(object):
674796
"""A Matching (i.e., a set of edges that do not share any nodes)"""
675797

676798
def __bool__(self):
@@ -742,8 +864,7 @@ class Matching(object):
742864
743865
"""
744866

745-
class Infected(object):
746-
867+
class Infected(object):
747868
def __repr__(self):
748869
"""Return repr(self)."""
749870

@@ -773,3 +894,5 @@ class Infected(object):
773894
Returns:
774895
int:
775896
"""
897+
898+
def connected_components(graph): ...

0 commit comments

Comments
 (0)