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
23 changes: 8 additions & 15 deletions src/pathpyG/algorithms/generative_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def erdos_renyi_gnm(n: int, m: int, mapping: IndexMap | None = None,

Args:
n: the number of nodes of the graph
m: the number of random edges to be generated
m: the number of random directed or undirected edges to be generated
mapping: optional given mapping of n nodes to node IDs. If this is not given a mapping is created
self_loops: whether or not to allow self-loops (v,v) to be generated
multi_edges: whether or not multiple identical edges are allowed
Expand Down Expand Up @@ -126,12 +126,8 @@ def erdos_renyi_gnm_randomize(graph: Graph, self_loops: bool = False, multi_edge
r = pp.algorithms.generative_models.G_nm_randomize(g)
```
"""
if graph.is_undirected():
m = int(graph.m / 2)
else:
m = graph.m
return erdos_renyi_gnm(
graph.n, m, directed=graph.is_directed(),
graph.n, graph.m, directed=graph.is_directed(),
self_loops=self_loops,
multi_edges=multi_edges,
mapping=graph.mapping
Expand Down Expand Up @@ -183,31 +179,28 @@ def erdos_renyi_gnp_randomize(graph: Graph, self_loops: bool = False) -> Graph:
The number of nodes, expected number of edges, edge directedness and node uids of the
generated graph match the corresponding values of the graph given as parameter.
"""
if graph.is_directed():
m = graph.m
else:
m = int(graph.m / 2)
M = max_edges(graph.n, directed=graph.is_directed(), self_loops=self_loops)
p = m / M
p = graph.m / M
return erdos_renyi_gnp(n=graph.n, p=p, directed=graph.is_directed(),
self_loops=self_loops, mapping=graph.mapping)


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
return p**graph.n * (1 - p) ** (scipy.special.binom(graph.n, 2) - graph.m / 2)
assert graph.is_directed() is False
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"""
return (graph.m / 2) * _np.log10(p) + (scipy.special.binom(graph.n, 2) - (graph.m / 2)) * _np.log10(1 - p)
assert graph.is_directed() is False
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
return (graph.m / 2) / scipy.special.binom(graph.n, 2)
return graph.m / scipy.special.binom(graph.n, 2)


def watts_strogatz(
Expand Down
13 changes: 10 additions & 3 deletions src/pathpyG/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,12 +601,19 @@ def m(self) -> int:
"""
Return number of edges.

Returns the number of edges in the graph. For an undirected graph, the number of directed edges is returned.
Returns the number of edges in the graph. For an undirected graph, the number of
undirected edges (accounting for self-loops) is returned, i.e. in an undirected
graph the directed edges (a,b) and (b,a) will be counted only once.

Returns:
int: number of edges in the graph
"""
return self.data.num_edges # type: ignore
if self.is_directed():
return self.data.num_edges # type: ignore
else:
num_self_loops = (self.data.edge_index[0] == self.data.edge_index[1]).sum().item()
num_edges_wo_self_loops = self.data.edge_index.size(1) - int(num_self_loops)
return int(num_edges_wo_self_loops/2 + num_self_loops) # type: ignore

@property
def order(self) -> int:
Expand Down Expand Up @@ -741,7 +748,7 @@ def __str__(self) -> str:
from pprint import pformat

if self.is_undirected():
s = "Undirected graph with {0} nodes and {1} (directed) edges\n".format(self.n, self.m)
s = "Undirected graph with {0} nodes and {1} edges\n".format(self.n, self.m)
else:
s = "Directed graph with {0} nodes and {1} edges\n".format(self.n, self.m)

Expand Down
5 changes: 1 addition & 4 deletions src/pathpyG/statistics/node_similarities.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,8 @@ def katz_index(graph: Graph, v, w, beta) -> float:
def LeichtHolmeNewman_index(graph: Graph, v, w, alpha) -> float:
A = graph.sparse_adj_matrix()
ev = _sp.sparse.linalg.eigs(A, which="LM", k=2, return_eigenvectors=False)
if graph.is_directed():
m = graph.m
else:
m = graph.m / 2
eigenvalues_sorted = _np.sort(_np.absolute(ev))
m = graph.m
lambda_1 = eigenvalues_sorted[1]
D = _sp.sparse.diags(degree_sequence(graph)).tocsc()
I = _sp.sparse.identity(graph.n).tocsc()
Expand Down
25 changes: 11 additions & 14 deletions tests/algorithms/test_generative_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,36 +34,32 @@ def test_erdos_renyi_gnm():
# test undirected graph w/o multi-edges, w/o self-loops and without IndexMapping
n = 100
m = 200
m_1 = erdos_renyi_gnm(n=n, m=m)
m_1 = erdos_renyi_gnm(n=n, m=m, directed=False, self_loops=False)
assert m_1.n == n
# 400 directed edges for undirected graph
assert m_1.m == 2 * m
assert m_1.m == m
# no multiple edges
assert len(set([(v, w) for v, w in m_1.edges])) == len([(v, w) for v, w in m_1.edges])
assert m_1.is_directed() is False

# test undirected graph w/o multi-edges, w/o self-loops and with custom IDs
m_2 = erdos_renyi_gnm(n=n, m=m, mapping=IndexMap([str(i) for i in range(n)]))
assert m_2.n == n
# 400 directed edges for undirected graph
assert m_2.m == 2 * m
assert m_2.m == m
# no multiple edges
assert len(set([(v, w) for v, w in m_2.edges])) == len([(v, w) for v, w in m_2.edges])
assert m_2.is_directed() is False

# test directed graph w/o multi-edges, w/o self-loops
m_3 = erdos_renyi_gnm(n=n, m=m, directed=True)
assert m_3.n == n
# 200 directed edges
assert m_3.m == m
assert len(set([(v, w) for v, w in m_3.edges])) == len([(v, w) for v, w in m_3.edges])
assert m_3.is_directed() is True

# test undirected graph w/o multi-edges and with self-loops
m_4 = erdos_renyi_gnm(n=n, m=m, self_loops=True)
assert m_4.n == n
# since self-loops only exist in one direction we have 2 * m - n <= M <= 2 * m
assert m_4.m >= 2 * m - n and m_4.m <= 2 * m
assert m_4.m == m
assert len(set([(v, w) for v, w in m_4.edges])) == len([(v, w) for v, w in m_4.edges])
assert m_4.is_directed() is False

Expand Down Expand Up @@ -123,11 +119,12 @@ def test_erdos_renyi_gnm_randomize():
n = 100
m = 200

g = erdos_renyi_gnp(n, m)
g_r = erdos_renyi_gnm_randomize(g)
g = erdos_renyi_gnm(n, m, directed=False, self_loops=False)
g_r = erdos_renyi_gnm_randomize(g, self_loops=False)
assert g_r.n == g.n
assert g_r.is_directed() == g.is_directed()
assert g_r.mapping == g.mapping
assert g_r.m == g_r.m
assert g_r.m == g.m


def test_erdos_renyi_gnp_randomize():
Expand Down Expand Up @@ -179,15 +176,15 @@ def test_generate_degree_sequence():

def test_watts_strogatz_simple():
g = watts_strogatz(5, 1, 0.0)
assert g.m == 10
assert g.m == 5
assert (
to_numpy(g.data.edge_index) == np.array([[0, 0, 1, 1, 2, 2, 3, 3, 4, 4], [1, 4, 0, 2, 1, 3, 2, 4, 0, 3]])
).all()

torch.manual_seed(1)
g = watts_strogatz(5, 1, 0.5, allow_duplicate_edges=False, allow_self_loops=False)
print(g.data.edge_index)
assert g.m == 10
assert g.m == 5
assert g.n == 5
assert g.has_self_loops() is False
assert g.is_directed() is False
Expand Down Expand Up @@ -256,7 +253,7 @@ def test_stochastic_block_model():
g = stochastic_block_model(M, z, IndexMap(list('abcdefghi')))

assert g.n == 9
assert g.m == 18
assert g.m == 9
assert g.is_undirected()
_, labels = connected_components(g)
assert Counter(labels) == {0: 3, 1: 3, 2: 3}
2 changes: 1 addition & 1 deletion tests/io/test_pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def test_df_to_graph():
)
g = df_to_graph(df_graph_with_string_attr, is_undirected=True)
assert g.n == 3
assert g.m == 6
assert g.m == 3


def test_add_node_attributes_by_name(simple_graph):
Expand Down
Loading