From 25d45077507136ebdfcd806ede15655e84cd365d Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Tue, 23 Feb 2021 11:56:36 +0100 Subject: [PATCH 1/9] Fixed dependencies. --- setup.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index cf77adcc..3c637e26 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import setup with open('README.md', "r", encoding="utf-8") as fh: @@ -8,16 +8,17 @@ setup( name="NEMtropy", author="Nicolo' Vallarano, Emiliano Marchese, Matteo Bruno", - author_email='nicolo.vallarano@imtlucca.it, emiliano.marchese@imtlucca.it, matteo.bruno@imtlucca.it', + author_email='nicolo.vallarano@imtlucca.it, emiliano.marchese@imtlucca.it,' + ' matteo.bruno@imtlucca.it', packages=["NEMtropy"], package_dir={'': 'src'}, version="2.0.1", description="NEMtropy is a Maximum-Entropy toolbox for networks, it" - " provides the user with a state of the art solver for a range variety" - " of Maximum Entropy Networks models derived from the ERGM family." - " This module allows you to solve the desired model and generate a" - " number of randomized graphs from the original one:" - " the so-called graphs ensemble.", + " provides the user with a state of the art solver for a" + " range variety of Maximum Entropy Networks models derived" + " from the ERGM family. This module allows you to solve the" + " desired model and generate a number of randomized graphs" + " from the original one: the so-called graphs ensemble.", long_description=long_description, long_description_content_type="text/markdown", license="GNU General Public License v3", @@ -26,7 +27,8 @@ keywords=['Network reconstruction', 'Networks Null Models', 'Maximum Entrophy Methods'], classifiers=[ - 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'License :: OSI Approved :: GNU Library or Lesser General' + ' Public License (LGPL)', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', @@ -38,7 +40,7 @@ "scipy>=1.4", "networkx>=2.4", "powerlaw>=1.4" - "tqdm>=4.56.2" + "tqdm>=4.5" ], extras_require={ "dev": [ From bd24779783fbddc541aa2c1162d4358fa86f7220 Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Tue, 23 Feb 2021 11:56:54 +0100 Subject: [PATCH 2/9] Fixed some errors and typos in docstrings. --- src/NEMtropy/models_functions.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/NEMtropy/models_functions.py b/src/NEMtropy/models_functions.py index f6592c77..0aa24aa9 100644 --- a/src/NEMtropy/models_functions.py +++ b/src/NEMtropy/models_functions.py @@ -1991,10 +1991,10 @@ def linsearch_fun_DCM(xx, args): Alpha determines how much to move on the descending direction found by the algorithm. - :param X: Tuple of arguments to find alpha: + :param xx: Tuple of arguments to find alpha: solution, solution step, tuning parameter beta, initial alpha, function f - :type X: (numpy.ndarray, numpy.ndarray, float, float, func) + :type xx: (numpy.ndarray, numpy.ndarray, float, float, func) :param args: Tuple, step function and arguments. :type args: (func, tuple) :return: Working alpha. @@ -2073,6 +2073,7 @@ def linsearch_fun_DCM_fixed(xx): # DECM functions # -------------- + @jit(nopython=True) def iterative_decm(x, args): """Returns the next iterative step for the DECM Model. @@ -2567,10 +2568,10 @@ def linsearch_fun_DECM(xx, args): Alpha determines how much to move on the descending direction found by the algorithm. - :param X: Tuple of arguments to find alpha: + :param xx: Tuple of arguments to find alpha: solution, solution step, tuning parameter beta, initial alpha, function f - :type X: (numpy.ndarray, numpy.ndarray, float, float, func) + :type xx: (numpy.ndarray, numpy.ndarray, float, float, func) :param args: Tuple, step function and arguments. :type args: (func, tuple) :return: Working alpha. @@ -2627,10 +2628,10 @@ def linsearch_fun_DECM_fixed(xx): Alpha determines how much to move on the descending direction found by the algorithm. - :param X: Tuple of arguments to find alpha: + :param xx: Tuple of arguments to find alpha: solution, solution step, tuning parameter beta, initial alpha, step. - :type X: (numpy.ndarray, numpy.ndarray, float, float, int) + :type xx: (numpy.ndarray, numpy.ndarray, float, float, int) :return: Working alpha. :rtype: float """ @@ -3147,8 +3148,8 @@ def expected_in_degree_dcm_exp(theta): """Expected in-degrees after the DBCM. It is based on DBCM exponential version. - :param sol: DBCM solution. - :type sol: numpy.ndarray + :param theta: DBCM solution. + :type theta: numpy.ndarray :return: In-degrees DBCM expectation. :rtype: numpy.ndarray """ @@ -3381,8 +3382,8 @@ def loglikelihood_decm_exp(x, args): """Returns DECM [*]_ loglikelihood function evaluated in theta. It is based on the exponential version of the DECM. - :param theta: Evaluating point *theta*. - :type theta: numpy.ndarray + :param x: Evaluating point *theta*. + :type x: numpy.ndarray :param args: Arguments to define the loglikelihood function. Out and in degrees sequences, and out and in strengths sequences :type args: (numpy.ndarray, numpy.ndarray, numpy.ndarray, @@ -3653,7 +3654,7 @@ def expected_decm_exp(theta): It is based on DBCM exponential version. :param theta: DBCM solution. - :type x: numpy.ndarray + :type theta: numpy.ndarray :return: DBCM expected parameters sequence. :rtype: numpy.ndarray """ From 501f8c7d61cd5f4b0d740f6f537b20f913111df6 Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Tue, 23 Feb 2021 12:11:45 +0100 Subject: [PATCH 3/9] v2.0.1 fixed simple example: import numpy was missing. --- README.md | 1 + requirements.txt | 0 2 files changed, 1 insertion(+) create mode 100644 requirements.txt diff --git a/README.md b/README.md index 4d49bf6d..2d7ab0f0 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ Simple Example As an example we solve the UBCM for zachary karate club network. ``` + import numpy as np import networkx as nx from NEMtropy import UndirectedGraph diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e69de29b From 1b78e26b891c95beb3be46daf386f69f58c5405c Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Wed, 24 Feb 2021 15:24:10 +0100 Subject: [PATCH 4/9] v2.0.1: Fixed bug in build graphs function. Now we use indexing to scroll the edgelist. --- src/NEMtropy/network_functions.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/NEMtropy/network_functions.py b/src/NEMtropy/network_functions.py index 768c9a4c..44d1e106 100644 --- a/src/NEMtropy/network_functions.py +++ b/src/NEMtropy/network_functions.py @@ -52,14 +52,16 @@ def build_graph_fast(edgelist, is_directed): if is_directed: n_nodes = len(set(edgelist[:, 0]) | set(edgelist[:, 1])) adj = np.zeros((n_nodes, n_nodes)) - for edges in edgelist: + for ii in np.arange(edgelist.shape[0]): + edges = edgelist[ii] i = int(edges[0]) j = int(edges[1]) adj[i, j] = 1 else: n_nodes = len(set(edgelist[:, 0]) | set(edgelist[:, 1])) adj = np.zeros((n_nodes, n_nodes)) - for edges in edgelist: + for ii in np.arange(edgelist.shape[0]): + edges = edgelist[ii] i = int(edges[0]) j = int(edges[1]) adj[i, j] = 1 @@ -107,7 +109,8 @@ def build_graph_fast_weighted(edgelist, is_directed): if is_directed: n_nodes = len(set(edgelist[:, 0]) | set(edgelist[:, 1])) adj = np.zeros((n_nodes, n_nodes)) - for edges in edgelist: + for ii in np.arange(edgelist.shape[0]): + edges = edgelist[ii] i = int(edges[0]) j = int(edges[1]) w = edges[2] @@ -115,7 +118,8 @@ def build_graph_fast_weighted(edgelist, is_directed): else: n_nodes = len(set(edgelist[:, 0]) | set(edgelist[:, 1])) adj = np.zeros((n_nodes, n_nodes)) - for edges in edgelist: + for ii in np.arange(edgelist.shape[0]): + edges = edgelist[ii] i = int(edges[0]) j = int(edges[1]) w = edges[2] From 0cee8c8dbda2fcb49c0218da3c417474a4690ac5 Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Wed, 24 Feb 2021 15:24:46 +0100 Subject: [PATCH 5/9] v2.0.1: Updated requirements: added powerlaw module. --- src/NEMtropy.egg-info/requires.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NEMtropy.egg-info/requires.txt b/src/NEMtropy.egg-info/requires.txt index 0b9ba4db..5fb26c90 100644 --- a/src/NEMtropy.egg-info/requires.txt +++ b/src/NEMtropy.egg-info/requires.txt @@ -1,6 +1,7 @@ numpy>=1.17 scipy>=1.4 networkx>=2.4 +powerlaw>=1.4 [dev] pytest>=6.0.1 From 0265e33a250f628d22f8b8975ed075fb1408001b Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Fri, 26 Feb 2021 13:04:40 +0100 Subject: [PATCH 6/9] v2.0.1: Edgelist bug fixed: input edgeslist with string labels is now working. --- src/NEMtropy/network_functions.py | 51 +++++++++++++++++-------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/NEMtropy/network_functions.py b/src/NEMtropy/network_functions.py index 44d1e106..3d01076b 100644 --- a/src/NEMtropy/network_functions.py +++ b/src/NEMtropy/network_functions.py @@ -2,6 +2,7 @@ import scipy.sparse import scipy from numba import jit +import numbers from scipy.sparse import csr_matrix @@ -255,31 +256,34 @@ def strength(a): def edgelist_from_edgelist_undirected(edgelist): - """Creates a new edgelist with the indexes of the nodes instead of the names. Returns also a dictionary that keep track of the nodes and, depending on the type of graph, degree and strengths sequences. + """Creates a new edgelist with the indexes of the nodes instead of the + names. Returns also a dictionary that keep track of the nodes and, + depending on the type of graph, degree and strengths sequences. :param edgelist: edgelist. :type edgelist: numpy.ndarray or list - :return: edgelist, degrees sequence, strengths sequence and new labels to old labels dictionary. + :return: edgelist, degrees sequence, strengths sequence and + new labels to old labels dictionary. :rtype: (numpy.ndarray, numpy.ndarray, numpy.ndarray, dict) """ edgelist = [tuple(item) for item in edgelist] if len(edgelist[0]) == 2: - nodetype = type(edgelist[0][0]) + # nodetype = type(edgelist[0][0]) edgelist = np.array( edgelist, - dtype=np.dtype([("source", nodetype), ("target", nodetype)]), + dtype=np.dtype([("source", object), ("target", object)]), ) else: - nodetype = type(edgelist[0][0]) - weigthtype = type(edgelist[0][2]) + # nodetype = type(edgelist[0][0]) + # weigthtype = type(edgelist[0][2]) # Vorrei mettere una condizione sul weighttype che deve essere numerico edgelist = np.array( edgelist, dtype=np.dtype( [ - ("source", nodetype), - ("target", nodetype), - ("weigth", weigthtype), + ("source", object), + ("target", object), + ("weigth", object), ] ), ) @@ -306,7 +310,7 @@ def edgelist_from_edgelist_undirected(edgelist): edgelist_new = np.array( edgelist_new, dtype=np.dtype( - [("source", int), ("target", int), ("weigth", weigthtype)] + [("source", int), ("target", int), ("weight", np.float64)] ), ) if len(edgelist[0]) == 3: @@ -314,10 +318,10 @@ def edgelist_from_edgelist_undirected(edgelist): (edgelist_new["source"], edgelist_new["target"]) ) aux_weights = np.concatenate( - (edgelist_new["weigth"], edgelist_new["weigth"]) + (edgelist_new["weight"], edgelist_new["weight"]) ) strength_seq = np.array( - [aux_weights[aux_edgelist == i].sum() for i in unique_nodes] + [aux_weights[aux_edgelist == i].sum() for i in nodes_dict] ) return edgelist_new, degree_seq, strength_seq, nodes_dict return edgelist_new, degree_seq, nodes_dict @@ -338,28 +342,29 @@ def edgelist_from_edgelist_directed(edgelist): # TODO: inserire esempio edgelist pesata edgelist binaria # nel docstring # edgelist = list(zip(*edgelist)) + edgelist = [tuple(item) for item in edgelist] if len(edgelist[0]) == 2: - nodetype = type(edgelist[0][0]) + # nodetype = type(edgelist[0][0]) edgelist = np.array( edgelist, dtype=np.dtype( [ - ("source", nodetype), - ("target", nodetype) + ("source", object), + ("target", object) ] ), ) else: - nodetype = type(edgelist[0][0]) - weigthtype = type(edgelist[0][2]) + # nodetype = type(edgelist[0][0]) + # weigthtype = type(edgelist[0][2]) # Vorrei mettere una condizione sul weighttype che deve essere numerico edgelist = np.array( edgelist, dtype=np.dtype( [ - ("source", nodetype), - ("target", nodetype), - ("weigth", weigthtype), + ("source", object), + ("target", object), + ("weigth", object), ] ), ) @@ -388,7 +393,7 @@ def edgelist_from_edgelist_directed(edgelist): edgelist_new = np.array( edgelist_new, dtype=np.dtype( - [("source", int), ("target", int), ("weigth", weigthtype)] + [("source", int), ("target", int), ("weigth", np.float64)] ), ) out_indices, out_counts = np.unique( @@ -400,8 +405,8 @@ def edgelist_from_edgelist_directed(edgelist): out_degree[out_indices] = out_counts in_degree[in_indices] = in_counts if len(edgelist[0]) == 3: - out_strength = np.zeros_like(unique_nodes, dtype=weigthtype) - in_strength = np.zeros_like(unique_nodes, dtype=weigthtype) + out_strength = np.zeros_like(unique_nodes, dtype=np.float64) + in_strength = np.zeros_like(unique_nodes, dtype=np.float64) out_counts_strength = np.array( [ edgelist_new[edgelist_new["source"] == i]["weigth"].sum() From 81024bb22c382a4db85c6705e19338f6c069724a Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Fri, 26 Feb 2021 13:53:50 +0100 Subject: [PATCH 7/9] v2.0.1: Updated requirements. --- src/NEMtropy.egg-info/requires.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NEMtropy.egg-info/requires.txt b/src/NEMtropy.egg-info/requires.txt index 5fb26c90..d957ac9a 100644 --- a/src/NEMtropy.egg-info/requires.txt +++ b/src/NEMtropy.egg-info/requires.txt @@ -1,7 +1,7 @@ numpy>=1.17 scipy>=1.4 networkx>=2.4 -powerlaw>=1.4 +powerlaw>=1.4tqdm>=4.5 [dev] pytest>=6.0.1 From bd0f6c46d23cd01820930efc2a8b600d09c00c53 Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Fri, 26 Feb 2021 13:54:19 +0100 Subject: [PATCH 8/9] v2.0.1: Updated tests: added test for edgelist with string and weights. --- tests/test_init.py | 68 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 84171b71..236045f5 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -11,28 +11,80 @@ def setUp(self): pass def test_adjacency_init(self): - A = np.array([[0, 1, 1], [1, 0, 1], [0, 1, 0]]) + a = np.array([[0, 1, 1], [1, 0, 1], [0, 1, 0]]) k_out = np.array([2, 2, 1]) k_in = np.array([1, 2, 2]) g0 = sample.DirectedGraph() - g0._initialize_graph(A) + g0._initialize_graph(a) # debug # test result - self.assertTrue(k_out.all() == g0.dseq_out.all()) - self.assertTrue(k_in.all() == g0.dseq_in.all()) + self.assertTrue((k_out == g0.dseq_out).all()) + self.assertTrue((k_in == g0.dseq_in).all()) def test_edgelist_init(self): - E = np.array([(0, 1), (0, 2), (1, 2), (1, 0), (2, 1)]) + e = np.array([(0, 1), (0, 2), (1, 2), (1, 0), (2, 1)]) k_out = np.array([2, 2, 1]) k_in = np.array([1, 2, 2]) g0 = sample.DirectedGraph() - g0._initialize_graph(E) + g0._initialize_graph(edgelist=e) # debug # test result - self.assertTrue(k_out.all() == g0.dseq_out.all()) - self.assertTrue(k_in.all() == g0.dseq_in.all()) + + self.assertTrue((k_out == g0.dseq_out).all()) + self.assertTrue((k_in == g0.dseq_in).all()) + + def test_edgelist_init_string_undirected(self): + e = np.array([("1", "a"), ("2", "b"), ("2", "a")]) + k = np.array([1, 2, 2, 1]) + + g0 = sample.UndirectedGraph() + g0._initialize_graph(edgelist=e) + # debug + # test result + self.assertTrue(k.all() == g0.dseq.all()) + + def test_edgelist_init_string_undirected_weighted(self): + e = np.array([("1", "a", 3), ("2", "b", 4), ("2", "a", 3)]) + k = np.array([1, 2, 2, 1]) + s = np.array([3., 7., 6., 4.]) + + g0 = sample.UndirectedGraph() + g0._initialize_graph(edgelist=e) + # debug + # test result + self.assertTrue((k == g0.dseq).all()) + self.assertTrue((s == g0.strength_sequence).all()) + + def test_edgelist_init_string_directed(self): + e = np.array([("1", "a"), ("2", "b"), ("2", "a")]) + k_out = np.array([1, 2, 0, 0]) + k_in = np.array([0, 0, 2, 1]) + + g0 = sample.DirectedGraph() + g0._initialize_graph(edgelist=e) + # debug + # test result + self.assertTrue((k_out == g0.dseq_out).all()) + self.assertTrue((k_in == g0.dseq_in).all()) + + def test_edgelist_init_string_directed_weighted(self): + e = np.array([("1", "a", 3), ("2", "b", 4), ("2", "a", 3)]) + k_out = np.array([1, 2, 0, 0]) + k_in = np.array([0, 0, 2, 1]) + + s_out = np.array([3., 7., 0., 0.]) + s_in = np.array([0., 0., 6., 4.]) + + g0 = sample.DirectedGraph() + g0._initialize_graph(edgelist=e) + # debug + # test result + self.assertTrue((k_out == g0.dseq_out).all()) + self.assertTrue((k_in == g0.dseq_in).all()) + self.assertTrue((s_out == g0.out_strength).all()) + self.assertTrue((s_in == g0.in_strength).all()) if __name__ == "__main__": From efabfc29c441cd1230531de401d15cc2691edebd Mon Sep 17 00:00:00 2001 From: EmilianoMarchese Date: Fri, 26 Feb 2021 14:01:26 +0100 Subject: [PATCH 9/9] v2.0.1: Changed build_graph_from_edgelist --> build_adjacency_from_edgelist and fixed bug in numba. --- src/NEMtropy/network_functions.py | 74 ++++++++++++++++++------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/NEMtropy/network_functions.py b/src/NEMtropy/network_functions.py index 3d01076b..3026f027 100644 --- a/src/NEMtropy/network_functions.py +++ b/src/NEMtropy/network_functions.py @@ -1,15 +1,15 @@ import numpy as np -import scipy.sparse import scipy +import scipy.sparse from numba import jit -import numbers from scipy.sparse import csr_matrix -def build_graph_from_edgelist(edgelist, - is_directed, - is_sparse=False, - is_weighted=False): +def build_adjacency_from_edgelist( + edgelist, + is_directed, + is_sparse=False, + is_weighted=False): """Generates adjacency matrix given edgelist. :param edgelist: Edgelist. @@ -27,19 +27,19 @@ def build_graph_from_edgelist(edgelist, raise TypeError("edgelist must be an numpy.ndarray.") if is_sparse: if is_weighted: - adjacency = build_graph_sparse_weighted(edgelist, is_directed) + adjacency = build_adjacency_sparse_weighted(edgelist, is_directed) else: - adjacency = build_graph_sparse(edgelist, is_directed) + adjacency = build_adjacency_sparse(edgelist, is_directed) else: if is_weighted: - adjacency = build_graph_fast_weighted(edgelist, is_directed) + adjacency = build_adjacency_fast_weighted(edgelist, is_directed) else: - adjacency = build_graph_fast(edgelist, is_directed) + adjacency = build_adjacency_fast(edgelist, is_directed) return adjacency @jit(nopython=True) -def build_graph_fast(edgelist, is_directed): +def build_adjacency_fast(edgelist, is_directed): """Generates adjacency matrix given edgelist, numpy array format is used. @@ -70,7 +70,7 @@ def build_graph_fast(edgelist, is_directed): return adj -def build_graph_sparse(edgelist, is_directed): +def build_adjacency_sparse(edgelist, is_directed): """Generates adjacency matrix given edgelist, scipy sparse format is used. @@ -90,13 +90,13 @@ def build_graph_sparse(edgelist, is_directed): else: row = np.concatenate([edgelist[:, 0], edgelist[:, 1]]).astype(int) columns = np.concatenate([edgelist[:, 1], edgelist[:, 0]]).astype(int) - data = np.ones(2*n_nodes, dtype=int) + data = np.ones(2 * n_nodes, dtype=int) adj = csr_matrix((data, (row, columns)), shape=(n_nodes, n_nodes)) return adj @jit(nopython=True) -def build_graph_fast_weighted(edgelist, is_directed): +def build_adjacency_fast_weighted(edgelist, is_directed): """Generates weighted adjacency matrix given edgelist, numpy array format is used. @@ -129,7 +129,7 @@ def build_graph_fast_weighted(edgelist, is_directed): return adj -def build_graph_sparse_weighted(edgelist, is_directed): +def build_adjacency_sparse_weighted(edgelist, is_directed): """Generates weighted adjacency matrix given edgelist, scipy sparse format is used. @@ -154,7 +154,6 @@ def build_graph_sparse_weighted(edgelist, is_directed): return adj - def out_degree(a): """Returns matrix *a* out degrees sequence. @@ -431,6 +430,7 @@ def edgelist_from_edgelist_directed(edgelist): ) return edgelist_new, out_degree, in_degree, nodes_dict + # Bipartite networks functions @@ -459,7 +459,9 @@ def sample_bicm(avg_mat): if not isinstance(avg_mat, np.ndarray): avg_mat = np.array(avg_mat) dim1, dim2 = avg_mat.shape - return np.array(avg_mat > np.reshape(np.random.sample(dim1 * dim2), (dim1, dim2)), dtype=int) + return np.array( + avg_mat > np.reshape(np.random.sample(dim1 * dim2), (dim1, dim2)), + dtype=int) def sample_bicm_edgelist(x, y): @@ -510,13 +512,15 @@ def edgelist_from_biadjacency(biadjacency): coords = biadjacency.nonzero() if np.sum(biadjacency.data != 1) > 0: raise ValueError('Only binary matrices') - return np.array(list(zip(coords[0], coords[1])), dtype=np.dtype([('rows', int), ('columns', int)])),\ - np.array(biadjacency.sum(1)).flatten(), np.array(biadjacency.sum(0)).flatten() + return np.array(list(zip(coords[0], coords[1])), + dtype=np.dtype([('rows', int), ('columns', int)])), \ + np.array(biadjacency.sum(1)).flatten(), np.array( + biadjacency.sum(0)).flatten() else: if np.sum(biadjacency[biadjacency != 0] != 1) > 0: raise ValueError('Only binary matrices') return np.array(edgelist_from_biadjacency_fast(biadjacency), - dtype=np.dtype([('rows', int), ('columns', int)])),\ + dtype=np.dtype([('rows', int), ('columns', int)])), \ np.sum(biadjacency, axis=1), np.sum(biadjacency, axis=0) @@ -525,13 +529,15 @@ def biadjacency_from_edgelist(edgelist, fmt='array'): Build the biadjacency matrix of a bipartite network from its edgelist. Returns a matrix of the type specified by ``fmt``, by default a numpy array. """ - edgelist, rows_deg, cols_deg, rows_dict, cols_dict = edgelist_from_edgelist_bipartite(edgelist) + edgelist, rows_deg, cols_deg, rows_dict, cols_dict = edgelist_from_edgelist_bipartite( + edgelist) if fmt == 'array': biadjacency = np.zeros((len(rows_deg), len(cols_deg)), dtype=int) for edge in edgelist: biadjacency[edge[0], edge[1]] = 1 elif fmt == 'sparse': - biadjacency = scipy.sparse.coo_matrix((np.ones(len(edgelist)), (edgelist['rows'], edgelist['columns']))) + biadjacency = scipy.sparse.coo_matrix( + (np.ones(len(edgelist)), (edgelist['rows'], edgelist['columns']))) elif not isinstance(format, str): raise TypeError('format must be a string (either "array" or "sparse")') else: @@ -546,7 +552,8 @@ def edgelist_from_edgelist_bipartite(edgelist): Returns also two dictionaries that keep track of the nodes. """ edgelist = np.array(list(set([tuple(edge) for edge in edgelist]))) - out = np.zeros(np.shape(edgelist)[0], dtype=np.dtype([('source', object), ('target', object)])) + out = np.zeros(np.shape(edgelist)[0], + dtype=np.dtype([('source', object), ('target', object)])) out['source'] = edgelist[:, 0] out['target'] = edgelist[:, 1] edgelist = out @@ -556,8 +563,10 @@ def edgelist_from_edgelist_bipartite(edgelist): cols_dict = dict(enumerate(unique_cols)) inv_rows_dict = {v: k for k, v in rows_dict.items()} inv_cols_dict = {v: k for k, v in cols_dict.items()} - edgelist_new = [(inv_rows_dict[edge[0]], inv_cols_dict[edge[1]]) for edge in edgelist] - edgelist_new = np.array(edgelist_new, dtype=np.dtype([('rows', int), ('columns', int)])) + edgelist_new = [(inv_rows_dict[edge[0]], inv_cols_dict[edge[1]]) for edge + in edgelist] + edgelist_new = np.array(edgelist_new, + dtype=np.dtype([('rows', int), ('columns', int)])) return edgelist_new, rows_degs, cols_degs, rows_dict, cols_dict @@ -570,7 +579,8 @@ def adjacency_list_from_edgelist_bipartite(edgelist, convert_type=True): Returns also two dictionaries that keep track of the nodes and the two degree sequences. """ if convert_type: - edgelist, rows_degs, cols_degs, rows_dict, cols_dict = edgelist_from_edgelist_bipartite(edgelist) + edgelist, rows_degs, cols_degs, rows_dict, cols_dict = edgelist_from_edgelist_bipartite( + edgelist) adj_list = {} inv_adj_list = {} for edge in edgelist: @@ -594,15 +604,18 @@ def adjacency_list_from_adjacency_list_bipartite(old_adj_list): Returns also two dictionaries that keep track of the nodes and the two degree sequences. """ rows_dict = dict(enumerate(np.unique(list(old_adj_list.keys())))) - cols_dict = dict(enumerate(np.unique([el for mylist in old_adj_list.values() for el in mylist]))) + cols_dict = dict(enumerate( + np.unique([el for mylist in old_adj_list.values() for el in mylist]))) inv_rows_dict = {v: k for k, v in rows_dict.items()} inv_cols_dict = {v: k for k, v in cols_dict.items()} adj_list = {} inv_adj_list = {} for k in old_adj_list: - adj_list.setdefault(inv_rows_dict[k], set()).update({inv_cols_dict[val] for val in old_adj_list[k]}) + adj_list.setdefault(inv_rows_dict[k], set()).update( + {inv_cols_dict[val] for val in old_adj_list[k]}) for val in old_adj_list[k]: - inv_adj_list.setdefault(inv_cols_dict[val], set()).add(inv_rows_dict[k]) + inv_adj_list.setdefault(inv_cols_dict[val], set()).add( + inv_rows_dict[k]) rows_degs = np.array([len(adj_list[k]) for k in adj_list]) cols_degs = np.array([len(inv_adj_list[k]) for k in inv_adj_list]) return adj_list, inv_adj_list, rows_degs, cols_degs, rows_dict, cols_dict @@ -627,7 +640,8 @@ def adjacency_list_from_biadjacency(biadjacency): inv_adj_list = {} for edge_i in range(len(coords[0])): adj_list.setdefault(coords[0][edge_i], set()).add(coords[1][edge_i]) - inv_adj_list.setdefault(coords[1][edge_i], set()).add(coords[0][edge_i]) + inv_adj_list.setdefault(coords[1][edge_i], set()).add( + coords[0][edge_i]) rows_degs = np.array([len(adj_list[k]) for k in adj_list]) cols_degs = np.array([len(inv_adj_list[k]) for k in inv_adj_list]) return adj_list, inv_adj_list, rows_degs, cols_degs