diff --git a/src/pathpyG/algorithms/generative_models.py b/src/pathpyG/algorithms/generative_models.py index 30eface3c..33243ea94 100644 --- a/src/pathpyG/algorithms/generative_models.py +++ b/src/pathpyG/algorithms/generative_models.py @@ -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 @@ -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 @@ -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( diff --git a/src/pathpyG/core/graph.py b/src/pathpyG/core/graph.py index 95143808e..e8a438224 100644 --- a/src/pathpyG/core/graph.py +++ b/src/pathpyG/core/graph.py @@ -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: @@ -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) diff --git a/src/pathpyG/statistics/node_similarities.py b/src/pathpyG/statistics/node_similarities.py index 6a20a451a..a589c2d04 100644 --- a/src/pathpyG/statistics/node_similarities.py +++ b/src/pathpyG/statistics/node_similarities.py @@ -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() diff --git a/tests/algorithms/test_generative_models.py b/tests/algorithms/test_generative_models.py index b487fcf89..45e036489 100644 --- a/tests/algorithms/test_generative_models.py +++ b/tests/algorithms/test_generative_models.py @@ -34,10 +34,9 @@ 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 @@ -45,8 +44,7 @@ def test_erdos_renyi_gnm(): # 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 @@ -54,7 +52,6 @@ def test_erdos_renyi_gnm(): # 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 @@ -62,8 +59,7 @@ def test_erdos_renyi_gnm(): # 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 @@ -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(): @@ -179,7 +176,7 @@ 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() @@ -187,7 +184,7 @@ def test_watts_strogatz_simple(): 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 @@ -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} diff --git a/tests/io/test_pandas.py b/tests/io/test_pandas.py index 1d33fd0c2..c233c63c3 100644 --- a/tests/io/test_pandas.py +++ b/tests/io/test_pandas.py @@ -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):