Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions docs/tutorial/paths_higher_order.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -817,7 +817,7 @@
}
],
"source": [
"m = pp.MultiOrderModel.from_PathData(paths, max_order=1)\n",
"m = pp.MultiOrderModel.from_path_data(paths, max_order=1)\n",
"g = m.layers[1]\n",
"print(g.data.edge_index)\n",
"print(g.data.edge_weight)\n",
Expand Down Expand Up @@ -869,7 +869,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -1162,7 +1162,7 @@
}
],
"source": [
"m = pp.MultiOrderModel.from_PathData(paths_2, max_order=1)\n",
"m = pp.MultiOrderModel.from_path_data(paths_2, max_order=1)\n",
"g = m.layers[1]\n",
"print(g.data.edge_index)\n",
"print(g.data.edge_weight)\n",
Expand All @@ -1186,7 +1186,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -1470,7 +1470,7 @@
}
],
"source": [
"m = pp.MultiOrderModel.from_PathData(paths, max_order=2)\n",
"m = pp.MultiOrderModel.from_path_data(paths, max_order=2)\n",
"g = m.layers[2]\n",
"pp.plot(g, node_label=g.nodes, edge_size=5);"
]
Expand Down Expand Up @@ -1519,7 +1519,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -1803,7 +1803,7 @@
}
],
"source": [
"m = pp.MultiOrderModel.from_PathData(paths_2, max_order=2)\n",
"m = pp.MultiOrderModel.from_path_data(paths_2, max_order=2)\n",
"g = m.layers[2]\n",
"pp.plot(g, node_label=g.nodes);"
]
Expand Down Expand Up @@ -2251,7 +2251,7 @@
},
{
"cell_type": "code",
"execution_count": 19,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -2271,7 +2271,7 @@
}
],
"source": [
"m1 = pp.MultiOrderModel.from_PathData(paths, max_order=2)\n",
"m1 = pp.MultiOrderModel.from_path_data(paths, max_order=2)\n",
"print(m1.estimate_order(paths, significance_threshold=0.01))"
]
},
Expand All @@ -2284,7 +2284,7 @@
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -2296,7 +2296,7 @@
}
],
"source": [
"m2 = pp.MultiOrderModel.from_PathData(paths_2, max_order=2)\n",
"m2 = pp.MultiOrderModel.from_path_data(paths_2, max_order=2)\n",
"print(m2.estimate_order(paths_2, significance_threshold=0.01))"
]
},
Expand All @@ -2309,7 +2309,7 @@
},
{
"cell_type": "code",
"execution_count": 21,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -2334,7 +2334,7 @@
"paths_3.append_walk(('b', 'c', 'e'), weight=6.0)\n",
"paths_3.append_walk(('b', 'c', 'd'), weight=2.0)\n",
"\n",
"m3 = pp.MultiOrderModel.from_PathData(paths_3, max_order=2)\n",
"m3 = pp.MultiOrderModel.from_path_data(paths_3, max_order=2)\n",
"print(m3.layers[1].data.edge_weight)\n",
"print(m3.estimate_order(paths_3, significance_threshold=0.01))"
]
Expand Down Expand Up @@ -2372,7 +2372,7 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -2393,7 +2393,7 @@
"paths_3.append_walk(('b', 'c', 'e'), weight=7.0)\n",
"paths_3.append_walk(('b', 'c', 'd'), weight=1.0)\n",
"\n",
"m3 = pp.MultiOrderModel.from_PathData(paths_3, max_order=2)\n",
"m3 = pp.MultiOrderModel.from_path_data(paths_3, max_order=2)\n",
"print(m3.layers[1].data.edge_weight)\n",
"print(m3.estimate_order(paths_3, significance_threshold=0.01))"
]
Expand Down Expand Up @@ -2457,7 +2457,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -2741,7 +2741,7 @@
}
],
"source": [
"m = pp.MultiOrderModel.from_PathData(paths_tube, max_order=1)\n",
"m = pp.MultiOrderModel.from_path_data(paths_tube, max_order=1)\n",
"g = m.layers[1]\n",
"pp.plot(g, node_label=g.mapping.node_ids.tolist());"
]
Expand All @@ -2755,7 +2755,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -2771,7 +2771,7 @@
],
"source": [
"paths_tube.to(device)\n",
"m = pp.MultiOrderModel.from_PathData(paths_tube, max_order=20)\n",
"m = pp.MultiOrderModel.from_path_data(paths_tube, max_order=20)\n",
"print(m.layers[20])"
]
},
Expand Down
4 changes: 0 additions & 4 deletions src/pathpyG/algorithms/centrality.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,6 @@ def path_visitation_probabilities(paths: PathData) -> dict:
Args:
paths: PathData object that contains path data
"""
# if not isinstance(paths, PathData):
# assert False, "`paths` must be an instance of Paths"
# Log.add('Calculating visitation probabilities...', Severity.INFO)

# entries capture the probability that a given node is visited on an arbitrary path
# Note: this is identical to the subpath count of zero-length paths
# (i.e. the relative frequencies of nodes across all pathways)
Expand Down
35 changes: 25 additions & 10 deletions src/pathpyG/algorithms/generative_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@
import torch
from torch_geometric.utils import degree

import logging

from pathpyG.core.graph import Graph
from pathpyG.core.index_map import IndexMap

logger = logging.getLogger("root")


def max_edges(n: int, directed: bool = False, multi_edges: bool = False, self_loops: bool = False) -> int | float:
"""Returns the maximum number of edges that a directed or undirected network with n nodes can
Expand Down Expand Up @@ -82,7 +86,9 @@ def erdos_renyi_gnm(n: int, m: int, mapping: IndexMap | None = None,
Returns:
Graph: graph object
"""
assert m <= max_edges(n, directed=directed, self_loops=self_loops, multi_edges=multi_edges)
if m > max_edges(n, directed=directed, self_loops=self_loops, multi_edges=multi_edges):
logger.error("Given number of edges is larger than theoretical maximum")
raise ValueError("Given number of edges is larger than theoretical maximum")

edges = set()
edges_added: int = 0
Expand Down Expand Up @@ -186,20 +192,26 @@ def erdos_renyi_gnp_randomize(graph: Graph, self_loops: bool = False) -> Graph:


def erdos_renyi_gnp_likelihood(p: float, graph: Graph) -> float:
"""Calculate the likelihood of parameter p for a G(n,p) model and a given graph"""
assert graph.is_directed() is False
"""Calculate the likelihood of parameter p for a G(n,p) model and a given undirected graph"""
if graph.is_directed():
logger.error("erdos_renyi_gnp_likelihood does not support directed graphs")
raise NotImplementedError("erdos_renyi_gnp_likelihood does not support directed graphs")
return p**graph.n * (1 - p) ** (scipy.special.binom(graph.n, 2) - graph.m)


def erdos_renyi_gnp_log_likelihood(p: float, graph: Graph) -> float:
"""Calculate the log-likelihood of parameter p for a G(n,p) model and a given graph"""
assert graph.is_directed() is False
"""Calculate the log-likelihood of parameter p for a G(n,p) model and a given undirected graph"""
if graph.is_directed():
logger.error("erdos_renyi_gnp_log_likelihood does not support directed graphs")
raise NotImplementedError("erdos_renyi_gnp_log_likelihood does not support directed graphs")
return graph.m * _np.log10(p) + (scipy.special.binom(graph.n, 2) - (graph.m)) * _np.log10(1 - p)


def erdos_renyi_gnp_mle(graph: Graph) -> float:
"""Calculate the maximum likelihood estimate of parameter p for a G(n,p) model and a given undirected graph"""
assert graph.is_directed() is False
if graph.is_directed():
logger.error("erdos_renyi_gnp_mle does not support directed graphs")
raise NotImplementedError("erdos_renyi_gnp_mle does not support directed graphs")
return graph.m / scipy.special.binom(graph.n, 2)


Expand Down Expand Up @@ -246,11 +258,12 @@ def watts_strogatz(

if not allow_duplicate_edges:
if n * (n - 1) < edges.shape[1]:
logger.error("number of edges is greater than the number of possible edges in the graph. Set `allow_duplicate_edges=True` to allow this.")
raise ValueError(
"The number of edges is greater than the number of possible edges in the graph. Set `allow_duplicate_edges=True` to allow this."
"number of edges is greater than the number of possible edges in the graph. Set `allow_duplicate_edges=True` to allow this."
)
elif n * (n - 1) * 0.5 < edges.shape[1] and p > 0.3:
warnings.warn(
logger.info(
"Avoding duplicate in graphs with high connectivity and high rewiring probability may be slow. Consider setting `allow_duplicate_edges=True`."
)

Expand Down Expand Up @@ -426,7 +439,8 @@ def molloy_reed(degree_sequence: _np.array | Dict[int, float],

# assume that we are given a graphical degree sequence
if not is_graphic_erdos_gallai(degree_sequence):
raise AttributeError('degree sequence is not graphic')
logger.error("given degree sequence is not graphic")
raise ValueError('gicen degree sequence is not graphic')

# create empty network with n nodes
n = len(degree_sequence)
Expand Down Expand Up @@ -466,9 +480,10 @@ def molloy_reed(degree_sequence: _np.array | Dict[int, float],


def molloy_reed_randomize(g: Graph) -> Optional[Graph]:
"""Generates a random realization of a given network based on the observed degree sequence.
"""Generates a randomized realization of a given undirected network based on the observed degree sequence.
"""
if g.is_directed():
logger.error("molloy_reed_randomize is only implemented for undirected graphs")
raise NotImplementedError('molloy_reed_randomize is only implemented for undirected graphs')
# degrees are listed in order of node indices
degrees = degree(g.data.edge_index[1], num_nodes=g.n, dtype=torch.int).tolist()
Expand Down
31 changes: 10 additions & 21 deletions src/pathpyG/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
from torch_geometric.data import Data
from torch_geometric.utils import scatter, to_undirected

import logging
from pathpyG.core.index_map import IndexMap

logger = logging.getLogger("root")


class Graph:
"""
Expand Down Expand Up @@ -60,8 +63,9 @@ def __init__(self, data: Data, mapping: Optional[IndexMap] = None):
self.mapping = mapping

# set num_nodes property
if "num_nodes" not in data and "edge_index" in data:
if "num_nodes" not in data and "edge_index" in data:
data.num_nodes = data.edge_index.max().item() + 1
logger.debug("Inferred number of nodes from edge_index, n = %s", data.num_nodes)

# turn edge index tensor into EdgeIndex object
if not isinstance(data.edge_index, EdgeIndex):
Expand All @@ -71,7 +75,8 @@ def __init__(self, data: Data, mapping: Optional[IndexMap] = None):
data.edge_index.get_sparse_size(dim=0) != data.num_nodes
or data.edge_index.get_sparse_size(dim=1) != data.num_nodes
):
raise Exception("sparse size of EdgeIndex should match number of nodes!")
logger.error("Sparse size of edge_index does not match number of nodes, n = %s", data.num_nodes)
raise ValueError("sparse size of EdgeIndex must match number of nodes!")

self.data = data

Expand Down Expand Up @@ -128,6 +133,7 @@ def from_edge_index(edge_index: torch.Tensor, mapping: Optional[IndexMap] = None
d = Data(edge_index=edge_index)
else:
if mapping is not None and mapping.num_ids() != num_nodes:
logger.error("Number of node IDs in mapping must match num_nodes")
raise ValueError("Number of node IDs in mapping must match num_nodes")
d = Data(edge_index=edge_index, num_nodes=num_nodes)
return Graph(d, mapping=mapping)
Expand Down Expand Up @@ -456,7 +462,8 @@ def out_degrees(self) -> Dict[str, float]:
"""
return self.degrees(mode="out")

def degrees(self, mode: str = "in", edge_attr: Any = None, return_tensor: bool = False) -> Dict[str, float]:
def degrees(self, mode: str = "in", edge_attr: Any = None, return_tensor: bool = False) -> Union[Dict[str, float],
torch.tensor]:
"""
Return (weighted) degrees of nodes.

Expand Down Expand Up @@ -487,24 +494,6 @@ def degrees(self, mode: str = "in", edge_attr: Any = None, return_tensor: bool =
else:
return {str(self.mapping.to_id(i)): d[i].item() for i in range(self.n)}

# def weighted_outdegrees(self) -> torch.Tensor:
# """
# Compute the weighted outdegrees of each node in the graph.

# Args:
# graph (Graph): pathpy graph object.

# Returns:
# tensor: Weighted outdegrees of nodes.
# """
# edge_weight = getattr(self.data, "edge_weight", None)
# if edge_weight is None:
# edge_weight = torch.ones(self.data.num_edges, device=self.data.edge_index.device)
# weighted_outdegree = scatter(
# edge_weight, self.data.edge_index[0], dim=0, dim_size=self.data.num_nodes, reduce="sum"
# )
# return weighted_outdegree

def transition_probabilities(self, edge_attr: Any = None) -> torch.Tensor:
"""
Compute transition probabilities based on (weighted) outdegrees.
Expand Down
Loading
Loading