matrix.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. """
  2. ====================
  3. Biadjacency matrices
  4. ====================
  5. """
  6. import itertools
  7. import networkx as nx
  8. from networkx.convert_matrix import _generate_weighted_edges
  9. __all__ = ["biadjacency_matrix", "from_biadjacency_matrix"]
  10. @nx._dispatchable(edge_attrs="weight")
  11. def biadjacency_matrix(
  12. G, row_order, column_order=None, dtype=None, weight="weight", format="csr"
  13. ):
  14. r"""Returns the biadjacency matrix of the bipartite graph G.
  15. Let `G = (U, V, E)` be a bipartite graph with node sets
  16. `U = u_{1},...,u_{r}` and `V = v_{1},...,v_{s}`. The biadjacency
  17. matrix [1]_ is the `r` x `s` matrix `B` in which `b_{i,j} = 1`
  18. if, and only if, `(u_i, v_j) \in E`. If the parameter `weight` is
  19. not `None` and matches the name of an edge attribute, its value is
  20. used instead of 1.
  21. Parameters
  22. ----------
  23. G : graph
  24. A NetworkX graph
  25. row_order : list of nodes
  26. The rows of the matrix are ordered according to the list of nodes.
  27. column_order : list, optional
  28. The columns of the matrix are ordered according to the list of nodes.
  29. If column_order is None, then the ordering of columns is arbitrary.
  30. dtype : NumPy data-type, optional
  31. A valid NumPy dtype used to initialize the array. If None, then the
  32. NumPy default is used.
  33. weight : string or None, optional (default='weight')
  34. The edge data key used to provide each value in the matrix.
  35. If None, then each edge has weight 1.
  36. format : str in {'dense', 'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'}
  37. The type of the matrix to be returned (default 'csr'). For
  38. some algorithms different implementations of sparse matrices
  39. can perform better. See [2]_ for details.
  40. Returns
  41. -------
  42. M : SciPy sparse array
  43. Biadjacency matrix representation of the bipartite graph G.
  44. Notes
  45. -----
  46. No attempt is made to check that the input graph is bipartite.
  47. For directed bipartite graphs only successors are considered as neighbors.
  48. To obtain an adjacency matrix with ones (or weight values) for both
  49. predecessors and successors you have to generate two biadjacency matrices
  50. where the rows of one of them are the columns of the other, and then add
  51. one to the transpose of the other.
  52. See Also
  53. --------
  54. adjacency_matrix
  55. from_biadjacency_matrix
  56. References
  57. ----------
  58. .. [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
  59. .. [2] Scipy Dev. References, "Sparse Matrices",
  60. https://docs.scipy.org/doc/scipy/reference/sparse.html
  61. """
  62. import scipy as sp
  63. nlen = len(row_order)
  64. if nlen == 0:
  65. raise nx.NetworkXError("row_order is empty list")
  66. if len(row_order) != len(set(row_order)):
  67. msg = "Ambiguous ordering: `row_order` contained duplicates."
  68. raise nx.NetworkXError(msg)
  69. if column_order is None:
  70. column_order = list(set(G) - set(row_order))
  71. mlen = len(column_order)
  72. if len(column_order) != len(set(column_order)):
  73. msg = "Ambiguous ordering: `column_order` contained duplicates."
  74. raise nx.NetworkXError(msg)
  75. row_index = dict(zip(row_order, itertools.count()))
  76. col_index = dict(zip(column_order, itertools.count()))
  77. if G.number_of_edges() == 0:
  78. row, col, data = [], [], []
  79. else:
  80. row, col, data = zip(
  81. *(
  82. (row_index[u], col_index[v], d.get(weight, 1))
  83. for u, v, d in G.edges(row_order, data=True)
  84. if u in row_index and v in col_index
  85. )
  86. )
  87. A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, mlen), dtype=dtype)
  88. try:
  89. return A.asformat(format)
  90. except ValueError as err:
  91. raise nx.NetworkXError(f"Unknown sparse array format: {format}") from err
  92. @nx._dispatchable(graphs=None, returns_graph=True)
  93. def from_biadjacency_matrix(
  94. A,
  95. create_using=None,
  96. edge_attribute="weight",
  97. *,
  98. row_order=None,
  99. column_order=None,
  100. ):
  101. r"""Creates a new bipartite graph from a biadjacency matrix given as a
  102. SciPy sparse array.
  103. Parameters
  104. ----------
  105. A : scipy sparse array
  106. A biadjacency matrix representation of a graph
  107. create_using : NetworkX graph
  108. Use specified graph for result. The default is Graph()
  109. edge_attribute : string
  110. Name of edge attribute to store matrix numeric value. The data will
  111. have the same type as the matrix entry (int, float, (real,imag)).
  112. row_order : list, optional (default: range(number of rows in `A`))
  113. A list of the nodes represented by the rows of the matrix `A`. Will
  114. be represented in the graph as nodes with the `bipartite` attribute set
  115. to 0. Must be the same length as the number of rows in `A`.
  116. column_order : list, optional (default: range(number of columns in `A`))
  117. A list of the nodes represented by the columns of the matrix `A`. Will
  118. be represented in the graph as nodes with the `bipartite` attribute set
  119. to 1. Must be the same length as the number of columns in `A`.
  120. Returns
  121. -------
  122. G : NetworkX graph
  123. A bipartite graph with edges from the biadjacency matrix `A`, and
  124. nodes from `row_order` and `column_order`.
  125. Raises
  126. ------
  127. ValueError
  128. If `row_order` or `column_order` are provided and are not the same
  129. length as the number of rows or columns in `A`, respectively.
  130. Notes
  131. -----
  132. The nodes are labeled with the attribute `bipartite` set to an integer
  133. 0 or 1 representing membership in the `top` set (`bipartite=0`) or `bottom`
  134. set (`bipartite=1`) of the bipartite graph.
  135. If `create_using` is an instance of :class:`networkx.MultiGraph` or
  136. :class:`networkx.MultiDiGraph` and the entries of `A` are of
  137. type :class:`int`, then this function returns a multigraph (of the same
  138. type as `create_using`) with parallel edges. In this case, `edge_attribute`
  139. will be ignored.
  140. See Also
  141. --------
  142. biadjacency_matrix
  143. from_numpy_array
  144. References
  145. ----------
  146. [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
  147. """
  148. G = nx.empty_graph(0, create_using)
  149. n, m = A.shape
  150. # Check/set row_order and column_order to have correct length and default values
  151. row_order, column_order = _validate_initialize_bipartite_nodelists(
  152. A, row_order, column_order
  153. )
  154. # Make sure we get even the isolated nodes of the graph.
  155. G.add_nodes_from(range(n), bipartite=0)
  156. G.add_nodes_from(range(n, n + m), bipartite=1)
  157. # Create an iterable over (u, v, w) triples and for each triple, add an
  158. # edge from u to v with weight w.
  159. triples = ((u, n + v, d) for (u, v, d) in _generate_weighted_edges(A))
  160. # If the entries in the adjacency matrix are integers and the graph is a
  161. # multigraph, then create parallel edges, each with weight 1, for each
  162. # entry in the adjacency matrix. Otherwise, create one edge for each
  163. # positive entry in the adjacency matrix and set the weight of that edge to
  164. # be the entry in the matrix.
  165. if A.dtype.kind in ("i", "u") and G.is_multigraph():
  166. chain = itertools.chain.from_iterable
  167. triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples)
  168. G.add_weighted_edges_from(triples, weight=edge_attribute)
  169. # If the user provided nodelists, relabel the nodes of the graph inplace
  170. mapping = dict(
  171. itertools.chain(zip(range(n), row_order), zip(range(n, n + m), column_order))
  172. )
  173. if len(mapping):
  174. nx.relabel_nodes(G, mapping, copy=False)
  175. return G
  176. def _validate_initialize_bipartite_nodelists(A, row_order, column_order):
  177. n, m = A.shape
  178. # Validate nodelists if provided
  179. if row_order is not None:
  180. if len(row_order) != n:
  181. raise ValueError(
  182. "Length of row_order does not match number of rows in A ({n})"
  183. )
  184. else:
  185. row_order = []
  186. if column_order is not None:
  187. if len(column_order) != m:
  188. raise ValueError(
  189. "Length of column_order does not match number of columns in A ({m})"
  190. )
  191. else:
  192. column_order = []
  193. return row_order, column_order