From 4ee2b3b6ff57ebf36a8e21e040b6232c6f87cdee Mon Sep 17 00:00:00 2001 From: Brenda Praggastis Date: Wed, 9 Jun 2021 12:12:12 -0700 Subject: [PATCH] updated init to add toys --- docs/build/.buildinfo | 2 +- .../.doctrees/algorithms/algorithms.doctree | Bin 186847 -> 3515 bytes docs/build/.doctrees/classes/classes.doctree | Bin 709526 -> 3610 bytes docs/build/.doctrees/drawing/drawing.doctree | Bin 141915 -> 3657 bytes docs/build/.doctrees/environment.pickle | Bin 329167 -> 54507 bytes docs/build/.doctrees/reports/reports.doctree | Bin 69652 -> 3118 bytes .../_modules/algorithms/homology_mod2.html | 1135 ------- .../algorithms/s_centrality_measures.html | 586 ---- docs/build/_modules/classes/entity.html | 1259 -------- docs/build/_modules/classes/hypergraph.html | 2798 ----------------- docs/build/_modules/classes/staticentity.html | 1294 -------- docs/build/_modules/drawing/rubber_band.html | 715 ----- docs/build/_modules/drawing/two_column.html | 426 --- docs/build/_modules/drawing/util.html | 353 --- docs/build/_modules/index.html | 224 -- .../_modules/reports/descriptive_stats.html | 629 ---- docs/build/_static/documentation_options.js | 2 +- docs/build/algorithms/algorithms.html | 674 +--- docs/build/algorithms/modules.html | 17 +- docs/build/classes/classes.html | 2701 +--------------- docs/build/classes/modules.html | 10 +- docs/build/core.html | 37 +- docs/build/drawing/drawing.html | 443 +-- docs/build/drawing/modules.html | 10 +- docs/build/genindex.html | 656 +--- docs/build/glossary.html | 4 +- docs/build/home.html | 2 +- docs/build/index.html | 2 +- docs/build/install.html | 2 +- docs/build/license.html | 2 +- docs/build/nwhy.html | 2 +- docs/build/objects.inv | Bin 2278 -> 877 bytes docs/build/overview/index.html | 2 +- docs/build/publications.html | 2 +- docs/build/py-modindex.html | 310 -- docs/build/reports/modules.html | 6 +- docs/build/reports/reports.html | 285 +- docs/build/search.html | 2 +- docs/build/searchindex.js | 2 +- docs/build/widget.html | 2 +- docs/source/conf.py | 2 +- setup.py | 2 +- 42 files changed, 96 insertions(+), 14504 deletions(-) delete mode 100644 docs/build/_modules/algorithms/homology_mod2.html delete mode 100644 docs/build/_modules/algorithms/s_centrality_measures.html delete mode 100644 docs/build/_modules/classes/entity.html delete mode 100644 docs/build/_modules/classes/hypergraph.html delete mode 100644 docs/build/_modules/classes/staticentity.html delete mode 100644 docs/build/_modules/drawing/rubber_band.html delete mode 100644 docs/build/_modules/drawing/two_column.html delete mode 100644 docs/build/_modules/drawing/util.html delete mode 100644 docs/build/_modules/index.html delete mode 100644 docs/build/_modules/reports/descriptive_stats.html delete mode 100644 docs/build/py-modindex.html diff --git a/docs/build/.buildinfo b/docs/build/.buildinfo index d47e0bb6..15de628c 100644 --- a/docs/build/.buildinfo +++ b/docs/build/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: bbecb70f0b1dbbfd5f5e21c3f3cc3da1 +config: 61749d9a3b09dd54ea31a725ed375529 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/.doctrees/algorithms/algorithms.doctree b/docs/build/.doctrees/algorithms/algorithms.doctree index 03a511d1a4fad5be2c5e3a4eb851f39031d34f6d..aab674b2f0d7cfb642dfa5d4afbdb8f628288d2c 100644 GIT binary patch delta 275 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 diff --git a/docs/build/.doctrees/classes/classes.doctree b/docs/build/.doctrees/classes/classes.doctree index bf061d416db9d920d774d11701c118f626d5a564..ae309cc8a5a293434e4268290db62902f00d5662 100644 GIT binary patch delta 410 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 diff --git a/docs/build/.doctrees/drawing/drawing.doctree b/docs/build/.doctrees/drawing/drawing.doctree index 77265ab86771f750fde97abfa630d34220f84f5f..a9d74851329ba3651c46783d37cee973fc769f2b 100644 GIT binary patch delta 356 zcmcb8h2x|QTLa5fJ3fYuY{g8ItC+TMC1%LgPSMCvh@CRM+m=z5F>(4zTgK4o|7{tI zm@<+ke`L}#i_c)pV9t=q(9V$c7AQ?Z=;)D5DM~EQ%uClTDbLqU&d({$&C|`zPbtkw zosu~H$sxv&>6V8XUD-0~GNdzVKxP?KBAF$DVwNz>tkROq9E2&2vP^!{FUT_4vSmyM znlcq+iq0e?Qxs54;fGicbWC1JYFiw5L&n++rwsdy zn2gAbvW()4{*0cC6&XuQdpPqFb5lzy3sQ@xfP!R$Uxt%kMvPxZnO{b~U&ac*(hLQV f_}UB}pl%(YZdai0c%bgejHMZqwo7O-IWqzPehGqk literal 141915 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 LKu(=Pw5R_c%koj8 diff --git a/docs/build/.doctrees/environment.pickle b/docs/build/.doctrees/environment.pickle index ca6be2e3bdf37f9b64174eef3b0767f567683701..58acddd9de2e54cb80db3432d77aab2495f78a3b 100644 GIT binary patch literal 54507 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 diff --git a/docs/build/.doctrees/reports/reports.doctree b/docs/build/.doctrees/reports/reports.doctree index dd0af8e0cb52c501aa8c5b5e1e15cea66461259f..686572f77aa2f3464d7a67c60e7eb55ebda1b656 100644 GIT binary patch delta 209 zcmbQTfMuNoTLa5fWgdo&Y{g8ItC)^)C1%LgPSMCvh@CQhJ`1BVkgpUwB|{~4O3&np z*;3P=u`pUpmuF?PWy?s(kj_Y&d{IctfFH<;&tS}8&XCE_&XDyMC{02b)5D*epHiBW zs+*jjSCX1nQamMbdnFs=awevX#oJXm8D}#)Wz=R=WjJNnXT)SgW|U?WmG*GvCFZ7< vR2HNbPXW26)-S`!FC)e;qtvf7LjfdGmB9m4uLD%?no*Pyzx^T~qcbA_Wu!-& literal 69652 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 diff --git a/docs/build/_modules/algorithms/homology_mod2.html b/docs/build/_modules/algorithms/homology_mod2.html deleted file mode 100644 index ffc33828..00000000 --- a/docs/build/_modules/algorithms/homology_mod2.html +++ /dev/null @@ -1,1135 +0,0 @@ - - - - - - - - - - algorithms.homology_mod2 — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • algorithms.homology_mod2
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for algorithms.homology_mod2

-"""
-Homology and Smith Normal Form
-==============================
-The purpose of computing the Homology groups for data generated
-hypergraphs is to identify data sources that correspond to interesting
-features in the topology of the hypergraph.
-
-The elements of one of these Homology groups are generated by $k$
-dimensional cycles of relationships in the original data that are not
-bound together by higher order relationships. Ideally, we want the
-briefest description of these cycles; we want a minimal set of
-relationships exhibiting interesting cyclic behavior. This minimal set
-will be a bases for the Homology group.
-
-The cyclic relationships in the data are discovered using a **boundary
-map** represented as a matrix. To discover the bases we compute the
-**Smith Normal Form** of the boundary map.
-
-Homology Mod2
--------------
-This module computes the homology groups for data represented as an
-abstract simplicial complex with chain groups $\{C_k\}$ and $Z_2$ additions.
-The boundary matrices are represented as rectangular matrices over $Z_2$.
-These matrices are diagonalized and represented in Smith
-Normal Form. The kernel and image bases are computed and the Betti
-numbers and homology bases are returned.
-
-Methods for obtaining SNF for Z/2Z are based on Ferrario's work:
-http://www.dlfer.xyz/post/2016-10-27-smith-normal-form/
-
-"""
-
-import numpy as np
-import hypernetx as hnx
-import warnings
-import copy
-from hypernetx import HyperNetXError
-from collections import defaultdict
-import itertools as it
-import pickle
-from scipy.sparse import csr_matrix
-
-__all__ = [
-    "kchainbasis",
-    "bkMatrix",
-    "swap_rows",
-    "swap_columns",
-    "add_to_row",
-    "add_to_column",
-    "logical_dot",
-    "logical_matmul",
-    "matmulreduce",
-    "logical_matadd",
-    "smith_normal_form_mod2",
-    "reduced_row_echelon_form_mod2",
-    "boundary_group",
-    "chain_complex",
-    "betti",
-    "betti_numbers",
-    "homology_basis",
-    "hypergraph_homology_basis",
-    "interpret",
-]
-
-
-
[docs]def kchainbasis(h, k): - """ - Compute the set of k dimensional cells in the abstract simplicial - complex associated with the hypergraph. - - Parameters - ---------- - h : hnx.Hypergraph - k : int - dimension of cell - - Returns - ------- - : list - an ordered list of kchains represented as tuples of length k+1 - - See also - -------- - hnx.hypergraph.toplexes - - Notes - ----- - - Method works best if h is simple [Berge], i.e. no edge contains another and there are no duplicate edges (toplexes). - - Hypergraph node uids must be sortable. - - """ - - import itertools as it - - kchains = set() - for e in h.edges(): - en = sorted(h.edges[e]) - if len(en) == k + 1: - kchains.add(tuple(en)) - elif len(en) > k + 1: - kchains.update(set(it.combinations(en, k + 1))) - return sorted(list(kchains))
- - -
[docs]def bkMatrix(km1basis, kbasis): - """ - Compute the boundary map from $C_{k-1}$-basis to $C_k$ basis with - respect to $Z_2$ - - Parameters - ---------- - km1basis : indexable iterable - Ordered list of $k-1$ dimensional cell - kbasis : indexable iterable - Ordered list of $k$ dimensional cells - - Returns - ------- - bk : np.array - boundary matrix in $Z_2$ stored as boolean - - """ - bk = np.zeros((len(km1basis), len(kbasis)), dtype=int) - for cell in kbasis: - for idx in range(len(cell)): - face = cell[:idx] + cell[idx + 1 :] - row = km1basis.index(face) - col = kbasis.index(cell) - bk[row, col] = 1 - return bk
- - -def _rswap(i, j, S): - """ - Swaps ith and jth row of copy of S - - Parameters - ---------- - i : int - j : int - S : np.array - - Returns - ------- - N : np.array - """ - N = copy.deepcopy(S) - row = copy.deepcopy(N[i]) - N[i] = copy.deepcopy(N[j]) - N[j] = row - return N - - -def _cswap(i, j, S): - """ - Swaps ith and jth column of copy of S - - Parameters - ---------- - i : int - j : int - S : np.array - matrix - - Returns - ------- - N : np.array - """ - N = _rswap(i, j, S.transpose()).transpose() - return N - - -
[docs]def swap_rows(i, j, *args): - """ - Swaps ith and jth row of each matrix in args - Returns a list of new matrices - - Parameters - ---------- - i : int - j : int - args : np.arrays - - Returns - ------- - list - list of copies of args with ith and jth row swapped - """ - output = list() - for M in args: - output.append(_rswap(i, j, M)) - return output
- - -
[docs]def swap_columns(i, j, *args): - """ - Swaps ith and jth column of each matrix in args - Returns a list of new matrices - - Parameters - ---------- - i : int - j : int - args : np.arrays - - Returns - ------- - list - list of copies of args with ith and jth row swapped - """ - output = list() - for M in args: - output.append(_cswap(i, j, M)) - return output
- - -
[docs]def add_to_row(M, i, j): - """ - Replaces row i with logical xor between row i and j - - Parameters - ---------- - M : np.array - i : int - index of row being altered - j : int - index of row being added to altered - - Returns - ------- - N : np.array - """ - N = copy.deepcopy(M) - N[i] = 1 * np.logical_xor(N[i], N[j]) - return N
- - -
[docs]def add_to_column(M, i, j): - """ - Replaces column i (of M) with logical xor between column i and j - - Parameters - ---------- - M : np.array - matrix - i : int - index of column being altered - j : int - index of column being added to altered - - Returns - ------- - N : np.array - """ - N = M.transpose() - return add_to_row(N, i, j).transpose()
- - -
[docs]def logical_dot(ar1, ar2): - """ - Returns the boolean equivalent of the dot product mod 2 on two 1-d arrays of - the same length. - - Parameters - ---------- - ar1 : numpy.ndarray - 1-d array - ar2 : numpy.ndarray - 1-d array - - Returns - ------- - : bool - boolean value associated with dot product mod 2 - - Raises - ------ - HyperNetXError - If arrays are not of the same length an error will be raised. - """ - if len(ar1) != len(ar2): - raise HyperNetXError("logical_dot requires two 1-d arrays of the same length") - else: - return 1 * np.logical_xor.reduce(np.logical_and(ar1, ar2))
- - -
[docs]def logical_matmul(mat1, mat2): - """ - Returns the boolean equivalent of matrix multiplication mod 2 on two - binary arrays stored as type boolean - - Parameters - ---------- - mat1 : np.ndarray - 2-d array of boolean values - mat2 : np.ndarray - 2-d array of boolean values - - Returns - ------- - mat : np.ndarray - boolean matrix equivalent to the mod 2 matrix multiplication of the - matrices as matrices over Z/2Z - - Raises - ------ - HyperNetXError - If inner dimensions are not equal an error will be raised. - - """ - L1, R1 = mat1.shape - L2, R2 = mat2.shape - if R1 != L2: - raise HyperNetXError( - "logical_matmul called for matrices with inner dimensions mismatched" - ) - - mat = np.zeros((L1, R2), dtype=int) - mat2T = mat2.transpose() - for i in range(L1): - if np.any(mat1[i]): - for j in range(R2): - mat[i, j] = logical_dot(mat1[i], mat2T[j]) - else: - mat[i] = np.zeros((1, R2), dtype=int) - return mat
- - -
[docs]def matmulreduce(arr, reverse=False): - """ - Recursively applies a 'logical multiplication' to a list of boolean arrays. - - For arr = [arr[0],arr[1],arr[2]...arr[n]] returns product arr[0]arr[1]...arr[n] - If reverse = True, returns product arr[n]arr[n-1]...arr[0] - - Parameters - ---------- - arr : list of np.array - list of nxm matrices represented as np.array - reverse : bool, optional - order to multiply the matrices - - Returns - ------- - P : np.array - Product of matrices in the list - """ - if reverse: - items = range(len(arr) - 1, -1, -1) - else: - items = range(len(arr)) - P = arr[items[0]] - for i in items[1:]: - P = logical_matmul(P, arr[i]) * 1 - return P
- - -
[docs]def logical_matadd(mat1, mat2): - """ - Returns the boolean equivalent of matrix addition mod 2 on two - binary arrays stored as type boolean - - Parameters - ---------- - mat1 : np.ndarray - 2-d array of boolean values - mat2 : np.ndarray - 2-d array of boolean values - - Returns - ------- - mat : np.ndarray - boolean matrix equivalent to the mod 2 matrix addition of the - matrices as matrices over Z/2Z - - Raises - ------ - HyperNetXError - If dimensions are not equal an error will be raised. - - """ - S1 = mat1.shape - S2 = mat2.shape - mat = np.zeros(S1, dtype=int) - if S1 != S2: - raise HyperNetXError( - "logical_matadd called for matrices with different dimensions" - ) - if len(S1) == 1: - for idx in range(S1[0]): - mat[idx] = 1 * np.logical_xor(mat1[idx], mat2[idx]) - else: - for idx in range(S1[0]): - for jdx in range(S1[1]): - mat[idx, jdx] = 1 * np.logical_xor(mat1[idx, jdx], mat2[idx, jdx]) - return mat
- - -# Convenience methods for computing Smith Normal Form -# All of these operations have themselves as inverses - - -def _sr(i, j, M, L): - return swap_rows(i, j, M, L) - - -def _sc(i, j, M, R): - return swap_columns(i, j, M, R) - - -def _ar(i, j, M, L): - return add_to_row(M, i, j), add_to_row(L, i, j) - - -def _ac(i, j, M, R): - return add_to_column(M, i, j), add_to_column(R, i, j) - - -def _get_next_pivot(M, s1, s2=None): - """ - Determines the first r,c indices in the submatrix of M starting - with row s1 and column s2 index (row,col) that is nonzero, - if it exists. - - Search starts with the s2th column and looks for the first nonzero - s1 row. If none is found, search continues to the next column and so - on. - - Parameters - ---------- - M : np.array - matrix represented as np.array - s1 : int - index of row position to start submatrix of M - s2 : int, optional, default = s1 - index of column position to start submatrix of M - - Returns - ------- - (r,c) : tuple of int or None - - """ - # find the next nonzero pivot to put in s,s spot for Smith Normal Form - m, n = M.shape - if not s2: - s2 = s1 - for c in range(s2, n): - for r in range(s1, m): - if M[r, c]: - return (r, c) - return None - - -
[docs]def smith_normal_form_mod2(M): - """ - Computes the invertible transformation matrices needed to compute the - Smith Normal Form of M modulo 2 - - Parameters - ---------- - M : np.array - a rectangular matrix with data type bool - track : bool - if track=True will print out the transformation as Z/2Z matrix as it - discovers L[i] and R[j] - - Returns - ------- - L, R, S, Linv : np.arrays - LMR = S is the Smith Normal Form of the matrix M. - - Note - ---- - Given a mxn matrix $M$ with - entries in $Z_2$ we start with the equation: $L M R = S$, where - $L = I_m$, and $R=I_n$ are identity matrices and $S = M$. We - repeatedly apply actions to the left and right side of the equation - to transform S into a diagonal matrix. - For each action applied to the left side we apply its inverse - action to the right side of I_m to generate $L^{-1}$. - Finally we verify: - $L M R = S$ and $LLinv = I_m$. - """ - - S = copy.copy(M) - dimL, dimR = M.shape - - # initialize left and right transformations with identity matrices - L = np.eye(dimL, dtype=int) - R = np.eye(dimR, dtype=int) - Linv = np.eye(dimL, dtype=int) - for s in range(min(dimL, dimR)): - # Find index pair (rdx,cdx) with value 1 in submatrix M[s:,s:] - pivot = _get_next_pivot(S, s) - if not pivot: - break - else: - rdx, cdx = pivot - # Swap rows and columns as needed so that 1 is in the s,s position - if rdx > s: - S, L = _sr(s, rdx, S, L) - Linv = swap_columns(rdx, s, Linv)[0] - if cdx > s: - S, R = _sc(s, cdx, S, R) - # add sth row to every row with 1 in sth column & sth column to every column with 1 in sth row - row_indices = [idx for idx in range(s + 1, dimL) if S[idx, s] == 1] - for rdx in row_indices: - S, L = _ar(rdx, s, S, L) - Linv = add_to_column(Linv, s, rdx) - column_indices = [jdx for jdx in range(s + 1, dimR) if S[s, jdx] == 1] - for cdx in column_indices: - S, R = _ac(cdx, s, S, R) - return L, R, S, Linv
- - -
[docs]def reduced_row_echelon_form_mod2(M): - """ - Computes the invertible transformation matrices needed to compute - the reduced row echelon form of M modulo 2 - - Parameters - ---------- - M : np.array - a rectangular matrix with elements in $Z_2$ - - Returns - ------- - L, S, Linv : np.arrays - LM = S where S is the reduced echelon form of M - and M = LinvS - """ - S = copy.deepcopy(M) - dimL, dimR = M.shape - - # method with numpy - Linv = np.eye(dimL, dtype=int) - L = np.eye(dimL, dtype=int) - - s2 = 0 - s1 = 0 - while s2 <= dimR and s1 <= dimL: - # Find index pair (rdx,cdx) with value 1 in submatrix M[s1:,s2:] - # look for the first 1 in the s2 column - pivot = _get_next_pivot(S, s1, s2) - - if not pivot: - return L, S, Linv - else: - rdx, cdx = pivot - if rdx > s1: - # Swap rows as needed so that 1 leads the row - S, L = _sr(s1, rdx, S, L) - Linv = swap_columns(rdx, s1, Linv)[0] - # add s1th row to every nonzero row - row_indices = [ - idx for idx in range(0, dimL) if idx != s1 and S[idx, cdx] == 1 - ] - for idx in row_indices: - S, L = _ar(idx, s1, S, L) - Linv = add_to_column(Linv, s1, idx) - s1, s2 = s1 + 1, cdx + 1 - - return L, S, Linv
- - -
[docs]def boundary_group(image_basis): - """ - Returns a csr_matrix with rows corresponding to the elements of the - group generated by image basis over $\mathbb{Z}_2$ - - Parameters - ---------- - image_basis : numpy.ndarray or scipy.sparse.csr_matrix - 2d-array of basis elements - - Returns - ------- - : scipy.sparse.csr_matrix - """ - if len(image_basis) > 10: - msg = """ - This method is inefficient for large image bases. - """ - warnings.warn(msg, stacklevel=2) - if np.sum(image_basis) == 0: - return None - dim = image_basis.shape[0] - itm = csr_matrix(list(it.product([0, 1], repeat=dim))) - return csr_matrix(np.mod(itm * image_basis, 2))
- - -def _compute_matrices_for_snf(bd): - """ - Helper method for smith normal form decomposition for boundary maps - associated to chain complex - - Parameters - ---------- - bd : dict - dict of k-boundary matrices keyed on dimension of domain - - Returns - ------- - L,R,S,Linv : dict - dict of matrices ranging over krange - - """ - L, R, S, Linv = [dict() for i in range(4)] - - for kdx in bd: - L[kdx], R[kdx], S[kdx], Linv[kdx] = smith_normal_form_mod2(bd[kdx]) - return L, R, S, Linv - - -def _get_krange(max_dim, k=None): - """ - Helper method to compute range of appropriate k dimensions for homology - computations given k and the max dimension of a simplicial complex - """ - if k is None: - krange = [1, max_dim] - elif isinstance(k, int): - if k == 0: - msg = ( - "Only kth simplicial homology groups for k>0 may be computed." - "If you are interested in k=0, compute the number connected components." - ) - print(msg) - return - if k > max_dim: - msg = f"No simplices of dim {k} exist. k adjusted to max dim." - print(msg) - krange = [min([k, max_dim])] * 2 - elif not len(k) == 2: - msg = f"Please enter krange as a positive integer or list of integers: [<min k>,<max k>] inclusive." - print(msg) - return None - elif not k[0] <= k[1]: - msg = f"k must be an integer or a list of two integers [min,max] with min <=max" - print(msg) - return None - else: - krange = k - - if krange[1] > max_dim: - msg = f"No simplices of dim {krange[1]} exist. Range adjusted to max dim." - print(msg) - krange[1] = max_dim - if krange[0] < 1: - msg = ( - "Only kth simplicial homology groups for k>0 may be computed." - "If you are interested in k=0, compute the number of connected components." - ) - print(msg) - krange[0] = 1 - return krange - - -
[docs]def chain_complex(h, k=None): - """ - Compute the k-chains and k-boundary maps required to compute homology - for all values in k - - Parameters - ---------- - h : hnx.Hypergraph - k : int or list of length 2, optional, default=None - k must be an integer greater than 0 or a list of - length 2 indicating min and max dimensions to be - computed. eg. if k = [1,2] then 0,1,2,3-chains - and boundary maps for k=1,2,3 will be returned, - if None than k = [1,max dimension of edge in h] - - Returns - ------- - C, bd : dict - C is a dictionary of lists - bd is a dictionary of numpy arrays - """ - max_dim = np.max([len(h.edges[e]) for e in h.edges()]) - 1 - krange = _get_krange(max_dim, k) - if not krange: - return - # Compute chain complex - - C = defaultdict(list) - C[krange[0] - 1] = kchainbasis(h, krange[0] - 1) - bd = dict() - for kdx in range(krange[0], krange[1] + 2): - C[kdx] = kchainbasis(h, kdx) - bd[kdx] = bkMatrix(C[kdx - 1], C[kdx]) - return C, bd
- - -
[docs]def betti(bd, k=None): - """ - Generate the kth-betti numbers for a chain complex with boundary - matrices given by bd - - Parameters - ---------- - bd: dict of k-boundary matrices keyed on dimension of domain - k : int, list or tuple, optional, default=None - list must be min value and max value of k values inclusive - if None, then all betti numbers for dimensions of existing cells will be - computed. - - Returns - ------- - betti : dict - Description - """ - rank = defaultdict(int) - if k: - max_dim = max(bd.keys()) - krange = _get_krange(max_dim, k) - if not krange: - return - kvals = sorted(set(range(krange[0], krange[1] + 2)).intersection(bd.keys())) - else: - kvals = bd.keys() - for kdx in kvals: - _, S, _ = hnx.reduced_row_echelon_form_mod2(bd[kdx]) - rank[kdx] = np.sum(np.sum(S, axis=1).astype(bool)) - - betti = dict() - for kdx in kvals: - if kdx + 1 in kvals: - betti[kdx] = bd[kdx].shape[1] - rank[kdx] - rank[kdx + 1] - else: - continue - - return betti
- - -
[docs]def betti_numbers(h, k=None): - """ - Return the kth betti numbers for the simplicial homology of the ASC - associated to h - - Parameters - ---------- - h : hnx.Hypergraph - Hypergraph to compute the betti numbers from - k : int or list, optional, default=None - list must be min value and max value of k values inclusive - if None, then all betti numbers for dimensions of existing cells will be - computed. - - Returns - ------- - betti : dict - A dictionary of betti numbers keyed by dimension - """ - _, bd = chain_complex(h, k) - return betti(bd)
- - -
[docs]def homology_basis(bd, k=None, boundary=False, **kwargs): - """ - Compute a basis for the kth-simplicial homology group, $H_k$, defined by a - chain complex $C$ with boundary maps given by bd $= \{k:\partial_k$\}$ - - Parameters - ---------- - bd : dict - dict of boundary matrices on k-chains to k-1 chains keyed on k - if krange is a tuple then all boundary matrices k \in [krange[0],..,krange[1]] - inclusive must be in the dictionary - k : int or list of ints, optional, default=None - k must be a positive integer or a list of - 2 integers indicating min and max dimensions to be - computed, if none given all homology groups will be computed from - available boundary matrices in bd - boundary : bool - option to return a basis for the boundary group from each dimension. - Needed to compute the shortest generators in the homology group. - - Returns - ------- - basis : dict - dict of generators as 0-1 tuples keyed by dim - basis for dimension k will be returned only if bd[k] and bd[k+1] have - been provided. - im : dict - dict of boundary group generators keyed by dim - """ - max_dim = max(bd.keys()) - if k: - krange = _get_krange(max_dim, k) - kvals = sorted( - set(bd.keys()).intersection(range(krange[0], krange[1] + 2)) - ) # to get kth dim need k+1 bdry matrix - else: - kvals = bd.keys() - - L, R, S, Linv = _compute_matrices_for_snf( - {k: v for k, v in bd.items() if k in kvals} - ) - - rank = dict() - for kdx in kvals: - rank[kdx] = np.sum(S[kdx]) - - basis = dict() - im = dict() - for kdx in kvals: - if kdx + 1 not in kvals: - continue - rank1 = rank[kdx] - rank2 = rank[kdx + 1] - ker1 = R[kdx][:, rank1:] - im2 = Linv[kdx + 1][:, :rank2] - cokernel2 = Linv[kdx + 1][:, rank2:] - cokproj2 = L[kdx + 1][rank2:, :] - - proj = matmulreduce([cokernel2, cokproj2, ker1]).transpose() - _, proj, _ = reduced_row_echelon_form_mod2(proj) - # proj = np.array(proj) - basis[kdx] = np.array([row for row in proj if np.any(row)]) - if boundary: - im[kdx] = im2.transpose() - if boundary: - return basis, im - else: - return basis
- - -
[docs]def hypergraph_homology_basis(h, k=None, shortest=False, interpreted=True): - """ - Computes the kth-homology groups mod 2 for the ASC - associated with the hypergraph h for k in krange inclusive - - Parameters - ---------- - h : hnx.Hypergraph - k : int or list of length 2, optional, default = None - k must be an integer greater than 0 or a list of - length 2 indicating min and max dimensions to be - computed - shortest : bool, optional, default=False - option to look for shortest representative for each coset in the - homology group, only good for relatively small examples - interpreted : bool, optional, default = True - if True will return an explicit basis in terms of the k-chains - - Returns - ------- - basis : list - list of generators as k-chains as boolean vectors - interpreted_basis : - lists of kchains in basis - - """ - C, bd = chain_complex(h, k) - if shortest: - basis = defaultdict(list) - tbasis, im = homology_basis(bd, boundary=True) - for kdx in tbasis: - imgrp = boundary_group(im[kdx]) - if imgrp is None: - basis[kdx] = tbasis[kdx] - else: - for b in tbasis[kdx]: - coset = np.array( - np.mod(imgrp + b, 2) - ) # dimensions appear to be wrong. See tests2 cell 5 - idx = np.argmin(np.sum(coset, axis=1)) - basis[kdx].append(coset[idx]) - basis = dict(basis) - - else: - basis = homology_basis(bd) - - if interpreted: - interpreted_basis = dict() - for kdx in basis: - interpreted_basis[kdx] = interpret(C[kdx], basis[kdx], labels=None) - return basis, interpreted_basis - else: - return basis
- - -
[docs]def interpret(Ck, arr, labels=None): - """ - Returns the data as represented in Ck associated with the arr - - Parameters - ---------- - Ck : list - a list of k-cells being referenced by arr - arr : np.array - array of 0-1 vectors - labels : dict, optional - dictionary of labels to associate to the nodes in the cells - - Returns - ---- - : list - list of k-cells referenced by data in Ck - - """ - - def translate(cell, labels=labels): - if not labels: - return cell - else: - temp = list() - for node in cell: - temp.append(labels[node]) - return tuple(temp) - - output = list() - for vec in arr: - if len(Ck) != len(vec): - raise HyperNetXError("elements of arr must have the same length as Ck") - output.append([translate(Ck[idx]) for idx in range(len(vec)) if vec[idx] == 1]) - return output
-
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/algorithms/s_centrality_measures.html b/docs/build/_modules/algorithms/s_centrality_measures.html deleted file mode 100644 index b3b67d10..00000000 --- a/docs/build/_modules/algorithms/s_centrality_measures.html +++ /dev/null @@ -1,586 +0,0 @@ - - - - - - - - - - algorithms.s_centrality_measures — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • algorithms.s_centrality_measures
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for algorithms.s_centrality_measures

-# Copyright © 2018 Battelle Memorial Institute
-# All rights reserved.
-
-"""
-
-S-Centrality Measures
-=====================
-We generalize graph metrics to s-metrics for a hypergraph by using its s-connected
-components. This is accomplished by computing the s edge-adjacency matrix and
-constructing the corresponding graph of the matrix. We then use existing graph metrics
-on this representation of the hypergraph. In essence we construct an *s*-line graph
-corresponding to the hypergraph on which to apply our methods.
-
-S-Metrics for hypergraphs are discussed in depth in:        
-*Aksoy, S.G., Joslyn, C., Ortiz Marrero, C. et al. Hypernetwork science via high-order hypergraph walks.
-EPJ Data Sci. 9, 16 (2020). https://doi.org/10.1140/epjds/s13688-020-00231-0*
-
-"""
-
-import numpy as np
-from collections import defaultdict
-import networkx as nx
-import warnings
-import sys
-from functools import partial
-
-try:
-    import nwhy
-
-    nwhy_available = True
-except:
-    nwhy_available = False
-
-sys.setrecursionlimit(10000)
-
-__all__ = [
-    "s_betweenness_centrality",
-    "s_harmonic_closeness_centrality",
-    "s_harmonic_centrality",
-    "s_closeness_centrality",
-    "s_eccentricity",
-]
-
-
-def _s_centrality(
-    func, H, s=1, edges=True, f=None, return_singletons=True, use_nwhy=True, **kwargs
-):
-    """
-    Wrapper for computing s-centrality either in NetworkX or in NWHy
-
-    Parameters
-    ----------
-    func : function
-        Function or partial function from NetworkX or NWHy
-    H : hnx.Hypergraph
-
-    s : int, optional
-        s-width for computation
-    edges : bool, optional
-        If True, an edge linegraph will be used, otherwise a node linegraph will be used
-    f : str, optional
-        Identifier of node or edge of interest for computing centrality
-    return_singletons : bool, optional
-        If True will return 0 value for each singleton in the s-linegraph
-    use_nwhy : bool, optional
-        If True will attempt to use nwhy centrality methods if availaable
-    **kwargs
-        Centrality metric specific keyword arguments to be passed to func
-
-    Returns
-    : dict
-        dictionary of centrality scores keyed by names
-    """
-    comps = H.s_component_subgraphs(
-        s=s, edges=edges, return_singletons=return_singletons
-    )
-    if f is not None:
-        for cps in comps:
-            if (edges and f in cps.edges) or (not edges and f in cps.nodes):
-                comps = [cps]
-                break
-        else:
-            return {f: 0}
-
-    stats = dict()
-    if H.isstatic:
-        for h in comps:
-            if edges:
-                vertices = h.edges
-            else:
-                vertices = h.nodes
-
-            if h.shape[edges * 1] == 1:
-                stats.update({v: 0 for v in vertices})
-            elif use_nwhy and nwhy_available and h.nwhy:
-                g = h.get_linegraph(s=s, edges=edges, use_nwhy=True)
-                stats.update(dict(zip(vertices, func(g, **kwargs))))
-            else:
-                g = h.get_linegraph(s=s, edges=edges, use_nwhy=False)
-                stats.update(
-                    {
-                        h.get_name(k, edges=edges): v
-                        for k, v in func(g, **kwargs).items()
-                    }
-                )
-            if f:
-                return {f: stats[f]}
-    else:
-        for h in comps:
-            if edges:
-                A, Adict = h.edge_adjacency_matrix(s=s, index=True)
-            else:
-                A, Adict = h.adjacency_matrix(s=s, index=True)
-            A = (A >= s) * 1
-            g = nx.from_scipy_sparse_matrix(A)
-            stats.update({Adict[k]: v for k, v in func(g, **kwargs).items()})
-            if f:
-                return {f: stats[f]}
-
-    return stats
-
-
-
[docs]def s_betweenness_centrality( - H, s=1, edges=True, normalized=True, return_singletons=True, use_nwhy=True -): - """ - A centrality measure for an s-edge(node) subgraph of H based on shortest paths. - Equals the betweenness centrality of vertices in the edge(node) s-linegraph. - - In a graph (2-uniform hypergraph) the betweenness centrality of a vertex $v$ - is the ratio of the number of non-trivial shortest paths between any pair of - vertices in the graph that pass through $v$ divided by the total number of - non-trivial shortest paths in the graph. - - The centrality of edge to all shortest s-edge paths - $V$ = the set of vertices in the linegraph. - $\sigma(s,t)$ = the number of shortest paths between vertices $s$ and $t$. - $\sigma(s, t|v)$ = the number of those paths that pass through vertex $v$ - $$c_B(v) =\sum_{s \neq t \in V} \frac{\sigma(s, t|v)}{\sigma(s, t)}$$ - - Parameters - ---------- - H : hnx.Hypergraph - s : int - s connectedness requirement - edges : bool, optional - determines if edge or node linegraph - normalized - bool, default=False, - If true the betweenness values are normalized by `2/((n-1)(n-2))`, - where n is the number of edges in H - return_singletons : bool, optional - if False will ignore singleton components of linegraph - - - Returns - ------- - : dict - A dictionary of s-betweenness centrality value of the edges. - - """ - if use_nwhy and nwhy_available and H.nwhy: - func = partial(nwhy.Slinegraph.s_betweenness_centrality, normalized=False) - else: - use_nwhy = False - func = partial(nx.betweenness_centrality, normalized=False) - result = _s_centrality( - func, - H, - s=s, - edges=edges, - return_singletons=return_singletons, - use_nwhy=use_nwhy, - ) - - if normalized and H.shape[edges * 1] > 2: - n = H.shape[edges * 1] - return {k: v * 2 / ((n - 1) * (n - 2)) for k, v in result.items()} - else: - return result
- - -
[docs]def s_closeness_centrality( - H, s=1, edges=True, return_singletons=True, source=None, use_nwhy=True -): - """ - In a connected component the reciprocal of the sum of the distance between an - edge(node) and all other edges(nodes) in the component times the number of edges(nodes) - in the component minus 1. - - $V$ = the set of vertices in the linegraph. - $n = |V|$ - $d$ = shortest path distance - $$C(u) = \frac{n - 1}{\sum_{v \neq u \in V} d(v, u)}$$ - - - Parameters - ---------- - H : hnx.Hypergraph - - s : int, optional - - edges : bool, optional - Indicates if method should compute edge linegraph (default) or node linegraph. - return_singletons : bool, optional - Indicates if method should return values for singleton components. - source : str, optional - Identifier of node or edge of interest for computing centrality - use_nwhy : bool, optional - If true will use the :ref:`NWHy library <nwhy>` if available. - - Returns - ------- - : dict or float - returns the s-closeness centrality value of the edges(nodes). - If source=None a dictionary of values for each s-edge in H is returned. - If source then a single value is returned. - """ - if use_nwhy and nwhy_available and H.nwhy: - func = partial(nwhy.Slinegraph.s_closeness_centrality) - else: - use_nwhy = False - func = partial(nx.closeness_centrality) - return _s_centrality( - func, - H, - s=s, - edges=edges, - return_singletons=return_singletons, - f=source, - use_nwhy=use_nwhy, - )
- - -
[docs]def s_harmonic_closeness_centrality(H, s=1, edge=None, use_nwhy=True): - msg = """ - s_harmonic_closeness_centrality is being replaced with s_harmonic_centrality - and will not be available in future releases. - """ - warnings.warn(msg) - return s_harmonic_centrality(H, s=s, edges=True, normalized=True, source=edge)
- - -
[docs]def s_harmonic_centrality( - H, - s=1, - edges=True, - source=None, - normalized=False, - return_singletons=True, - use_nwhy=True, -): - """ - A centrality measure for an s-edge subgraph of H. A value equal to 1 means the s-edge - intersects every other s-edge in H. All values range between 0 and 1. - Edges of size less than s return 0. If H contains only one s-edge a 0 is returned. - - The denormalized reciprocal of the harmonic mean of all distances from $u$ to all other vertices. - $V$ = the set of vertices in the linegraph. - $d$ = shortest path distance - $$C(u) = \sum_{v \neq u \in V} \frac{1}{d(v, u)}$$ - - Normalized this becomes: - $$C(u) = \sum_{v \neq u \in V} \frac{1}{d(v, u)}\cdot\frac{2}{(n-1)(n-2)}$$ - where $n$ is the number vertices. - - Parameters - ---------- - H : hnx.Hypergraph - - s : int, optional - - edges : bool, optional - Indicates if method should compute edge linegraph (default) or node linegraph. - return_singletons : bool, optional - Indicates if method should return values for singleton components. - source : str, optional - Identifier of node or edge of interest for computing centrality - use_nwhy : bool, optional - If true will use the :ref:`NWHy library <nwhy>` if available. - - Returns - ------- - : dict or float - returns the s-harmonic closeness centrality value of the edges, a number between 0 and 1 inclusive. - If source=None a dictionary of values for each s-edge in H is returned. - If source then a single value is returned. - - """ - - if use_nwhy and nwhy_available and H.nwhy: - func = partial(nwhy.Slinegraph.s_harmonic_closeness_centrality) - else: - use_nwhy = False - func = partial(nx.harmonic_centrality) - result = _s_centrality( - func, - H, - s=s, - edges=edges, - return_singletons=return_singletons, - f=source, - use_nwhy=use_nwhy, - ) - - if normalized and H.shape[edges * 1] > 2: - n = H.shape[edges * 1] - return {k: v * 2 / ((n - 1) * (n - 2)) for k, v in result.items()} - else: - return result
- - -
[docs]def s_eccentricity( - H, s=1, edges=True, source=None, return_singletons=True, use_nwhy=True -): - """ - The length of the longest shortest path from a vertex $u$ to every other vertex in the linegraph. - $V$ = set of vertices in the linegraph - $d$ = shortest path distance - $$ \text{s-ecc}(u) = \text{max}\{d(u,v): v \in V\} $$ - - Parameters - ---------- - H : hnx.Hypergraph - - s : int, optional - - edges : bool, optional - Indicates if method should compute edge linegraph (default) or node linegraph. - return_singletons : bool, optional - Indicates if method should return values for singleton components. - source : str, optional - Identifier of node or edge of interest for computing centrality - use_nwhy : bool, optional - If true will use the :ref:`NWHy library <nwhy>` if available. - - Returns - ------- - : dict or float - returns the s-eccentricity value of the edges(nodes). - If source=None a dictionary of values for each s-edge in H is returned. - If source then a single value is returned. - - """ - if use_nwhy and nwhy_available and H.nwhy: - func = nwhy.Slinegraph.s_eccentricity - else: - use_nwhy = False - func = nx.eccentricity - - if source is not None: - return _s_centrality( - func, - H, - s=s, - edges=edges, - f=source, - return_singletons=return_singletons, - use_nwhy=use_nwhy, - ) - else: - return _s_centrality( - func, - H, - s=s, - edges=edges, - return_singletons=return_singletons, - use_nwhy=use_nwhy, - )
-
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/classes/entity.html b/docs/build/_modules/classes/entity.html deleted file mode 100644 index c240bd16..00000000 --- a/docs/build/_modules/classes/entity.html +++ /dev/null @@ -1,1259 +0,0 @@ - - - - - - - - - - classes.entity — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • classes.entity
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for classes.entity

-# Copyright © 2018 Battelle Memorial Institute
-# All rights reserved.
-
-from collections import defaultdict
-import warnings
-from copy import copy
-import numpy as np
-import networkx as nx
-from hypernetx import HyperNetXError
-
-__all__ = ["Entity", "EntitySet"]
-
-
-
[docs]class Entity(object): - """ - Base class for objects used in building network-like objects including - Hypergraphs, Posets, Cell Complexes. - - Parameters - ---------- - uid : hashable - a unique identifier - - elements : list or dict, optional, default: None - a list of entities with identifiers different than uid and/or - hashables different than uid, see `Honor System`_ - - entity : Entity - an Entity object to be cloned into a new Entity with uid. If the uid is the same as - Entity.uid then the entities will not be distinguishable and error will be raised. - The `elements` in the signature will be added to the cloned entity. - - props : keyword arguments, optional, default: {} - properties belonging to the entity added as key=value pairs. - Both key and value must be hashable. - - Notes - ----- - - An Entity is a container-like object, which has a unique identifier and - may contain elements and have properties. - The Entity class was created as a generic object providing structure for - Hypergraph nodes and edges. - - - An Entity is distinguished by its identifier (sortable,hashable) :func:`Entity.uid` - - An Entity is a container for other entities but may not contain itself, :func:`Entity.elements` - - An Entity has properties :func:`Entity.properties` - - An Entity has memberships to other entities, :func:`Entity.memberships`. - - An Entity has children, :func:`Entity.children`, which are the elements of its elements. - - :func:`Entity.children` are registered in the :func:`Entity.registry`. - - All descendents of Entity are registered in :func:`Entity.fullregistry()`. - - .. _Honor System: - - **Honor System** - - HyperNetX has an Honor System that applies to Entity uid values. - Two entities are equal if their __dict__ objects match. - For performance reasons many methods distinguish entities by their uids. - It is, therefore, up to the user to ensure entities with the same uids are indeed the same. - Not doing so may cause undesirable side effects. - In particular, the methods in the Hypergraph class assume distinct nodes and edges - have distinct uids. - - Examples - -------- - - >>> x = Entity('x') - >>> y = Entity('y',[x]) - >>> z = Entity('z',[x,y],weight=1) - >>> z - Entity(z,['y', 'x'],{'weight': 1}) - >>> z.uid - 'z' - >>> z.elements - {'x': Entity(x,[],{}), 'y': Entity(y,['x'],{})} - >>> z.properties - {'weight': 1} - >>> z.children - {'x'} - >>> x.memberships - {'y': Entity(y,['x'],{}), 'z': Entity(z,['y', 'x'],{'weight': 1})} - >>> z.fullregistry() - {'x': Entity(x,[],{}), 'y': Entity(y,['x'],{})} - - - See Also - -------- - EntitySet - - """ - - def __init__(self, uid, elements=[], entity=None, **props): - super().__init__() - - self._uid = uid - - if entity is not None: - if isinstance(entity, Entity): - if uid == entity.uid: - raise HyperNetXError( - "The new entity will be indistinguishable from the original with the same uid. Use a differen uid." - ) - self._elements = entity.elements - self._memberships = entity.memberships - self.__dict__.update(entity.properties) - else: - self._elements = dict() - self._memberships = dict() - self._registry = dict() - - self.__dict__.update(props) - self._registry = self.registry - - if isinstance(elements, dict): - for k, v in elements.items(): - if isinstance(v, Entity): - self.add_element(v) - else: - self.add_element(Entity(k, v)) - elif elements is not None: - self.add(*elements) - - @property - def properties(self): - """Dictionary of properties of entity""" - temp = self.__dict__.copy() - del temp["_elements"] - del temp["_memberships"] - del temp["_registry"] - del temp["_uid"] - return temp - - @property - def uid(self): - """String identifier for entity""" - return self._uid - - @property - def elements(self): - """ - Dictionary of elements belonging to entity. - """ - return dict(self._elements) - - @property - def memberships(self): - """ - Dictionary of elements to which entity belongs. - - This assignment is done on construction and controlled by - :func:`Entity.add_element()` - and :func:`Entity.remove_element()` methods. - """ - - return { - k: self._memberships[k] - for k in self._memberships - if not isinstance(self._memberships[k], EntitySet) - } - - @property - def children(self): - """ - Set of uids of the elements of elements of entity. - - To return set of ids for deeper level use: - :code:`Entity.levelset(level).keys()` - see: :func:`Entity.levelset` - """ - return set(self.levelset(2).keys()) - - @property - def registry(self): - """ - Dictionary of uid:Entity pairs for children entity. - - To return a dictionary of all entities at all depths - :func:`Entity.complete_registry()` - """ - return self.levelset(2) - - @property - def uidset(self): - """ - Set of uids of elements of entity. - """ - return frozenset(self._elements.keys()) - - @property - def incidence_dict(self): - """ - Dictionary of element.uid:element.uidset for each element in entity - - To return an incidence dictionary of all nested entities in entity - use nested_incidence_dict - """ - temp = dict() - for ent in self.elements.values(): - temp[ent.uid] = {item for item in ent.elements} - return temp - - @property - def is_empty(self): - """Boolean indicating if entity.elements is empty""" - return len(self) == 0 - - @property - def is_bipartite(self): - """ - Returns boolean indicating if the entity satisfies the `Bipartite Condition`_ - """ - if self.uidset.isdisjoint(self.children): - return True - else: - return False - - def __eq__(self, other): - """ - Defines equality for Entities based on equivalence of their __dict__ objects. - - Checks all levels of self and other to verify they are - referencing the same uids and that they have the same set of properties. - If at any point we get duplicate addresses we stop checking that branch - because we are guaranteed equality from there on. - - May cause a recursion error if depth is too great. - """ - seen = set() - # Define a compare method to call recursively on each level of self and other - - def _comp(a, b, seen): - # Compare top level properties: same class? same ids? same children? same parents? same attributes? - if ( - (a.__class__ != b.__class__) - or (a.uid != b.uid) - or (a.uidset != b.uidset) - or (a.properties != b.properties) - or (a.memberships != b.memberships) - ): - return False - # If all agree then look at the next level down since a and b share uidsets. - for uid, elt in a.elements.items(): - if isinstance(elt, Entity): - if uid in seen: - continue - seen.add(uid) - if not _comp(elt, b[uid], seen): - return False - # if not an Entity then elt is hashable so we usual equality - elif elt != b[uid]: - return False - return True - - return _comp(self, other, seen) - - def __len__(self): - """Returns the number of elements in entity""" - return len(self._elements) - - def __str__(self): - """Return the entity uid.""" - return f"{self.uid}" - - def __repr__(self): - """Returns a string resembling the constructor for entity without any - children""" - return f"Entity({self._uid},{list(self.uidset)},{self.properties})" - - def __contains__(self, item): - """ - Defines containment for Entities. - - Parameters - ---------- - item : hashable or Entity - - Returns - ------- - Boolean - - Depends on the `Honor System`_ . Allows for uids to be used as shorthand for their entity. - This is done for performance reasons, but will fail if uids are - not unique to their entities. - Is not transitive. - """ - if isinstance(item, Entity): - return item.uid in self._elements - else: - return item in self._elements - - def __getitem__(self, item): - """ - Returns Entity element by uid. Use :func:`E[uid]`. - - Parameters - ---------- - item : hashable or Entity - - Returns - ------- - Entity or None - - If item not in entity, returns None. - """ - if isinstance(item, Entity): - return self._elements.get(item.uid, "") - else: - return self._elements.get(item) - - def __iter__(self): - """Returns iterator on element ids.""" - return iter(self.elements) - - def __call__(self): - """Returns an iterator on elements""" - for e in self.elements.values(): - yield e - - def __setattr__(self, k, v): - """Sets entity property. - - Parameters - ---------- - k : hashable, property key - v : hashable, property value - Will not set uid or change elements or memberships. - - Returns - ------- - None - - """ - if k == "uid": - raise HyperNetXError( - "Cannot reassign uid to Entity once it" - " has been created. Create a clone instead." - ) - elif k == "elements": - raise HyperNetXError("To add elements to Entity use self.add().") - elif k == "memberships": - raise HyperNetXError( - "Can't choose your own memberships, " "they are like parents!" - ) - else: - self.__dict__[k] = v - - def _depth_finder(self, entset=None): - """ - Helper method when working with levels. - - Parameters - ---------- - entset : dict, optional - a dictionary of entities keyed by uid - - Returns - ------- - Dictionary extending entset - """ - temp = dict() - for uid, item in entset.items(): - temp.update(item.elements) - return temp - -
[docs] def level(self, item, max_depth=10): - """ - The first level where item appears in self. - - Parameters - ---------- - item : hashable - uid for an entity - - max_depth : int, default: 10 - last level to check for entity - - Returns - ------- - level : int - - Note - ---- - Item must be the uid of an entity listed - in :func:`fullregistry()` - """ - d = 1 - currentlevel = self.levelset(1) - while d <= max_depth + 1: - if item in currentlevel: - return d - else: - d += 1 - currentlevel = self._depth_finder(currentlevel) - return None
- -
[docs] def levelset(self, k=1): - """ - A dictionary of level k of self. - - Parameters - ---------- - k : int, optional, default: 1 - - Returns - ------- - levelset : dict - - Note - ---- - An Entity contains other entities, hence the relationships between entities - and their elements may be represented in a directed graph with entity as root. - The levelsets are sets of entities which make up the elements appearing at - a certain level. - """ - if k <= 0: - return None - currentlevel = self.elements - if k > 1: - for idx in range(k - 1): - currentlevel = self._depth_finder(currentlevel) - return currentlevel
- -
[docs] def depth(self, max_depth=10): - """ - Returns the number of nonempty level sets of level <= max_depth - - Parameters - ---------- - max_depth : int, optional, default: 10 - If full depth is desired set max_depth to number of entities in - system + 1. - - Returns - ------- - depth : int - If max_depth is exceeded output will be numpy infinity. - If there is a cycle output will be numpy infinity. - - """ - if max_depth < 0: - return 0 - currentlevel = self.elements - if not currentlevel: - return 0 - else: - depth = 1 - while depth < max_depth + 1: - currentlevel = self._depth_finder(currentlevel) - if not currentlevel: - return depth - depth += 1 - return np.inf
- -
[docs] def fullregistry(self, lastlevel=10, firstlevel=1): - """ - A dictionary of all entities appearing in levels firstlevel - to lastlevel. - - Parameters - ---------- - lastlevel : int, optional, default: 10 - - firstlevel : int, optional, default: 1 - - Returns - ------- - fullregistry : dict - - """ - currentlevel = self.levelset(firstlevel) - accumulater = dict(currentlevel) - for idx in range(firstlevel, lastlevel): - currentlevel = self._depth_finder(currentlevel) - accumulater.update(currentlevel) - return accumulater
- -
[docs] def complete_registry(self): - """ - A dictionary of all entities appearing in any level of - entity - - Returns - ------- - complete_registry : dict - """ - results = dict() - Entity._complete_registry(self, results) - return results
- - @staticmethod - def _complete_registry(entity, results): - """ - Helper method for complete_registry - """ - for uid, e in entity.elements.items(): - if uid not in results: - results[uid] = e - Entity._complete_registry(e, results) - -
[docs] def nested_incidence_dict(self, level=10): - """ - Returns a nested dictionary with keys up to level - - Parameters - ---------- - level : int, optional, default: 10 - If level<=1, returns the incidence_dict. - - Returns - ------- - nested_incidence_dict : dict - - """ - if level > 1: - return {ent.uid: ent.nested_incidence_dict(level - 1) for ent in self()} - else: - return self.incidence_dict
- -
[docs] def size(self): - """ - Returns the number of elements in entity - """ - return len(self)
- -
[docs] def clone(self, newuid): - """ - Returns shallow copy of entity with newuid. Entity's elements will - belong to two distinct Entities. - - Parameters - ---------- - newuid : hashable - Name of the new entity - - Returns - ------- - clone : Entity - - """ - return Entity(newuid, entity=self)
- -
[docs] def intersection(self, other): - """ - A dictionary of elements belonging to entity and other. - - Parameters - ---------- - other : Entity - - Returns - ------- - Dictionary of elements : dict - - """ - return {e: self[e] for e in self if e in other}
- -
[docs] def restrict_to(self, element_subset, name=None): - """ - Shallow copy of entity removing elements not in element_subset. - - Parameters - ---------- - element_subset : iterable - A subset of entities elements - - name: hashable, optional - If not given, a name is generated to reflect entity uid - - Returns - ------- - New Entity : Entity - Could be empty. - - """ - newelements = [self[e] for e in element_subset if e in self] - name = name or f"{self.uid}_r" - return Entity(name, newelements, **self.properties)
- -
[docs] def add(self, *args): - """ - Adds unpacked args to entity elements. Depends on add_element() - - Parameters - ---------- - args : One or more entities or hashables - - Returns - ------- - self : Entity - - Note - ---- - Adding an element to an object in a hypergraph will not add the - element to the hypergraph and will cause an error. Use :func:`Hypergraph.add_edge <classes.hypergraph.Hypergraph.add_edge>` - or :func:`Hypergraph.add_node_to_edge <classes.hypergraph.Hypergraph.add_node_to_edge>` instead. - - """ - for item in args: - self.add_element(item) - - return self
- -
[docs] def add_elements_from(self, arg_set): - """ - Similar to :func:`add()` it allows for adding from an interable. - - Parameters - ---------- - arg_set : Iterable of hashables or entities - - Returns - ------- - self : Entity - - """ - for item in arg_set: - self.add_element(item) - - return self
- -
[docs] def add_element(self, item): - """ - Adds item to entity elements and adds entity to item.memberships. - - Parameters - ---------- - item : hashable or Entity - If hashable, will be replaced with empty Entity using hashable as uid - - Returns - ------- - self : Entity - - Notes - ----- - If item is in entity elements, no new element is added but properties - will be updated. - If item is in complete_registry(), only the item already known to self will be added. - This method employs the `Honor System`_ since membership in complete_registry is checked - using the item's uid. It is assumed that the user will only use the same uid - for identical instances within the entities registry. - - """ - checkelts = self.complete_registry() - if isinstance(item, Entity): - # if item is an Entity, descendents will be compared to avoid collisions - if item.uid == self.uid: - raise HyperNetXError( - f"Error: Self reference in submitted elements." - f" Entity {self.uid} may not contain itself. " - ) - elif item in self: - # item is already an element so only the properties will be updated - checkelts[item.uid].__dict__.update(item.properties) - elif item.uid in checkelts: - # if item belongs to an element or a descendent of an element - # then the existing descendent becomes an element - # and properties are updated. - checkelts[item.uid]._memberships[self.uid] = self - checkelts[item.uid].__dict__.update(item.properties) - self._elements[item.uid] = checkelts[item.uid] - else: - # if item's uid doesn't appear in complete_registry - # then it is added as something new - item._memberships[self.uid] = self - self._elements[item.uid] = item - else: - # item must be a hashable. - # if it appears as a uid in checkelts then - # the corresponding Entity will become an element of entity. - # Otherwise, at most it will be added as an empty Entity. - if self.uid == item: - raise HyperNetXError( - f"Error: Self reference in submitted elements." - f" Entity {self.uid} may not contain itself." - ) - elif item not in self._elements: - if item in checkelts: - self._elements[item] = checkelts[item] - checkelts[item]._memberships[self.uid] = self - else: - self._elements[item] = Entity(item, _memberships={self.uid: self}) - - return self
- -
[docs] def remove(self, *args): - """ - Removes args from entitie's elements if they belong. - Does nothing with args not in entity. - - Parameters - ---------- - args : One or more hashables or entities - - Returns - ------- - self : Entity - - - """ - for item in args: - Entity.remove_element(self, item) - return self
- -
[docs] def remove_elements_from(self, arg_set): - """ - Similar to :func:`remove()`. Removes elements in arg_set. - - Parameters - ---------- - arg_set : Iterable of hashables or entities - - Returns - ------- - self : Entity - - """ - for item in arg_set: - Entity.remove_element(self, item) - return self
- -
[docs] def remove_element(self, item): - """ - Removes item from entity and reference to entity from - item.memberships - - Parameters - ---------- - item : Hashable or Entity - - Returns - ------- - self : Entity - - - """ - if isinstance(item, Entity): - del item._memberships[self.uid] - del self._elements[item.uid] - else: - del self[item]._memberships[self.uid] - del self._elements[item] - - return self
- -
[docs] @staticmethod - def merge_entities(name, ent1, ent2): - """ - Merge two entities making sure they do not conflict. - - Parameters - ---------- - name : hashable - - ent1 : Entity - First entity to have elements and properties added to new - entity - - ent2 : Entity - elements of ent2 will be checked against ent1.complete_registry() - and only nonexisting elements will be added using add() method. - Properties of ent2 will update properties of ent1 in new entity. - - Returns - ------- - a new entity : Entity - - """ - newent = ent1.clone(name) - newent.add_elements_from(ent2.elements.values()) - for k, v in ent2.properties.items(): - newent.__setattr__(k, v) - return newent
- - -
[docs]class EntitySet(Entity): - """ - .. _entityset: - - Parameters - ---------- - uid : hashable - a unique identifier - - elements : list or dict, optional, default: None - a list of entities with identifiers different than uid and/or - hashables different than uid, see `Honor System`_ - - props : keyword arguments, optional, default: {} - properties belonging to the entity added as key=value pairs. - Both key and value must be hashable. - - Notes - ----- - The EntitySet class was created to distinguish Entities satifying the Bipartite Condition. - - .. _Bipartite Condition: - - **Bipartite Condition** - - *Entities that are elements of the same EntitySet, may not contain each other as elements.* - The elements and children of an EntitySet generate a specific partition for a bipartite graph. - The partition is isomorphic to a Hypergraph where the elements correspond to hyperedges and - the children correspond to the nodes. EntitySets are the basic objects used to construct hypergraphs - in HNX. - - Example: :: - - >>> y = Entity('y') - >>> x = Entity('x') - >>> x.add(y) - >>> y.add(x) - >>> w = EntitySet('w',[x,y]) - HyperNetXError: Error: Fails the Bipartite Condition for EntitySet. - y references a child of an existing Entity in the EntitySet. - - """ - - def __init__(self, uid, elements=[], **props): - super().__init__(uid, elements, **props) - if not self.is_bipartite: - raise HyperNetXError( - "Entity does not satisfy the Bipartite Condition, elements and children are not disjoint." - ) - - def __str__(self): - """Return the entityset uid.""" - return f"{self.uid}" - - def __repr__(self): - """Returns a string resembling the constructor for entityset without any - children""" - return f"EntitySet({self._uid},{list(self.uidset)},{self.properties})" - -
[docs] def add(self, *args): - """ - Adds args to entityset's elements, checking to make sure no self references are - made to element ids. - Ensures Bipartite Condition of EntitySet. - - Parameters - ---------- - args : One or more entities or hashables - - Returns - ------- - self : EntitySet - - """ - for item in args: - if isinstance(item, Entity): - if item.uid in self.children: - raise HyperNetXError( - f"Error: Fails the Bipartite Condition for EntitySet. {item.uid} references a child of an existing Entity in the EntitySet." - ) - elif not self.uidset.isdisjoint(item.uidset): - raise HyperNetXError( - f"Error: Fails the bipartite condition for EntitySet." - ) - else: - Entity.add_element(self, item) - else: - if not item in self.children: - Entity.add_element(self, item) - else: - raise HyperNetXError( - f"Error: {item} references a child of an existing Entity in the EntitySet." - ) - return self
- -
[docs] def clone(self, newuid): - """ - Returns shallow copy of entityset with newuid. Entityset's - elements will belong to two distinct entitysets. - - - Parameters - ---------- - newuid : hashable - Name of the new entityset - - Returns - ------- - clone : EntitySet - - """ - return EntitySet(newuid, elements=self.elements.values(), **self.properties)
- -
[docs] def collapse_identical_elements(self, newuid, return_equivalence_classes=False): - """ - Returns a deduped copy of the entityset, using representatives of equivalence classes as element keys. - Two elements of an EntitySet are collapsed if they share the same children. - - Parameters - ---------- - newuid : hashable - - return_equivalence_classes : boolean, default=False - If True, return a dictionary of equivalence classes keyed by new edge names - - Returns - ------- - : EntitySet - eq_classes : dict - if return_equivalence_classes = True - - Notes - ----- - Treats elements of the entityset as equal if they have the same uidsets. Using this - as an equivalence relation, the entityset's uidset is partitioned into equivalence classes. - The equivalent elements are identified using a single entity by using the - frozenset of uids associated to these elements as the uid for the new element - and dropping the properties. - If use_reps is set to True a representative element of the equivalence class is - used as identifier instead of the frozenset. - - Example: :: - - >>> E = EntitySet('E',elements=[Entity('E1', ['a','b']),Entity('E2',['a','b'])]) - >>> E.incidence_dict - {'E1': {'a', 'b'}, 'E2': {'a', 'b'}} - >>> E.collapse_identical_elements('_',).incidence_dict - {'E2': {'a', 'b'}} - - """ - - shared_children = defaultdict(set) - for e in self.__call__(): - shared_children[frozenset(e.uidset)].add(e.uid) - new_entity_dict = { - f"{next(iter(v))}:{len(v)}": set(k) for k, v in shared_children.items() - } - if return_equivalence_classes: - eq_classes = { - f"{next(iter(v))}:{len(v)}": v for k, v in shared_children.items() - } - return EntitySet(newuid, new_entity_dict), dict(eq_classes) - else: - return EntitySet(newuid, new_entity_dict)
- -
[docs] def incidence_matrix(self, sparse=True, index=False): - """ - An incidence matrix for the EntitySet indexed by children x uidset. - - Parameters - ---------- - sparse : boolean, optional, default: True - - index : boolean, optional, default False - If True return will include a dictionary of children uid : row number - and element uid : column number - - Returns - ------- - incidence_matrix : scipy.sparse.csr.csr_matrix or np.ndarray - - row dictionary : dict - Dictionary identifying row with item in entityset's children - - column dictionary : dict - Dictionary identifying column with item in entityset's uidset - - Notes - ----- - - Example: :: - - >>> E = EntitySet('E',{'a':{1,2,3},'b':{2,3},'c':{1,4}}) - >>> E.incidence_matrix(sparse=False, index=True) - (array([[0, 1, 1], - [1, 1, 0], - [1, 1, 0], - [0, 0, 1]]), {0: 1, 1: 2, 2: 3, 3: 4}, {0: 'b', 1: 'a', 2: 'c'}) - """ - if sparse: - from scipy.sparse import csr_matrix - - nchildren = len(self.children) - nuidset = len(self.uidset) - - ndict = dict(zip(self.children, range(nchildren))) - edict = dict(zip(self.uidset, range(nuidset))) - - if len(ndict) != 0: - - if index: - rowdict = {v: k for k, v in ndict.items()} - coldict = {v: k for k, v in edict.items()} - - if sparse: - # Create csr sparse matrix - rows = list() - cols = list() - data = list() - for e in self: - for n in self[e].elements: - data.append(1) - rows.append(ndict[n]) - cols.append(edict[e]) - MP = csr_matrix((data, (rows, cols))) - else: - # Create an np.matrix - MP = np.zeros((nchildren, nuidset), dtype=int) - for e in self: - for n in self[e].elements: - MP[ndict[n], edict[e]] = 1 - if index: - return MP, rowdict, coldict - else: - return MP - else: - if index: - return np.zeros(1), {}, {} - else: - return np.zeros(1)
- -
[docs] def restrict_to(self, element_subset, name=None): - """ - Shallow copy of entityset removing elements not in element_subset. - - Parameters - ---------- - element_subset : iterable - A subset of the entityset's elements - - name: hashable, optional - If not given, a name is generated to reflect entity uid - - Returns - ------- - new entityset : EntitySet - Could be empty. - - See also - -------- - Entity.restrict_to - - """ - newelements = [self[e] for e in element_subset if e in self] - name = name or f"{self.uid}_r" - return EntitySet(name, newelements, **self.properties)
-
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/classes/hypergraph.html b/docs/build/_modules/classes/hypergraph.html deleted file mode 100644 index 29e149a6..00000000 --- a/docs/build/_modules/classes/hypergraph.html +++ /dev/null @@ -1,2798 +0,0 @@ - - - - - - - - - - classes.hypergraph — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • classes.hypergraph
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for classes.hypergraph

-# Copyright © 2018 Battelle Memorial Institute
-# All rights reserved.
-
-import warnings
-import pickle
-import networkx as nx
-from networkx.algorithms import bipartite
-import numpy as np
-import pandas as pd
-from scipy.sparse import issparse, coo_matrix, dok_matrix, csr_matrix
-from collections import OrderedDict, defaultdict
-from hypernetx.classes.entity import Entity, EntitySet
-from hypernetx.classes.staticentity import StaticEntity, StaticEntitySet
-from hypernetx.exception import HyperNetXError
-from hypernetx.utils.decorators import not_implemented_for
-
-
-__all__ = ["Hypergraph"]
-
-
-
[docs]class Hypergraph: - """ - Hypergraph H = (V,E) references a pair of disjoint sets: - V = nodes (vertices) and E = (hyper)edges. - - An HNX Hypergraph is either dynamic or static. - Dynamic hypergraphs can change by adding or subtracting objects - from them. Static hypergraphs require that all of the nodes and edges - be known at creation. A hypergraph is dynamic by default. - - *Dynamic hypergraphs* require the user to keep track of its objects, - by using a unique names for each node and edge. This allows for multi-edge graphs and - inseperable nodes. - - For example: Let V = {1,2,3} and E = {e1,e2,e3}, - where e1 = {1,2}, e2 = {1,2}, and e3 = {1,2,3}. - The edges e1 and e2 contain the same set of nodes and yet - are distinct and must be distinguishable within H. - - In a dynamic hypergraph each node and edge is - instantiated as an Entity and given an identifier or uid. Entities - keep track of inclusion relationships and can be nested. Since - hypergraphs can be quite large, only the entity identifiers will be used - for computation intensive methods, this means the user must take care - to keep a one to one correspondence between their set of uids and - the objects in their hypergraph. See `Honor System`_ - Dynamic hypergraphs are most practical for small to modestly sized - hypergraphs (<1000 objects). - - *Static hypergraphs* store node and edge information in numpy arrays and - are immutable. Each node and edge receives a class generated internal - identifier used for computations so do not require the user to create - different ids for nodes and edges. To create a static hypergraph set - `static = True` in the signature. - - We will create hypergraphs in multiple ways: - - 1. As an empty instance: :: - - >>> H = hnx.Hypergraph() - >>> H.nodes, H.edges - ({}, {}) - - 2. From a dictionary of iterables (elements of iterables must be of type hypernetx.Entity or hashable): :: - - >>> H = Hypergraph({'a':[1,2,3],'b':[4,5,6]}) - >>> H.nodes, H.edges - # output: (EntitySet(_:Nodes,[1, 2, 3, 4, 5, 6],{}), EntitySet(_:Edges,['b', 'a'],{})) - - 3. From an iterable of iterables: (elements of iterables must be of type hypernetx.Entity or hashable): :: - - >>> H = Hypergraph([{'a','b'},{'b','c'},{'a','c','d'}]) - >>> H.nodes, H.edges - # output: (EntitySet(_:Nodes,['d', 'b', 'c', 'a'],{}), EntitySet(_:Edges,['_1', '_2', '_0'],{})) - - 4. From a hypernetx.EntitySet or StaticEntitySet: :: - - >>> a = Entity('a',{1,2}); b = Entity('b',{2,3}) - >>> E = EntitySet('sample',elements=[a,b]) - >>> H = Hypergraph(E) - >>> H.nodes, H.edges. - # output: (EntitySet(_:Nodes,[1, 2, 3],{}), EntitySet(_:Edges,['b', 'a'],{})) - - All of these constructions apply for both dynamic and static hypergraphs. To - create a static hypergraph set the parameter `static=True`. In addition a static - hypergraph is automatically created if a StaticEntity, StaticEntitySet, or pandas.DataFrame object - is passed to the Hypergraph constructor. - - 5. | From a pandas.DataFrame. The dataframe must have at least two columns with headers and there can be no nans. - | By default the first column corresponds to the edge names and the second column to the node names. - | You can specify the columns by restricting the dataframe to the columns of interest in the order: - | :code:`hnx.Hypergraph(df[[edge_column_name,node_column_name]])` - | See :ref:`Colab Tutorials <colab>` Tutorial 6 - Static Hypergraphs and Entities for additional information. - - - Parameters - ---------- - setsystem : (optional) EntitySet, StaticEntitySet, dict, iterable, pandas.dataframe, default: None - See notes above for setsystem requirements. - - name : hashable, optional, default: None - If None then a placeholder '_' will be inserted as name - - static : boolean, optional, default: False - If True the hypergraph will be immutable, edges and nodes may not be changed. - - use_nwhy : boolean, optional, default = False - If True hypergraph will be static and computations will be done using - C++ backend offered by NWHypergraph. This requires installation of the - NWHypergraph C++ library. Please see the :ref:`NWHy documentation <nwhy>` for more information. - - - """ - - # TODO: remove lambda functions from constructor in H and E. - - def __init__( - self, setsystem=None, name=None, static=False, use_nwhy=False, filepath=None - ): - self.filepath = filepath - if use_nwhy: - static = True - try: - import nwhy - - self.nwhy = True - - except: - self.nwhy = False - print("NWHypergraph is not available. Will continue with static=True.") - use_nwhy = False - else: - self.nwhy = False - if not name: - self.name = "" - else: - self.name = name - - if static == True or ( - isinstance(setsystem, StaticEntitySet) or - isinstance(setsystem, StaticEntity) or - isinstance(setsystem, pd.DataFrame) ) : - self._static=True - if setsystem is None: - self._edges=StaticEntitySet() - self._nodes=StaticEntitySet() - else: - E=StaticEntitySet(entity = setsystem) - self._edges=E - self._nodes=E.restrict_to_levels([1]) - else: - self._static=False - if setsystem is None: - setsystem=EntitySet("_", elements = []) - elif isinstance(setsystem, Entity): - setsystem=EntitySet("_", setsystem.incidence_dict) - elif isinstance(setsystem, dict): - # Must be a dictionary with values equal to iterables of Entities and hashables. - # Keys will be uids for new edges and values of the dictionary will generate the nodes. - setsystem=EntitySet("_", setsystem) - elif not isinstance(setsystem, EntitySet): - # If no ids are given, return default ids indexed by position in iterator - # This should be an iterable of sets - edge_labels=[self.name + str(x) for x in range(len(setsystem))] - setsystem=EntitySet("_", dict(zip(edge_labels, setsystem))) - - _reg=setsystem.registry - _nodes={k: Entity(k, **_reg[k].properties) for k in _reg} - _elements={j: {k: _nodes[k] for k in setsystem[j]} for j in setsystem} - _edges={ - j: Entity(j, elements=_elements[j].values(), **setsystem[j].properties) - for j in setsystem - } - - self._edges = EntitySet( - f"{self.name}:Edges", elements=_edges.values(), **setsystem.properties - ) - self._nodes = EntitySet(f"{self.name}:Nodes", elements=_nodes.values()) - if self._static: - temprows, tempcols = self.edges.data.T - tempdata = np.ones(len(temprows), dtype=int) - self.state_dict = { - "data": (temprows, tempcols, tempdata) - } # how can we incorporate the counts into the nwhy hypergraph? - if self.nwhy: - self.g = nwhy.NWHypergraph(*self.state_dict["data"]) - self.nwhy_dict = {"snodelg": dict(), "sedgelg": dict()} - self.state_dict["snodelg"] = dict() - self.state_dict["sedgelg"] = dict() - if self.filepath is not None: - self.save_state(fpath=self.filepath) - - @property - def edges(self): - """ - Object associated with self._edges. - - Returns - ------- - StaticEntitySet or EntitySet - If self.isstatic the StaticEntitySet, otherwise EntitySet. - """ - return self._edges - - @property - def nodes(self): - """ - Object associated with self._nodes. - - Returns - ------- - StaticEntitySet or EntitySet - If self.isstatic the StaticEntitySet, otherwise EntitySet. - - """ - return self._nodes - - @property - def isstatic(self): - """ - Checks whether nodes and edges are immutable - - Returns - ------- - Boolean - - """ - return self._static - - @property - def incidence_dict(self): - """ - Dictionary keyed by edge uids with values the uids of nodes in each edge - - Returns - ------- - dict - - """ - return self._edges.incidence_dict - - @property - def shape(self): - """ - (number of nodes, number of edges) - - Returns - ------- - tuple - - """ - if self.nwhy: - return (self.g.number_of_nodes(), self.g.number_of_edges()) - else: - return (len(self._nodes.elements), len(self._edges.elements)) - - def __str__(self): - """ - String representation of hypergraph - - Returns - ------- - str - - """ - return f"Hypergraph({self.edges.elements},name={self.name})" - - def __repr__(self): - """ - String representation of hypergraph - - Returns - ------- - str - - """ - return f"Hypergraph({self.edges.elements},name={self.name})" - - def __len__(self): - """ - Number of nodes - - Returns - ------- - int - - """ - if self.nwhy: - return self.g.number_of_nodes() - else: - return len(self._nodes) - - def __iter__(self): - """ - Iterate over the nodes of the hypergraph - - Returns - ------- - dict_keyiterator - - """ - return iter(self.nodes) - - def __contains__(self, item): - """ - Returns boolean indicating if item is in self.nodes - - Parameters - ---------- - item : hashable or Entity - - """ - if isinstance(item, Entity): - return item.uid in self.nodes - else: - return item in self.nodes - - def __getitem__(self, node): - """ - Returns the neighbors of node - - Parameters - ---------- - node : Entity or hashable - If hashable, then must be uid of node in hypergraph - - Returns - ------- - neighbors(node) : iterator - - """ - return self.neighbors(node) - -
[docs] @not_implemented_for("dynamic") - def get_id(self, uid, edges=False): - """ - Return the internally assigned id associated with a label. - - Parameters - ---------- - uid : string - User provided name/id/label for hypergraph object - edges : bool, optional - Determines if uid is an edge or node name - - Returns - ------- - : int - internal id assigned at construction - """ - kdx = (edges + 1) % 2 - return int(np.argwhere(self.edges.labs(kdx) == uid)[0])
- -
[docs] @not_implemented_for("dynamic") - def get_name(self, id, edges=False): - """ - Return the user defined name/id/label associated to an - internally assigned id. - - Parameters - ---------- - id : int - Internally assigned id - edges : bool, optional - Determines if id references an edge or node - - Returns - ------- - str - User provided name/id/label for hypergraph object - """ - kdx = (edges + 1) % 2 - return self.edges.labs(kdx)[id]
- -
[docs] @not_implemented_for("dynamic") - def get_linegraph(self, s, edges=True, use_nwhy=True): - """ - Creates an ::term::s-linegraph for the Hypergraph. - If edges=True (default)then the edges will be the vertices of the line graph. - Two vertices are connected by an s-line-graph edge if the corresponding - hypergraphedges intersect in at least s hypergraph nodes. - If edges=False, the hypergraph nodes will be the vertices of the line graph. - Two vertices are connected if the nodes they correspond to share at least s - incident hyper edges. - - Parameters - ---------- - s : int - The width of the connections. - edges : bool, optional - Determine if edges or nodes will be the vertices in the linegraph. - use_nwhy : bool, optional - Requests that nwhy be used to construct the linegraph. If NWHy is not available this is ignored. - - Returns - ------- - nx.Graph - A NetworkX graph. - """ - if use_nwhy and self.nwhy: - d = self.nwhy_dict - else: - d = self.state_dict - key = "sedgelg" if edges else "snodelg" - if s in d[key]: - return d[key][s] - else: - if use_nwhy and self.nwhy: - d[key][s] = self.g.s_linegraph(s=s, edges=edges) - else: - if edges: - A = self.edge_adjacency_matrix(s=s) - else: - A = self.adjacency_matrix(s=s) - d[key][s] = nx.from_scipy_sparse_matrix(A) - if self.filepath is not None: - self.save_state(fpath=self.filepath) - return d[key][s]
- -
[docs] @not_implemented_for("dynamic") - def set_state(self, **kwargs): - """ - Allow state_dict updates from outside of class. Use with caution. - - Parameters - ---------- - **kwargs - key=value pairs to save in state dictionary - """ - self.state_dict.update(kwargs) - if self.filepath is not None: - self.save_state(fpath=self.filepath)
- -
[docs] @not_implemented_for("dynamic") - def save_state(self, fpath=None): - """ - Save the hypergraph as an ordered pair: [state_dict,labels] - The hypergraph can be recovered using the command: - - >>> H = hnx.Hypergraph.recover_from_state(fpath) - - Parameters - ---------- - fpath : str, optional - """ - if fpath is None: - fpath = self.filepath or "current_state.p" - pickle.dump([self.state_dict, self.edges.labels], open(fpath, "wb"))
- -
[docs] @classmethod - def recover_from_state(cls, fpath="current_state.p", newfpath=None, use_nwhy=True): - """ - Recover a static hypergraph pickled using save_state. - - Parameters - ---------- - fpath : str - Full path to pickle file containing state_dict and labels - of hypergraph - - Returns - ------- - H : Hypergraph - static hypergraph with state dictionary prefilled - """ - temp, labels = pickle.load(open(fpath, "rb")) - recovered_data = np.array(temp["data"])[[0, 1]].T # need to save counts as well - recovered_counts = np.array(temp["data"])[ - [2] - ] # ammend this to store cell weights - E = StaticEntitySet(data=recovered_data, labels=labels) - E.properties["counts"] = recovered_counts - H = Hypergraph(E, use_nwhy=use_nwhy) - H.state_dict.update(temp) - if newfpath == "same": - newfpath = fpath - if newfpath is not None: - H.filepath = newfpath - H.save_state() - return H
- -
[docs] @classmethod - def add_nwhy(cls, h, fpath=None): - """ - Add nwhy functionality to a hypergraph. - - Parameters - ---------- - h : hnx.Hypergraph - fpath : file path for storage of hypergraph state dictionary - - Returns - ------- - hnx.Hypergraph - Returns a copy of h with static set to true and nwhy set to True - if it is available. - - """ - - if h.isstatic: - sd = h.state_dict - H = Hypergraph(h.edges, use_nwhy=True, filepath=fpath) - H.state_dict.update(sd) - return H - else: - return Hypergraph(StaticEntitySet(h.edges), use_nwhy=True, filepath=fpath)
- -
[docs] def edge_size_dist(self): - """ - Returns the size for each edge - - Returns - ------- - np.array - - """ - if self.isstatic: - dist = self.state_dict.get("edge_size_dist", None) - if dist: - return dist - else: - if self.nwhy: - dist = self.g.edge_size_dist() - else: - dist = list(np.array(np.sum(self.incidence_matrix(), axis=0))[0]) - - self.set_state(edge_size_dist=dist) - return dist - else: - return list(np.array(np.sum(self.incidence_matrix(), axis=0))[0])
- -
[docs] def convert_to_static( - self, - name=None, - nodes_name="nodes", - edges_name="edges", - use_nwhy=False, - filepath=None, - ): - """ - Returns new static hypergraph with the same dictionary as original hypergraph - - Parameters - ---------- - name : None, optional - Name - nodes_name : str, optional - name for list of node labels - edges_name : str, optional - name for list of edge labels - - Returns - ------- - hnx.Hypergraph - Will have attribute static = True - - Note - ---- - Static hypergraphs store the user defined node and edge names in - a dictionary of labeled lists. The order of the lists provides an - index, which the hypergraph uses in place of the node and edge names - for fast processing. - """ - arr, cdict, rdict = self.edges.incidence_matrix(index=True) - labels = OrderedDict( - [ - (edges_name, [cdict[k] for k in range(len(cdict))]), - (nodes_name, [rdict[k] for k in range(len(rdict))]), - ] - ) - E = StaticEntity(arr=arr.T, labels=labels) - return Hypergraph(setsystem=E, name=name)
- -
[docs] def remove_static(self, name=None): - """ - Returns dynamic hypergraph - - Parameters - ---------- - name : None, optional - User defined namae of hypergraph - - Returns - ------- - hnx.Hypergraph - A new hypergraph with the same dictionary as self but allowing dynamic - changes to nodes and edges. - If hypergraph is not static, returns self. - """ - if not self.isstatic: - return self - else: - return Hypergraph(self.edges.incidence_dict, name=name)
- -
[docs] def translate(self, idx, edges=False): - """ - Returns the translation of numeric values associated with hypergraph. - Only needed if exposing the static identifiers assigned by the class. - If not static then the idx is returned. - - Parameters - ---------- - idx : int - class assigned integer for internal manipulation of Hypergraph data - edges : bool, optional, default: True - If True then translates from edge index. Otherwise will translate from - node index, default=False - - Returns - ------- - : int or string - User assigned identifier corresponding to idx - """ - if self.isstatic: - return self.get_name(idx, edges=edges) - else: - return idx
- -
[docs] def s_degree(self, node, s=1): # deprecate this to degree - """ - Same as `degree` - - Parameters - ---------- - node : Entity or hashable - If hashable, then must be uid of node in hypergraph - - s : positive integer, optional, default: 1 - - Returns - ------- - s_degree : int - The degree of a node in the subgraph induced by edges - of size s - - Note - ---- - The :term:`s-degree` of a node is the number of edges of size - at least s that contain the node. - - """ - msg = ( - "s-degree is deprecated and will be removed in" - " release 1.0.0. Use degree(node,s=int) instead." - ) - - warnings.warn(msg, DeprecationWarning) - return self.degree(node, s)
- -
[docs] def degree(self, node, s=1, max_size=None): - """ - The number of edges of size s that contain node. - - Parameters - ---------- - node : hashable - identifier for the node. - s : positive integer, optional, default: 1 - smallest size of edge to consider in degree - max_size : positive integer or None, optional, default: None - largest size of edge to consider in degree - - Returns - ------- - : int - - """ - if self.isstatic: - ndx = self.get_id(node) - # if s == 1: - # return np.sum(self.edges.data.T[1] == ndx) - if self.nwhy: - return self.g.degree(ndx, min_size=s, max_size=None) - - else: - if max_size is not None: - ids = np.where( - np.array(self.edge_size_dist()) in range(s, max_size + 1) - )[0] - else: - ids = np.where(np.array(self.edge_size_dist()) >= s)[0] - imat = self.incidence_matrix() - return np.sum(imat[ndx, ids]) - else: - memberships = set(self.nodes[node].memberships) - if max_size is not None: - return len( - set( - e - for e in memberships - if len(self.edges[e]) in range(s, max_size + 1) - ) - ) - elif s > 1: - return len(set(e for e in memberships if len(self.edges[e]) >= s)) - else: - return len(memberships)
- -
[docs] def size(self, edge): - """ - The number of nodes that belong to edge. - - Parameters - ---------- - edge : hashable - The uid of an edge in the hypergraph - - Returns - ------- - size : int - - """ - if self.isstatic: - edx = self.get_id(edge, edges=True) - esd = self.state_dict.get("edge_size_dist", None) - if esd is not None: - return esd[edx] - else: - if self.nwhy: - return self.g.size(edx) - else: - return np.sum(self.edges.data.T[0] == edx) - else: - return len(self.edges[edge])
- -
[docs] def number_of_nodes(self, nodeset=None): - """ - The number of nodes in nodeset belonging to hypergraph. - - Parameters - ---------- - nodeset : an interable of Entities, optional, default: None - If None, then return the number of nodes in hypergraph. - - Returns - ------- - number_of_nodes : int - - """ - if nodeset: - return len([n for n in self.nodes if n in nodeset]) - else: - if self.nwhy == True: - return self.g.number_of_nodes() - else: - return len(self.nodes)
- -
[docs] def number_of_edges(self, edgeset=None): - """ - The number of edges in edgeset belonging to hypergraph. - - Parameters - ---------- - edgeset : an interable of Entities, optional, default: None - If None, then return the number of edges in hypergraph. - - Returns - ------- - number_of_edges : int - """ - if edgeset: - return len([e for e in self.edges if e in edgeset]) - else: - if self.nwhy == True: - return self.g.number_of_edges() - else: - return len(self.edges)
- -
[docs] def order(self): - """ - The number of nodes in hypergraph. - - Returns - ------- - order : int - """ - if self.nwhy: - return self.g.number_of_nodes() - else: - return len(self.nodes)
- -
[docs] def dim(self, edge): - """ - Same as size(edge)-1. - """ - return self.size(edge) - 1
- -
[docs] def neighbors(self, node, s=1): - """ - The nodes in hypergraph which share s edge(s) with node. - - Parameters - ---------- - node : hashable or Entity - uid for a node in hypergraph or the node Entity - - s : int, list, optional, default : 1 - Minimum number of edges shared by neighbors with node. - - Returns - ------- - : list - List of neighbors - - """ - if not node in self.nodes: - print(f"Node is not in hypergraph {self.name}.") - return - - if self.isstatic: - g = self.get_linegraph(s=s, edges=False) - ndx = self.get_id(node) - if self.nwhy == True: - nbrs = g.s_neighbors(ndx) - else: - nbrs = list(g.neighbors(ndx)) - return [self.translate(nb, edges=False) for nb in nbrs] - - else: - node = self.nodes[ - node - ].uid # this allows node to be an Entity instead of a string - memberships = set(self.nodes[node].memberships).intersection( - self.edges.uidset - ) - edgeset = {e for e in memberships if len(self.edges[e]) >= s} - - neighborlist = set() - for e in edgeset: - neighborlist.update(self.edges[e].uidset) - neighborlist.discard(node) - return list(neighborlist)
- -
[docs] def edge_neighbors(self, edge, s=1): - """ - The edges in hypergraph which share s nodes(s) with edge. - - Parameters - ---------- - edge : hashable or Entity - uid for a edge in hypergraph or the edge Entity - - s : int, list, optional, default : 1 - Minimum number of nodes shared by neighbors edge node. - - Returns - ------- - : list - List of edge neighbors - - """ - if not edge in self.edges: - print(f"Edge is not in hypergraph {self.name}.") - return - - if self.isstatic: - g = self.get_linegraph(s=s, edges=True) - edx = self.get_id(edge, edges=True) - if self.nwhy == True: - nbrs = g.s_neighbors(edx) - else: - nbrs = list(g.neighbors(edx)) - return [self.translate(nb, edges=True) for nb in nbrs] - - else: - node = self.edges[edge].uid - return self.dual().neighbors(node, s=s)
- -
[docs] @not_implemented_for("static") - def remove_node(self, node): - """ - Removes node from edges and deletes reference in hypergraph nodes - - Parameters - ---------- - node : hashable or Entity - a node in hypergraph - - Returns - ------- - hypergraph : Hypergraph - - """ - if not node in self._nodes: - return self - else: - if not isinstance(node, Entity): - node = self._nodes[node] - for edge in node.memberships: - self._edges[edge].remove(node) - self._nodes.remove(node) - return self
- -
[docs] @not_implemented_for("static") - def remove_nodes(self, node_set): - """ - Removes nodes from edges and deletes references in hypergraph nodes - - Parameters - ---------- - node_set : an iterable of hashables or Entities - Nodes in hypergraph - - Returns - ------- - hypergraph : Hypergraph - - """ - for node in node_set: - self.remove_node(node) - return self
- - @not_implemented_for("static") - def _add_nodes_from(self, nodes): - """ - Private helper method instantiates new nodes when edges added to hypergraph. - - Parameters - ---------- - nodes : iterable of hashables or Entities - - """ - for node in nodes: - if node in self._edges: - raise HyperNetXError("Node already an edge.") - elif node in self._nodes and isinstance(node, Entity): - self._nodes[node].__dict__.update(node.properties) - elif node not in self._nodes: - if isinstance(node, Entity): - self._nodes.add(Entity(node.uid, **node.properties)) - else: - self._nodes.add(Entity(node)) - -
[docs] @not_implemented_for("static") - def add_edge(self, edge): - """ - - Adds a single edge to hypergraph. - - Parameters - ---------- - edge : hashable or Entity - If hashable the edge returned will be empty. - - Returns - ------- - hypergraph : Hypergraph - - Notes - ----- - When adding an edge to a hypergraph children must be removed - so that nodes do not have elements. - Each node (element of edge) must be instantiated as a node, - making sure its uid isn't already present in the self. - If an added edge contains nodes that cannot be added to hypergraph - then an error will be raised. - - """ - if edge in self._edges: - warnings.warn("Cannot add edge. Edge already in hypergraph") - elif edge in self._nodes: - warnings.warn("Cannot add edge. Edge is already a Node") - elif isinstance(edge, Entity): - if len(edge) > 0: - self._add_nodes_from(edge.elements.values()) - self._edges.add( - Entity( - edge.uid, - elements=[self._nodes[k] for k in edge], - **edge.properties, - ) - ) - for n in edge.elements: - self._nodes[n].memberships[edge.uid] = self._edges[edge.uid] - else: - self._edges.add(Entity(edge.uid, **edge.properties)) - else: - self._edges.add(Entity(edge)) # this generates an empty edge - return self
- -
[docs] @not_implemented_for("static") - def add_edges_from(self, edge_set): - """ - Add edges to hypergraph. - - Parameters - ---------- - edge_set : iterable of hashables or Entities - For hashables the edges returned will be empty. - - Returns - ------- - hypergraph : Hypergraph - - """ - for edge in edge_set: - self.add_edge(edge) - return self
- -
[docs] @not_implemented_for("static") - def add_node_to_edge(self, node, edge): - """ - - Adds node to an edge in hypergraph edges - - Parameters - ---------- - node: hashable or Entity - If Entity, only uid and properties will be used. - If uid is already in nodes then the known node will - be used - - edge: uid of edge or edge, must belong to self.edges - - Returns - ------- - hypergraph : Hypergraph - - """ - if edge in self._edges: - if not isinstance(edge, Entity): - edge = self._edges[edge] - if node in self._nodes: - self._edges[edge].add(self._nodes[node]) - else: - if not isinstance(node, Entity): - node = Entity(node) - else: - node = Entity(node.uid, **node.properties) - self._edges[edge].add(node) - self._nodes.add(node) - - return self
- -
[docs] @not_implemented_for("static") - def remove_edge(self, edge): - """ - Removes a single edge from hypergraph. - - Parameters - ---------- - edge : hashable or Entity - - Returns - ------- - hypergraph : Hypergraph - - Notes - ----- - - Deletes reference to edge from all of its nodes. - If any of its nodes do not belong to any other edges - the node is dropped from self. - - """ - if edge in self._edges: - if not isinstance(edge, Entity): - edge = self._edges[edge] - for node in edge.uidset: - edge.remove(node) - if len(self._nodes[node]._memberships) == 1: - self._nodes.remove(node) - self._edges.remove(edge) - return self
- -
[docs] @not_implemented_for("static") - def remove_edges(self, edge_set): - """ - Removes edges from hypergraph. - - Parameters - ---------- - edge_set : iterable of hashables or Entities - - Returns - ------- - hypergraph : Hypergraph - - """ - for edge in edge_set: - self.remove_edge(edge) - return self
- -
[docs] def incidence_matrix(self, index=False): - """ - An incidence matrix for the hypergraph indexed by nodes x edges. - - Parameters - ---------- - index : boolean, optional, default False - If True return will include a dictionary of node uid : row number - and edge uid : column number - - Returns - ------- - incidence_matrix : scipy.sparse.csr.csr_matrix or np.ndarray - - row dictionary : dict - Dictionary identifying rows with nodes - - column dictionary : dict - Dictionary identifying columns with edges - - """ - if self.isstatic: - mat = self.state_dict.get("incidence_matrix", None) - if mat is None: - mat = self.edges.incidence_matrix() - self.state_dict["incidence_matrix"] = mat - if index: - rdict = dict(enumerate(self.edges.labs(1))) - cdict = dict(enumerate(self.edges.labs(0))) - return mat, rdict, cdict - else: - return mat - - else: - return self.edges.incidence_matrix(index=index)
- -
[docs] @staticmethod - def incidence_to_adjacency(M, s=1, weighted=True): - """ - Helper method to obtain adjacency matrix from incidence matrix. - - Parameters - ---------- - M : scipy.sparse.csr.csr_matrix - - s : int, optional, default: 1 - - weighted : boolean, optional, default: True - - Returns - ------- - a matrix : scipy.sparse.csr.csr_matrix - - """ - A = M.dot(M.transpose()) - if issparse(A): - A.setdiag(0) - B = (A >= s) * 1 - A = A.multiply(B) - else: - np.fill_diagonal(A, 0) - B = (A >= s) * 1 - A = np.multiply(A, B) - - if not weighted: - A = (A > 0) * 1 - return csr_matrix(A)
- -
[docs] def adjacency_matrix(self, index=False, s=1, weighted=True): - """ - The sparse weighted :term:`s-adjacency matrix` - - Parameters - ---------- - s : int, optional, default: 1 - - index: boolean, optional, default: False - if True, will return a rowdict of row to node uid - - weighted: boolean, optional, default: True - - - Returns - ------- - adjacency_matrix : scipy.sparse.csr.csr_matrix - - row dictionary : dict - - Notes - ----- - If weighted is True each off diagonal cell will equal the number - of edges shared by the nodes indexing the row and column if that number is - greater than s, otherwise the cell will equal 0. If weighted is False, the off - diagonal cell will equal 1 if the nodes indexed by the row and column share at - least s edges and 0 otherwise. - - """ - M = self.incidence_matrix(index=index) - if index: - return Hypergraph.incidence_to_adjacency(M[0], s=s, weighted=weighted), M[1] - else: - return Hypergraph.incidence_to_adjacency(M, s=s, weighted=weighted)
- -
[docs] def edge_adjacency_matrix(self, index=False, s=1, weighted=True): - """ - The weighted :term:`s-adjacency matrix` for the dual hypergraph. - - Parameters - ---------- - s : int, optional, default: 1 - - index: boolean, optional, default: False - if True, will return a coldict of column to edge uid - - sparse: boolean, optional, default: True - - weighted: boolean, optional, default: True - - Returns - ------- - edge_adjacency_matrix : scipy.sparse.csr.csr_matrix or numpy.ndarray - - column dictionary : dict - - Notes - ----- - This is also the adjacency matrix for the line graph. - Two edges are s-adjacent if they share at least s nodes. - If index=True, returns a dictionary column_index:edge_uid - - """ - M = self.incidence_matrix(index=index) - if index: - return ( - Hypergraph.incidence_to_adjacency( - M[0].transpose(), s=s, weighted=weighted - ), - M[2], - ) - else: - return Hypergraph.incidence_to_adjacency( - M.transpose(), s=s, weighted=weighted - )
- -
[docs] def auxiliary_matrix(self, s=1, index=False): - """ - The unweighted :term:`s-auxiliary matrix` for hypergraph - - Parameters - ---------- - s : int - index : bool, optional, default: False - return a dictionary of labels for the rows of the matrix - - - Returns - ------- - auxiliary_matrix : scipy.sparse.csr.csr_matrix or numpy.ndarray - Will return the same type of matrix as self.arr - - Notes - ----- - Creates subgraph by restricting to edges of cardinality at least s. - Returns the unweighted s-edge adjacency matrix for the subgraph. - - """ - - edges = [e for e in self.edges if len(self.edges[e]) >= s] - H = self.restrict_to_edges(edges) - return H.edge_adjacency_matrix(s=s, index=index, weighted=False)
- -
[docs] def bipartite(self): - """ - Constructs the networkX bipartite graph associated to hypergraph. - - Returns - ------- - bipartite : nx.Graph() - - Notes - ----- - Creates a bipartite networkx graph from hypergraph. - The nodes and (hyper)edges of hypergraph become the nodes of bipartite graph. - For every (hyper)edge e in the hypergraph and node n in e there is an edge (n,e) - in the graph. - - """ - B = nx.Graph() - E = self.edges - V = self.nodes - B.add_nodes_from(E, bipartite=1) - B.add_nodes_from(V, bipartite=0) - B.add_edges_from([(v, e) for e in E for v in self.edges[e]]) - return B
- -
[docs] def dual(self, name=None): - """ - Constructs a new hypergraph with roles of edges and nodes of hypergraph reversed. - - Parameters - ---------- - name : hashable - - Returns - ------- - dual : hypergraph - """ - if self.isstatic: - E = self.edges.restrict_to_levels((1, 0)) - return Hypergraph(E, name=name, use_nwhy=self.nwhy) - else: - E = defaultdict(list) - for k, v in self.edges.incidence_dict.items(): - for n in v: - E[n].append(k) - return Hypergraph(E, name=name)
- - def _collapse_nwhy(self, edges, rec): - """ - Helper method for collapsing nodes and edges when hypergraph - is static and using nwhy - - Parameters - ---------- - edges : bool - Collapse the edges if True, otherwise the nodes - rec : bool - return the equivalence classes - """ - - if edges: - d = self.g.collapse_edges(return_equivalence_class=rec) - else: - d = self.g.collapse_nodes(return_equivalence_class=rec) - - if rec: - en = { - self.get_name( - k, edges=edges - ): f"{self.get_name(k,edges=edges)}:{len(v)}" - for k, v in d.items() - } - ec = { - f"{self.get_name(k,edges=edges)}:{len(v)}": { - self.get_name(vd, edges=edges) for vd in v - } - for k, v in d.items() - } - else: - en = { - self.get_name( - k, edges=edges - ): f"{self.get_name(k,edges=edges)}:{v.pop()}" - for k, v in d.items() - } - ec = {} - lev = self.edges.keys[1-1*edges] - E = self.edges.restrict_to_indices(sorted(d.keys()), level=1-1*edges) - E.labels[str(lev)] = np.array([en[k] for k in E.labels[lev]]) - if rec: - return E, ec - else: - return E - -
[docs] def collapse_edges( - self, - name=None, - use_reps=None, - return_counts=None, - return_equivalence_classes=False, - ): - """ - Constructs a new hypergraph gotten by identifying edges containing the same nodes - - Parameters - ---------- - name : hashable, optional, default: None - - return_equivalence_classes: boolean, optional, default: False - Returns a dictionary of edge equivalence classes keyed by frozen sets of nodes - - Returns - ------- - new hypergraph : Hypergraph - Equivalent edges are collapsed to a single edge named by a representative of the equivalent - edges followed by a colon and the number of edges it represents. - - equivalence_classes : dict - A dictionary keyed by representative edge names with values equal to the edges in - its equivalence class - - Notes - ----- - Two edges are identified if their respective elements are the same. - Using this as an equivalence relation, the uids of the edges are partitioned into - equivalence classes. - - A single edge from the collapsed edges followed by a colon and the number of elements - in its equivalence class as uid for the new edge - - - """ - if use_reps is not None or return_counts is not None: - msg = """ - use_reps ane return_counts are no longer supported keyword arguments and will throw - an error in the next release. - collapsed hypergraph automatically names collapsed objects by a string "rep:count" - """ - warnings.warn(msg, DeprecationWarning) - - if self.nwhy: - temp = self._collapse_nwhy(True, return_equivalence_classes) - else: - temp = self.edges.collapse_identical_elements( - "_", return_equivalence_classes=return_equivalence_classes - ) - if return_equivalence_classes: - return Hypergraph(temp[0], name, use_nwhy=self.nwhy), temp[1] - else: - return Hypergraph(temp, name, use_nwhy=self.nwhy)
- -
[docs] def collapse_nodes( - self, - name=None, - use_reps=True, - return_counts=True, - return_equivalence_classes=False, - ): - """ - Constructs a new hypergraph gotten by identifying nodes contained by the same edges - - Parameters - ---------- - name: str, optional, default: None - - return_equivalence_classes: boolean, optional, default: False - Returns a dictionary of node equivalence classes keyed by frozen sets of edges - - use_reps : boolean, optional, default: False - Deprecated, this no longer works and will be removed - Choose a single element from the collapsed nodes as uid for the new node, otherwise uses - a frozen set of the uids of nodes in the equivalence class - - return_counts: boolean, - Deprecated, this no longer works and will be removed - if use_reps is True the new nodes have uids given by a tuple of the rep - and the count - - Returns - ------- - new hypergraph : Hypergraph - - Notes - ----- - Two nodes are identified if their respective memberships are the same. - Using this as an equivalence relation, the uids of the nodes are partitioned into - equivalence classes. A single member of the equivalence class is chosen to represent - the class followed by the number of members of the class. - - Example - ------- - - >>> h = Hypergraph(EntitySet('example',elements=[Entity('E1', ['a','b']),Entity('E2',['a','b'])])) - >>> h.incidence_dict - {'E1': {'a', 'b'}, 'E2': {'a', 'b'}} - >>> h.collapse_nodes().incidence_dict - {'E1': {frozenset({'a', 'b'})}, 'E2': {frozenset({'a', 'b'})}} ### Fix this - >>> h.collapse_nodes(use_reps=True).incidence_dict - {'E1': {('a', 2)}, 'E2': {('a', 2)}} - - """ - if use_reps is not None or return_counts is not None: - msg = """ - use_reps ane return_counts are no longer supported keyword arguments and will throw - an error in the next release. - collapsed hypergraph automatically names collapsed objects by a string "rep:count" - """ - warnings.warn(msg, DeprecationWarning) - - if self.nwhy: - temp = self._collapse_nwhy(False, return_equivalence_classes) - if return_equivalence_classes: - return Hypergraph(temp[0], name, use_nwhy=self.nwhy), temp[1] - else: - return Hypergraph(temp, name, use_nwhy=self.nwhy) - else: - temp = self.dual().edges.collapse_identical_elements( - "_", return_equivalence_classes=return_equivalence_classes - ) - - if return_equivalence_classes: - return Hypergraph(temp[0], name, use_nwhy=self.nwhy).dual(), temp[1] - else: - return Hypergraph(temp, name, use_nwhy=self.nwhy).dual()
- -
[docs] def collapse_nodes_and_edges( - self, - name=None, - use_reps=True, - return_counts=True, - return_equivalence_classes=False, - ): - """ - Returns a new hypergraph by collapsing nodes and edges. - - Parameters - ---------- - - name: str, optional, default: None - - use_reps: boolean, optional, default: False - Choose a single element from the collapsed elements as a representative - - return_counts: boolean, optional, default: True - if use_reps is True the new elements are keyed by a tuple of the rep - and the count - - return_equivalence_classes: boolean, optional, default: False - Returns a dictionary of edge equivalence classes keyed by frozen sets of nodes - - Returns - ------- - new hypergraph : Hypergraph - - Notes - ----- - Collapses the Nodes and Edges EntitySets. Two nodes(edges) are duplicates - if their respective memberships(elements) are the same. Using this as an - equivalence relation, the uids of the nodes(edges) are partitioned into - equivalence classes. A single member of the equivalence class is chosen to represent - the class followed by the number of members of the class. - - Example - ------- - - >>> h = Hypergraph(EntitySet('example',elements=[Entity('E1', ['a','b']),Entity('E2',['a','b'])])) - >>> h.incidence_dict - {'E1': {'a', 'b'}, 'E2': {'a', 'b'}} - >>> h.collapse_nodes_and_edges().incidence_dict ### Fix this - {('E1', 2): {('a', 2)}} - - """ - if use_reps is not None or return_counts is not None: - msg = """ - use_reps ane return_counts are no longer supported keyword arguments and will throw - an error in the next release. - collapsed hypergraph automatically names collapsed objects by a string "rep:count" - """ - warnings.warn(msg, DeprecationWarning) - - if return_equivalence_classes: - temp, neq = self.collapse_nodes( - name="temp", return_equivalence_classes=True - ) - ntemp, eeq = temp.collapse_edges(name=name, return_equivalence_classes=True) - return ntemp, neq, eeq - else: - temp = self.collapse_nodes(name="temp") - return temp.collapse_edges(name=name)
- -
[docs] def restrict_to_edges(self, edgeset, name=None): - """ - Constructs a hypergraph using a subset of the edges in hypergraph - - Parameters - ---------- - edgeset: iterable of hashables or Entities - A subset of elements of the hypergraph edges - - name: str, optional - - Returns - ------- - new hypergraph : Hypergraph - """ - if self._static: - E = self._edges - setsystem = E.restrict_to(sorted(E.indices(E.keys[0], list(edgeset)))) - return Hypergraph(setsystem, name=name, use_nwhy=self.nwhy) - else: - inneredges = set() - for e in edgeset: - if isinstance(e, Entity): - inneredges.add(e.uid) - else: - inneredges.add(e) - return Hypergraph({e: self.edges[e] for e in inneredges}, name=name)
- -
[docs] def restrict_to_nodes(self, nodeset, name=None): - """ - Constructs a new hypergraph by restricting the edges in the hypergraph to - the nodes referenced by nodeset. - - Parameters - ---------- - nodeset: iterable of hashables - References a subset of elements of self.nodes - - name: string, optional, default: None - - Returns - ------- - new hypergraph : Hypergraph - """ - if self.isstatic: - E = self.edges.restrict_to_levels((1, 0)) - setsystem = E.restrict_to(sorted(E.indices(E.keys[0], list(nodeset)))) - return Hypergraph( - setsystem.restrict_to_levels((1, 0)), name=name, use_nwhy=self.nwhy - ) - else: - memberships = set() - innernodes = set() - for node in nodeset: - innernodes.add(node) - if node in self.nodes: - memberships.update(set(self.nodes[node].memberships)) - newedgeset = dict() - for e in memberships: - if e in self.edges: - temp = self.edges[e].uidset.intersection(innernodes) - if temp: - newedgeset[e] = Entity(e, temp, **self.edges[e].properties) - return Hypergraph(newedgeset, name=name)
- -
[docs] def toplexes(self, name=None, collapse=False, use_reps=False, return_counts=True): - """ - Returns a :term:`simple hypergraph` corresponding to self. - - Warning - ------- - Collapsing is no longer supported inside the toplexes method. Instead generate a new - collapsed hypergraph and compute the toplexes of the new hypergraph. - - Parameters - ---------- - name: str, optional, default: None - - # collapse: boolean, optional, default: False - # Should the hypergraph be collapsed? This would preserve a link between duplicate maximal sets. - # If False then only one of these sets will be used and uniqueness will be up to sets of equal size. - - # use_reps: boolean, optional, default: False - # If collapse=True then each toplex will be named by a representative of the set of - # equivalent edges, default is False (see collapse_edges). - - return_counts: boolean, optional, default: True - # If collapse=True then each toplex will be named by a tuple of the representative - # of the set of equivalent edges and their count - - """ - # TODO: There is a better way to do this....need to refactor - if collapse: - if len(self.edges) > 20: # TODO: Determine how big is too big. - warnings.warn( - "Collapsing a hypergraph can take a long time. It may be preferable to collapse the graph first and pickle it then apply the toplex method separately." - ) - temp = self.collapse_edges() - else: - temp = self - - if collapse: - msg = """ - collapse, return_counts, and use_reps are no longer supported keyword arguments - and will throw an error in the next release. - """ - warnings.warn(msg, DeprecationWarning) - - thdict = dict() - if self.nwhy: - tops = self.g.toplexes() - E = self.edges.restrict_to(tops) - return hnx.Hypergraph(E, use_nwhy=True) - else: - if self.isstatic: - for e in temp.edges: - thdict[e] = temp.edges[e] - else: - for e in temp.edges: - thdict[e] = temp.edges[e].uidset - tops = list() - for e in temp.edges: - flag = True - old_tops = list(tops) - for top in old_tops: - if set(thdict[e]).issubset(thdict[top]): - flag = False - break - elif set(thdict[top]).issubset(thdict[e]): - tops.remove(top) - if flag: - tops += [e] - return self.restrict_to_edges(tops, name=name)
- -
[docs] def is_connected(self, s=1, edges=False): - """ - Determines if hypergraph is :term:`s-connected <s-connected, s-node-connected>`. - - Parameters - ---------- - s: int, optional, default: 1 - - edges: boolean, optional, default: False - If True, will determine if s-edge-connected. - For s=1 s-edge-connected is the same as s-connected. - - Returns - ------- - is_connected : boolean - - Notes - ----- - - A hypergraph is s node connected if for any two nodes v0,vn - there exists a sequence of nodes v0,v1,v2,...,v(n-1),vn - such that every consecutive pair of nodes v(i),v(i+1) - share at least s edges. - - A hypergraph is s edge connected if for any two edges e0,en - there exists a sequence of edges e0,e1,e2,...,e(n-1),en - such that every consecutive pair of edges e(i),e(i+1) - share at least s nodes. - - """ - - if self.isstatic: - g = self.get_linegraph(s=s, edges=edges) - if self.nwhy: - return g.is_s_connected() - else: - return g.is_connected() - return result - else: - if edges: - A = self.edge_adjacency_matrix(s=s) - else: - A = self.adjacency_matrix(s=s) - G = nx.from_scipy_sparse_matrix(A) - return nx.is_connected(G)
- -
[docs] def singletons(self): - """ - Returns a list of singleton edges. A singleton edge is an edge of - size 1 with a node of degree 1. - - Returns - ------- - singles : list - A list of edge uids. - """ - if self.nwhy: - return self.edges.translate(0, self.g.singletons()) - else: - M, rdict, cdict = self.incidence_matrix(index=True) - idx = np.argmax(M.shape) # which axis has fewest members? if 1 then columns - cols = M.sum(idx) # we add down the row index if there are fewer columns - singles = list() - for c in range(cols.shape[(idx + 1) % 2]): # index along opposite axis - if cols[idx * c, c * ((idx + 1) % 2)] == 1: - # then see if the singleton entry in that column is also singleton in its row - # find the entry - if idx == 0: - r = np.argmax(M.getcol(c)) - # and get its sum - s = np.sum(M.getrow(r)) - # if this is also 1 then the entry in r,c represents a singleton - # so we want to change that entry to 0 and remove the row. - # this means we want to remove the edge corresponding to c - if s == 1: - singles.append(cdict[c]) - else: # switch the role of r and c - r = np.argmax(M.getrow(c)) - s = np.sum(M.getcol(r)) - if s == 1: - singles.append(cdict[r]) - return singles
- -
[docs] def remove_singletons(self, name=None): - """XX - Constructs clone of hypergraph with singleton edges removed. - - Parameters - ---------- - name: str, optional, default: None - - Returns - ------- - new hypergraph : Hypergraph - - """ - E = [e for e in self.edges if e not in self.singletons()] - return self.restrict_to_edges(E)
- -
[docs] def s_connected_components(self, s=1, edges=True, return_singletons=False): - """ - Returns a generator for the :term:`s-edge-connected components <s-edge-connected component>` - or the :term:`s-node-connected components <s-connected component, s-node-connected component>` - of the hypergraph. - - Parameters - ---------- - s : int, optional, default: 1 - - edges : boolean, optional, default: True - If True will return edge components, if False will return node components - return_singletons : bool, optional, default : False - - Notes - ----- - If edges=True, this method returns the s-edge-connected components as - lists of lists of edge uids. - An s-edge-component has the property that for any two edges e1 and e2 - there is a sequence of edges starting with e1 and ending with e2 - such that pairwise adjacent edges in the sequence intersect in at least - s nodes. If s=1 these are the path components of the hypergraph. - - If edges=False this method returns s-node-connected components. - A list of sets of uids of the nodes which are s-walk connected. - Two nodes v1 and v2 are s-walk-connected if there is a - sequence of nodes starting with v1 and ending with v2 such that pairwise - adjacent nodes in the sequence share s edges. If s=1 these are the - path components of the hypergraph. - - Example - ------- - >>> S = {'A':{1,2,3},'B':{2,3,4},'C':{5,6},'D':{6}} - >>> H = Hypergraph(S) - - >>> list(H.s_components(edges=True)) - [{'C', 'D'}, {'A', 'B'}] - >>> list(H.s_components(edges=False)) - [{1, 2, 3, 4}, {5, 6}] - - Yields - ------ - s_connected_components : iterator - Iterator returns sets of uids of the edges (or nodes) in the s-edge(node) - components of hypergraph. - - """ - components = list() - - if self.nwhy: - g = self.get_linegraph(s, edges=edges) - if return_singletons: - allobjects = set(self.edges) if edges == True else set(self.nodes) - for c in g.s_connected_components(): - comp = {self.get_name(nd, edges=edges) for nd in c} - allobjects.difference_update(comp) - for c in g.s_connected_components(): - yield {self.get_name(nd, edges=edges) for nd in c} - for obj in allobjects: - yield {obj} - else: - for c in g.s_connected_components(): - comp = {self.get_name(nd, edges=edges) for nd in c} - yield comp - - elif self.isstatic: - g = self.get_linegraph(s, edges=edges) - for c in nx.connected_components(g): - if not return_singletons and len(c) == 1: - continue - yield {self.get_name(n, edges=edges) for n in c} - else: - if edges: - A, coldict = self.edge_adjacency_matrix(s=s, index=True) - G = nx.from_scipy_sparse_matrix(A) - # if not return_singletons: - # temp = [c for c in nx.connected_components(G) if len(c) > 1] - # else: - # temp = nx.connected_components(G) - for c in nx.connected_components(G): - if not return_singletons and len(c) == 1: - continue - yield {coldict[n] for n in c} - else: - A, rowdict = self.adjacency_matrix(s=s, index=True) - G = nx.from_scipy_sparse_matrix(A) - for c in nx.connected_components(G): - if not return_singletons: - if len(c) == 1: - continue - yield {rowdict[n] for n in c}
- -
[docs] def s_component_subgraphs(self, s=1, edges=True, return_singletons=False): - """ - - Returns a generator for the induced subgraphs of s_connected components. - Removes singletons unless return_singletons is set to True. Computed using - s-linegraph generated either by the hypergraph (edges=True) or its dual - (edges = False) - - Parameters - ---------- - s : int, optional, default: 1 - - edges : boolean, optional, edges=False - Determines if edge or node components are desired. Returns - subgraphs equal to the hypergraph restricted to each set of nodes(edges) in the - s-connected components or s-edge-connected components - return_singletons : bool, optional - - Yields - ------ - s_component_subgraphs : iterator - Iterator returns subgraphs generated by the edges (or nodes) in the - s-edge(node) components of hypergraph. - - """ - for idx, c in enumerate( - self.s_components(s=s, edges=edges, return_singletons=return_singletons) - ): - if edges: - yield self.restrict_to_edges(c, name=f"{self.name}:{idx}") - else: - yield self.restrict_to_nodes(c, name=f"{self.name}:{idx}")
- -
[docs] def s_components(self, s=1, edges=True, return_singletons=True): - """ - Same as s_connected_components - - See Also - -------- - s_connected_components - """ - return self.s_connected_components( - s=s, edges=edges, return_singletons=return_singletons - )
- -
[docs] def connected_components(self, edges=False, return_singletons=True): - """ - Same as :meth:`s_connected_components` with s=1, but nodes are returned - by default. Return iterator. - - See Also - -------- - s_connected_components - """ - return self.s_connected_components(edges=edges, return_singletons=True)
- -
[docs] def connected_component_subgraphs(self, return_singletons=True): - """ - Same as :meth:`s_component_subgraphs` with s=1. Returns iterator - - See Also - -------- - s_component_subgraphs - """ - return self.s_component_subgraphs(return_singletons=return_singletons)
- -
[docs] def components(self, edges=False, return_singletons=True): - """ - Same as :meth:`s_connected_components` with s=1, but nodes are returned - by default. Return iterator. - - See Also - -------- - s_connected_components - """ - return self.s_connected_components(s=1, edges=edges)
- -
[docs] def component_subgraphs(self, return_singletons=False): - """ - Same as :meth:`s_components_subgraphs` with s=1. Returns iterator. - - See Also - -------- - s_component_subgraphs - """ - return self.s_component_subgraphs(return_singletons=return_singletons)
- -
[docs] def node_diameters(self, s=1): - """ - Returns the node diameters of the connected components in hypergraph. - - Parameters - ---------- - list of the diameters of the s-components and - list of the s-component nodes - """ - if self.nwhy: - g = self.get_linegraph(s, edges=False) - if g.is_s_connected(): - return g.s_diameter() - else: - diameters = list() - nodelists = list() - for c in g.s_connected_components(): - tc = self.edges.labs(1)[c] - nodelists.append(tc) - diameters.append(self.restrict_to_nodes(tc).node_diameters(s=s)) - else: - A, coldict = self.adjacency_matrix(s=s, index=True) - G = nx.from_scipy_sparse_matrix(A) - diams = [] - comps = [] - for c in nx.connected_components(G): - diamc = nx.diameter(G.subgraph(c)) - temp = set() - for e in c: - temp.add(coldict[e]) - comps.append(temp) - diams.append(diamc) - loc = np.argmax(diams) - return diams[loc], diams, comps
- -
[docs] def edge_diameters(self, s=1): - """ - Returns the edge diameters of the s_edge_connected component subgraphs - in hypergraph. - - Parameters - ---------- - s : int, optional, default: 1 - - Returns - ------- - maximum diameter : int - - list of diameters : list - List of edge_diameters for s-edge component subgraphs in hypergraph - - list of component : list - List of the edge uids in the s-edge component subgraphs. - - """ - if self.nwhy: - g = self.get_linegraph(s, edges=True) - if g.is_s_connected(): - return g.s_diameter() - else: - diameters = list() - edgelists = list() - for c in g.s_connected_components(): - tc = self.edges.labs(0)[c] - edgelists.append(tc) - diameters.append(self.restrict_to_edges(tc).edge_diameters(s=s)) - else: - A, coldict = self.edge_adjacency_matrix(s=s, index=True) - G = nx.from_scipy_sparse_matrix(A) - diams = [] - comps = [] - for c in nx.connected_components(G): - diamc = nx.diameter(G.subgraph(c)) - temp = set() - for e in c: - temp.add(coldict[e]) - comps.append(temp) - diams.append(diamc) - loc = np.argmax(diams) - return diams[loc], diams, comps
- -
[docs] def diameter(self, s=1): - """ - Returns the length of the longest shortest s-walk between nodes in hypergraph - - Parameters - ---------- - s : int, optional, default: 1 - - Returns - ------- - diameter : int - - Raises - ------ - HyperNetXError - If hypergraph is not s-edge-connected - - Notes - ----- - Two nodes are s-adjacent if they share s edges. - Two nodes v_start and v_end are s-walk connected if there is a sequence of - nodes v_start, v_1, v_2, ... v_n-1, v_end such that consecutive nodes - are s-adjacent. If the graph is not connected, an error will be raised. - - """ - if self.nwhy: - g = self.get_linegraph(s, edges=False) - if g.is_s_connected(): - return g.s_diameter() - else: - raise HyperNetXError(f"Hypergraph is not s-connected. s={s}") - else: - A = self.adjacency_matrix(s=s) - G = nx.from_scipy_sparse_matrix(A) - if nx.is_connected(G): - return nx.diameter(G) - else: - raise HyperNetXError(f"Hypergraph is not s-connected. s={s}")
- -
[docs] def edge_diameter(self, s=1): - """ - Returns the length of the longest shortest s-walk between edges in hypergraph - - Parameters - ---------- - s : int, optional, default: 1 - - Return - ------ - edge_diameter : int - - Raises - ------ - HyperNetXError - If hypergraph is not s-edge-connected - - Notes - ----- - Two edges are s-adjacent if they share s nodes. - Two nodes e_start and e_end are s-walk connected if there is a sequence of - edges e_start, e_1, e_2, ... e_n-1, e_end such that consecutive edges - are s-adjacent. If the graph is not connected, an error will be raised. - - """ - if self.nwhy: - g = self.get_linegraph(s, edges=True) - if g.is_s_connected(): - return g.s_diameter() - else: - raise HyperNetXError(f"Hypergraph is not s-connected. s={s}") - else: - A = self.edge_adjacency_matrix(s=s) - G = nx.from_scipy_sparse_matrix(A) - if nx.is_connected(G): - return nx.diameter(G) - else: - raise HyperNetXError(f"Hypergraph is not s-connected. s={s}")
- -
[docs] def distance(self, source, target, s=1): - """ - Returns the shortest s-walk distance between two nodes in the hypergraph. - - Parameters - ---------- - source : node.uid or node - a node in the hypergraph - - target : node.uid or node - a node in the hypergraph - - s : positive integer - the number of edges - - Returns - ------- - s-walk distance : int - - See Also - -------- - edge_distance - - Notes - ----- - The s-distance is the shortest s-walk length between the nodes. - An s-walk between nodes is a sequence of nodes that pairwise share - at least s edges. The length of the shortest s-walk is 1 less than - the number of nodes in the path sequence. - - Uses the networkx shortest_path_length method on the graph - generated by the s-adjacency matrix. - - """ - if self.isstatic: - g = self.get_linegraph(s=s, edges=False) - src = self.get_id(source, edges=False) - tgt = self.get_id(target, edges=False) - try: - if self.nwhy: - d = g.s_distance(src, tgt) - if d == -1: - warnings.warn(f"No {s}-path between {source} and {target}") - return np.inf - else: - return d - else: - return nx.shortest_path(g, src, tgt) - except: - warnings.warn(f"No {s}-path between {source} and {target}") - return np.inf - else: - if isinstance(source, Entity): - source = source.uid - if isinstance(target, Entity): - target = target.uid - A, rowdict = self.adjacency_matrix(s=s, index=True) - g = nx.from_scipy_sparse_matrix(A) - rkey = {v: k for k, v in rowdict.items()} - try: - path = nx.shortest_path_length(g, rkey[source], rkey[target]) - return path - except: - warnings.warn(f"No {s}-path between {source} and {target}") - return np.inf
- -
[docs] def edge_distance(self, source, target, s=1): - """XX TODO: still need to return path and translate into user defined nodes and edges - Returns the shortest s-walk distance between two edges in the hypergraph. - - Parameters - ---------- - source : edge.uid or edge - an edge in the hypergraph - - target : edge.uid or edge - an edge in the hypergraph - - s : positive integer - the number of intersections between pairwise consecutive edges - - TODO: add edge weights - weight : None or string, optional, default: None - if None then all edges have weight 1. If string then edge attribute - string is used if available. - - - Returns - ------- - s- walk distance : the shortest s-walk edge distance - A shortest s-walk is computed as a sequence of edges, - the s-walk distance is the number of edges in the sequence - minus 1. If no such path exists returns np.inf. - - See Also - -------- - distance - - Notes - ----- - The s-distance is the shortest s-walk length between the edges. - An s-walk between edges is a sequence of edges such that consecutive pairwise - edges intersect in at least s nodes. The length of the shortest s-walk is 1 less than - the number of edges in the path sequence. - - Uses the networkx shortest_path_length method on the graph - generated by the s-edge_adjacency matrix. - - """ - if self.isstatic: - g = self.get_linegraph(s=s, edges=True) - src = self.get_id(source, edges=True) - tgt = self.get_id(target, edges=True) - try: - if self.nwhy: - d = g.s_distance(src, tgt) - if d == -1: - warnings.warn(f"No {s}-path between {source} and {target}") - return np.inf - else: - return d - else: - return nx.shortest_path(g, src, tgt) - except: - warnings.warn(f"No {s}-path between {source} and {target}") - return np.inf - else: - if isinstance(source, Entity): - source = source.uid - if isinstance(target, Entity): - target = target.uid - A, coldict = self.edge_adjacency_matrix(s=s, index=True) - g = nx.from_scipy_sparse_matrix(A) - ckey = {v: k for k, v in coldict.items()} - try: - path = nx.shortest_path_length(g, ckey[source], ckey[target]) - return path - except: - warnings.warn(f"No {s}-path between {source} and {target}") - return np.inf
- -
[docs] def dataframe(self, sort_rows=False, sort_columns=False): - """ - Returns a pandas dataframe for hypergraph indexed by the nodes and - with column headers given by the edge names. - - Parameters - ---------- - sort_rows : bool, optional, default=True - sort rows based on hashable node names - sort_columns : bool, optional, default=True - sort columns based on hashable edge names - - """ - - mat, rdx, cdx = self.edges.incidence_matrix(index=True) - index = [rdx[i] for i in rdx] - columns = [cdx[j] for j in cdx] - df = pd.DataFrame(mat.todense(), index=index, columns=columns) - if sort_rows: - df = df.sort_index() - if sort_columns: - df = df[sorted(columns)] - return df
- -
[docs] @classmethod - def from_bipartite( - cls, B, set_names=("nodes", "edges"), name=None, static=False, use_nwhy=False - ): - """ - Static method creates a Hypergraph from a bipartite graph. - - Parameters - ---------- - - B: nx.Graph() - A networkx bipartite graph. Each node in the graph has a property - 'bipartite' taking the value of 0 or 1 indicating a 2-coloring of the graph. - - set_names: iterable of length 2, optional, default = ['nodes','edges'] - Category names assigned to the graph nodes associated to each bipartite set - - name: hashable - - static: bool - - Returns - ------- - : Hypergraph - - Notes - ----- - A partition for the nodes in a bipartite graph generates a hypergraph. - - >>> import networkx as nx - >>> B = nx.Graph() - >>> B.add_nodes_from([1, 2, 3, 4], bipartite=0) - >>> B.add_nodes_from(['a', 'b', 'c'], bipartite=1) - >>> B.add_edges_from([(1, 'a'), (1, 'b'), (2, 'b'), (2, 'c'), (3, 'c'), (4, 'a')]) - >>> H = Hypergraph.from_bipartite(B) - >>> H.nodes, H.edges - # output: (EntitySet(_:Nodes,[1, 2, 3, 4],{}), EntitySet(_:Edges,['b', 'c', 'a'],{})) - - """ - # TODO: Add filepath keyword to signatures here and with dataframe and numpy array - edges = [] - nodes = [] - for n, d in B.nodes(data=True): - if d["bipartite"] == 0: - nodes.append(n) - else: - edges.append(n) - - if not bipartite.is_bipartite_node_set(B, nodes): - raise HyperNetXError( - "Error: Method requires a 2-coloring of a bipartite graph." - ) - - if static: - elist = [] - for e in list(B.edges): - if e[0] in nodes: - elist.append([e[0], e[1]]) - else: - elist.append([e[1], e[0]]) - df = pd.DataFrame(elist, columns=set_names) - E = StaticEntitySet(entity=df) - name = name or "_" - return Hypergraph(E, name=name, use_nwhy=use_nwhy) - else: - node_entities = { - n: Entity(n, [], properties=B.nodes(data=True)[n]) for n in nodes - } - edge_dict = { - e: [node_entities[n] for n in list(B.neighbors(e))] for e in edges - } - name = name or "_" - return Hypergraph(setsystem=edge_dict, name=name)
- -
[docs] @classmethod - def from_numpy_array( - cls, - M, - node_names=None, - edge_names=None, - node_label="nodes", - edge_label="edges", - name=None, - key=None, - static=False, - use_nwhy=False, - ): - """ - Create a hypergraph from a real valued matrix represented as a 2 dimensionsl numpy array. - The matrix is converted to a matrix of 0's and 1's so that any truthy cells are converted to 1's and - all others to 0's. - - Parameters - ---------- - M : real valued array-like object, 2 dimensions - representing a real valued matrix with rows corresponding to nodes and columns to edges - - node_names : object, array-like, default=None - List of node names must be the same length as M.shape[0]. - If None then the node names correspond to row indices with 'v' prepended. - - edge_names : object, array-like, default=None - List of edge names must have the same length as M.shape[1]. - If None then the edge names correspond to column indices with 'e' prepended. - - name : hashable - - key : (optional) function - boolean function to be evaluated on each cell of the array, - must be applicable to numpy.array - - Returns - ------- - : Hypergraph - - Note - ---- - The constructor does not generate empty edges. - All zero columns in M are removed and the names corresponding to these - edges are discarded. - - - """ - # Create names for nodes and edges - # Validate the size of the node and edge arrays - - M = np.array(M) - if len(M.shape) != (2): - raise HyperNetXError("Input requires a 2 dimensional numpy array") - # apply boolean key if available - if key: - M = key(M) - - if node_names is not None: - nodenames = np.array(node_names) - if len(nodenames) != M.shape[0]: - raise HyperNetXError( - "Number of node names does not match number of rows." - ) - else: - nodenames = np.array([f"v{idx}" for idx in range(M.shape[0])]) - - if edge_names is not None: - edgenames = np.array(edge_names) - if len(edgenames) != M.shape[1]: - raise HyperNetXError( - "Number of edge_names does not match number of columns." - ) - else: - edgenames = np.array([f"e{jdx}" for jdx in range(M.shape[1])]) - - if static or use_nwhy: - arr = np.array(M) - if key: - arr = key(arr) * 1 - arr = arr.transpose() - labels = OrderedDict([(edge_label, edgenames), (node_label, nodenames)]) - E = StaticEntitySet(arr=arr, labels=labels) - return Hypergraph(E, name=name, use_nwhy=use_nwhy) - - else: - # Remove empty column indices from M columns and edgenames - colidx = np.array([jdx for jdx in range(M.shape[1]) if any(M[:, jdx])]) - colidxsum = np.sum(colidx) - if not colidxsum: - return Hypergraph() - else: - M = M[:, colidx] - edgenames = edgenames[colidx] - edict = dict() - # Create an EntitySet of edges from M - for jdx, e in enumerate(edgenames): - edict[e] = nodenames[ - [idx for idx in range(M.shape[0]) if M[idx, jdx]] - ] - return Hypergraph(edict, name=name)
- -
[docs] @classmethod - def from_dataframe( - cls, - df, - columns=None, - rows=None, - name=None, - fillna=0, - transpose=False, - transforms=[], - key=None, - node_label="nodes", - edge_label="edges", - static=False, - use_nwhy=False, - ): - """ - Create a hypergraph from a Pandas Dataframe object using index to label vertices - and Columns to label edges. The values of the dataframe are transformed into an - incidence matrix. - Note this is different than passing a dataframe directly - into the Hypergraph constructor. The latter automatically generates a static hypergraph - with edge and node labels given by the cell values. - - Parameters - ---------- - df : Pandas.Dataframe - a real valued dataframe with a single index - - columns : (optional) list, default = None - restricts df to the columns with headers in this list. - - rows : (optional) list, default = None - restricts df to the rows indexed by the elements in this list. - - name : (optional) string, default = None - - fillna : float, default = 0 - a real value to place in empty cell, all-zero columns will not generate - an edge. - - transpose : (optional) bool, default = False - option to transpose the dataframe, in this case df.Index will label the edges - and df.columns will label the nodes, transpose is applied before transforms and - key - - transforms : (optional) list, default = [] - optional list of transformations to apply to each column, - of the dataframe using pd.DataFrame.apply(). - Transformations are applied in the order they are - given (ex. abs). To apply transforms to rows or for additional - functionality, consider transforming df using pandas.DataFrame methods - prior to generating the hypergraph. - - key : (optional) function, default = None - boolean function to be applied to dataframe. Must be defined on numpy - arrays. - - See also - -------- - from_numpy_array()) - - - Returns - ------- - : Hypergraph - - Notes - ----- - The `from_dataframe` constructor does not generate empty edges. - All-zero columns in df are removed and the names corresponding to these - edges are discarded. - Restrictions and data processing will occur in this order: - - 1. column and row restrictions - 2. fillna replace NaNs in dataframe - 3. transpose the dataframe - 4. transforms in the order listed - 5. boolean key - - This method offers the above options for wrangling a dataframe into an incidence - matrix for a hypergraph. For more flexibility we recommend you use the Pandas - library to format the values of your dataframe before submitting it to this - constructor. - - """ - - if type(df) != pd.core.frame.DataFrame: - raise HyperNetXError("Error: Input object must be a pandas dataframe.") - - if columns: - df = df[columns] - if rows: - df = df.loc[rows] - - df = df.fillna(fillna) - if transpose: - df = df.transpose() - - # node_names = np.array(df.index) - # edge_names = np.array(df.columns) - - for t in transforms: - df = df.apply(t) - if key: - mat = key(df.values) * 1 - else: - mat = df.values * 1 - - params = { - "node_names": np.array(df.index), - "edge_names": np.array(df.columns), - "name": name, - "node_label": node_label, - "edge_label": edge_label, - "static": static, - "use_nwhy": use_nwhy, - } - return cls.from_numpy_array(mat, **params)
- - -# end of Hypergraph class - - -def _make_3_arrays(mat): - arr = coo_matrix(mat) - return arr.row, arr.col, arr.data -
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/classes/staticentity.html b/docs/build/_modules/classes/staticentity.html deleted file mode 100644 index 091c7421..00000000 --- a/docs/build/_modules/classes/staticentity.html +++ /dev/null @@ -1,1294 +0,0 @@ - - - - - - - - - - classes.staticentity — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • classes.staticentity
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for classes.staticentity

-from collections import OrderedDict, defaultdict
-from collections.abc import Iterable
-import warnings
-from copy import copy
-import numpy as np
-import networkx as nx
-from hypernetx import *
-from hypernetx.exception import HyperNetXError
-from hypernetx.classes.entity import Entity, EntitySet
-from hypernetx.utils import HNXCount, DefaultOrderedDict, remove_row_duplicates
-from scipy.sparse import coo_matrix, csr_matrix, issparse
-import itertools as it
-import pandas as pd
-
-__all__ = ["StaticEntity", "StaticEntitySet"]
-
-
-
[docs]class StaticEntity(object): - - """ - .. _staticentity: - - Parameters - ---------- - entity : StaticEntity, StaticEntitySet, Entity, EntitySet, pandas.DataFrame, dict, or list of lists - If a pandas.DataFrame, an error will be raised if there are nans. - data : array or array-like - Two dimensional array of integers. Provides sparse tensor indices for incidence - tensor. - arr : numpy.ndarray or scip.sparse.matrix, optional, default=None - Incidence tensor of data. - labels : OrderedDict of lists, optional, default=None - User defined labels corresponding to integers in data. - uid : hashable, optional, default=None - - props : user defined keyword arguments to be added to a properties dictionary, optional - - Attributes - ---------- - properties : dict - Description - - """ - - def __init__( - self, entity=None, data=None, arr=None, labels=None, uid=None, **props - ): - - self._uid = uid - self.properties = {} - if entity is not None: - if isinstance(entity, StaticEntity) or isinstance(entity, StaticEntitySet): - self.properties.update(entity.properties) - self.properties.update(props) - self.__dict__.update(self.properties) - self.__dict__.update(props) - self._data = entity.data.copy() - self._dimensions = entity.dimensions - self._dimsize = entity.dimsize - self._labels = OrderedDict( - (category, np.array(values)) - for category, values in entity.labels.items() - ) - self._keys = np.array(list(self._labels.keys())) - self._keyindex = lambda category: int( - np.where(np.array(list(self._labels.keys())) == category)[0] - ) - self._index = ( - lambda category, value: int( - np.where(self._labels[category] == value)[0] - ) - if np.where(self._labels[category] == value)[0].size > 0 - else None - ) - self._arr = None - elif isinstance(entity, pd.DataFrame): - self.properties.update(props) - data, labels, counts = _turn_dataframe_into_entity( - entity, return_counts=True - ) - self.properties.update({"counts": counts}) - self.__dict__.update(self.properties) - self._data = data - self._labels = labels - self._arr = None - self._dimensions = tuple([max(x) + 1 for x in self._data.transpose()]) - self._dimsize = len(self._dimensions) - self._keys = np.array(list(self._labels.keys())) - self._keyindex = lambda category: int( - np.where(np.array(list(self._labels.keys())) == category)[0] - ) - self._index = ( - lambda category, value: int( - np.where(self._labels[category] == value)[0] - ) - if np.where(self._labels[category] == value)[0].size > 0 - else None - ) - else: - if isinstance(entity, Entity) or isinstance(entity, EntitySet): - d = entity.incidence_dict - self._data, self._labels = _turn_dict_to_staticentity( - d - ) # For now duplicate entries will be removed. - elif isinstance(entity, dict): # returns only 2 levels - self._data, self._labels = _turn_dict_to_staticentity( - entity - ) # For now duplicate entries will be removed. - else: # returns only 2 levels - self._data, self._labels = _turn_iterable_to_staticentity(entity) - self._dimensions = tuple([len(self._labels[k]) for k in self._labels]) - self._dimsize = len(self._dimensions) - self._keys = np.array(list(self._labels.keys())) - self._keyindex = lambda category: int( - np.where(np.array(list(self._labels.keys())) == category)[0] - ) - self._index = ( - lambda category, value: int( - np.where(self._labels[category] == value)[0] - ) - if np.where(self._labels[category] == value)[0].size > 0 - else None - ) - self.properties.update(props) - self.__dict__.update( - self.properties - ) # Add function to set attributes ###########!!!!!!!!!!!!! - self._arr = None - elif data is not None: - self._arr = None - self._data, counts = remove_row_duplicates( - data, return_counts=True - ) # TODO Incorporate counts into data for distance - self.properties["counts"] = counts - self._dimensions = tuple([max(x) + 1 for x in self._data.transpose()]) - self._dimsize = len(self._dimensions) - self.properties.update(props) - self.__dict__.update(props) - if ( - labels is not None - ): # determine if hashmaps might be better than lambda expressions to recover indices - self._labels = OrderedDict( - (category, np.array(values)) for category, values in labels.items() - ) # OrderedDict(category,np.array([categorical values ....])) is aligned to arr - self._keyindex = lambda category: int( - np.where(np.array(list(self._labels.keys())) == category)[0] - ) - self._keys = np.array(list(labels.keys())) - self._index = ( - lambda category, value: int( - np.where(self._labels[category] == value)[0] - ) - if np.where(self._labels[category] == value)[0].size > 0 - else None - ) - else: - self._labels = OrderedDict( - [ - (int(dim), np.arange(ct)) - for dim, ct in enumerate(self.dimensions) - ] - ) - self._keyindex = lambda category: int(category) - self._keys = np.arange(self._dimsize) - self._index = ( - lambda category, value: value - if value in self._labels[category] - else None - ) - # self._index = lambda category, value: int(np.where(self._labels[category] == value)[0]) if np.where(self._labels[category] == value)[0].size > 0 else None - elif arr is not None: - self._arr = arr - self.properties.update(props) - self.__dict__.update(props) - self._state_dict = {"arr": arr * 1} - self._dimensions = arr.shape - self._dimsize = len(arr.shape) - self._data = _turn_tensor_to_data(arr * 1) - if ( - labels is not None - ): # determine if hashmaps might be better than lambda expressions to recover indices - self._labels = OrderedDict( - (category, np.array(values)) for category, values in labels.items() - ) - self._keyindex = lambda category: int( - np.where(np.array(list(self._labels.keys())) == category)[0] - ) - self._keys = np.array(list(labels.keys())) - self._index = ( - lambda category, value: int( - np.where(self._labels[category] == value)[0] - ) - if np.where(self._labels[category] == value)[0].size > 0 - else None - ) - else: - self._labels = OrderedDict( - [ - (int(dim), np.arange(ct)) - for dim, ct in enumerate(self.dimensions) - ] - ) - self._keyindex = lambda category: int(category) - self._keys = np.arange(self._dimsize) - self._index = ( - lambda category, value: value - if value in self._labels[category] - else None - ) - else: # no entity, data or arr is given - - if labels is not None: - self._labels = OrderedDict( - (category, np.array(values)) for category, values in labels.items() - ) - self._dimensions = tuple([len(labels[k]) for k in labels]) - self._data = np.zeros((0, len(labels)), dtype=int) - self._arr = np.empty(self._dimensions, dtype=int) - self._state_dict = {"arr": np.empty(self.dimensions, dtype=int)} - self._dimsize = len(self._dimensions) - self._keyindex = lambda category: int( - np.where(np.array(list(self._labels.keys())) == category)[0] - ) - self._keys = np.array(list(labels.keys())) - self._index = ( - lambda category, value: int( - np.where(self._labels[category] == value)[0] - ) - if np.where(self._labels[category] == value)[0].size > 0 - else None - ) - else: - self._data = np.zeros((0, 0), dtype=int) - self._arr = np.array([], dtype=int) - self._labels = OrderedDict([]) - self._dimensions = tuple([]) - self._dimsize = 0 - self._keyindex = lambda category: None - self._keys = np.array([]) - self._index = lambda category, value: None - - # if labels is a list of categorical values, then change it into an - # ordered dictionary? - self.properties = props - self.__dict__.update(props) # keyed by the method name and signature - - if len(self._labels) > 0: - self._labs = lambda kdx: self._labels.get(self._keys[kdx], {}) - else: - self._labs = lambda kdx: {} - - @property - def arr(self): - if self._arr is not None: - if type(self._arr) == int and self._arr == 0: - print("arr cannot be computed") - else: - try: - imat = np.zeros(self.dimensions, dtype=int) - for d in self._data: - imat[tuple(d)] = 1 - self._arr = imat - except Exception as ex: - print(ex) - print("arr cannot be computed") - self._arr = 0 - return self._arr # Do we need to return anything here - - # @property - # def array_with_counts(self): - # """ - # This method will be included in future release. - # It allows duplicate rows in the data table - # Returns - # ------- - # np.ndarray - # """ - # if self._arr is not None: - # if type(self._arr) == int and self._arr == 0: - # print("arr cannot be computed") - # else: - # try: - # imat = np.zeros(self.dimensions, dtype=int) - # for d in self._data: - # imat[tuple(d)] += 1 - # self._arr = imat - # except Exception as ex: - # print(ex) - # print("arr cannot be computed") - # self._arr = 0 - # return self._arr - - @property - def data(self): - """ - Data array or tensor array of Static Entity - - Returns - ------- - np.ndarray - """ - - return self._data - - @property - def labels(self): - """ - Ordered dictionary of labels - - Returns - ------- - collections.OrderedDict - """ - return self._labels - - @property - def dimensions(self): - """ - Dimension of Static Entity data - - Returns - ------- - tuple - """ - return self._dimensions - - @property - def dimsize(self): - """ - Number of categories in the data - - Returns - ------- - int - """ - return self._dimsize - - @property - def keys(self): - """ - Array of keys of labels - - Returns - ------- - np.ndarray - """ - return self._keys - - @property - def keyindex(self): - """ - Returns the index of a category in keys array - - Returns - ------- - int - """ - return self._keyindex - - @property - def uid(self): - return self._uid - - @property - def uidset(self): - """ - Returns a set of the string identifiers for Static Entity - - Returns - ------- - frozenset - """ - return self.uidset_by_level(0) - - @property - def elements(self): - """ - Keys and values in the order of insertion - - Returns - ------- - collections.OrderedDict - - level1 = elements, level2 = children - """ - if len(self._keys) == 1: - return {k: {} for k in self._labels[self._keys[0]]} - else: - return self.elements_by_level(0, translate=True) - - @property - def children(self): - """ - Labels of keys of first index - - Returns - ------- - set - """ - return set(self._labs(1)) - - @property - def incidence_dict(self): - """ - Dictionary using index 0 as nodes and index 1 as edges - - Returns - ------- - collections.OrderedDict - """ - return self.elements_by_level(0, 1, translate=True) - - @property - def dataframe(self): - """ - Returns the entity data in DataFrame format - - Returns - ------- - pandas.core.frame.DataFrame - """ - return self.turn_entity_data_into_dataframe(self.data) - - def __len__(self): - """ - Returns the number of elements in Static Entity - - Returns - ------- - int - """ - return self._dimensions[0] - - def __str__(self): - """ - Return the Static Entity uid - - Returns - ------- - string - """ - return f"{self.uid}" - - def __repr__(self): - """ - Returns a string resembling the constructor for staticentity without any - children - - Returns - ------- - string - """ - return f"StaticEntity({self._uid},{list(self.uidset)},{self.properties})" - - def __contains__(self, item): - """ - Defines containment for StaticEntity based on labels/categories. - - Parameters - ---------- - item : string - - Returns - ------- - bool - """ - return item in np.concatenate(list(self._labels.values())) - - def __getitem__(self, item): - """ - Get value of key in E.elements - - Parameters - ---------- - item : string - - Returns - ------- - list - """ - # return self.elements_by_level(0, 1)[item] - return self.elements[item] - - def __iter__(self): - """ - Create iterator from E.elements - - Returns - ------- - odict_iterator - """ - return iter(self.elements) - - def __call__(self, label_index=0): - return iter(self._labs(label_index)) - -
[docs] def size(self): - """ - The number of elements in E, the size of dimension 0 in the E.arr - - Returns - ------- - int - """ - return len(self)
- -
[docs] def labs(self, kdx): - """ - Retrieve labels by index in keys - - Parameters - ---------- - kdx : int - index of key in E.keys - - Returns - ------- - np.ndarray - """ - return self._labs(kdx)
- -
[docs] def is_empty(self, level=0): - """ - Boolean indicating if entity.elements is empty - - Parameters - ---------- - level : int, optional - - Returns - ------- - bool - """ - return len(self._labs(level)) == 0
- -
[docs] def uidset_by_level(self, level=0): - """ - The labels found in columns = level - - Parameters - ---------- - level : int, optional - - Returns - ------- - frozenset - """ - return frozenset(self._labs(level)) # should be update this to tuples?
- -
[docs] def elements_by_level(self, level1=0, level2=None, translate=False): - """ - Elements of staticentity by specified column - - Parameters - ---------- - level1 : int, optional - edges - level2 : int, optional - nodes - translate : bool, optional - whether to replace indices with labels - - Returns - ------- - collections.defaultdict - - think: level1 = edges, level2 = nodes - """ - # Is there a better way to view a slice of self._arr? - if level1 > self.dimsize - 1 or level1 < 0: - print(f"This StaticEntity has no level {level1}.") - return - if level2 is None: - level2 = level1 + 1 - - if level2 > self.dimsize - 1 or level2 < 0: - print(f"This StaticEntity has no level {level2}.") - elts = OrderedDict([[k, np.array([])] for k in self._labs(level1)]) - elif level1 == level2: - print(f"level1 equals level2") - elts = OrderedDict([[k, np.array([])] for k in self._labs(level1)]) - - temp = remove_row_duplicates(self.data[:, [level1, level2]]) - elts = defaultdict(list) - for row in temp: - elts[row[0]].append(row[1]) - - if translate: - telts = OrderedDict() - for kdx, vec in elts.items(): - k = self._labs(level1)[kdx] - telts[k] = list() - for vdx in vec: - telts[k].append(self._labs(level2)[vdx]) - return telts - else: - return elts
- -
[docs] def incidence_matrix(self, level1=0, level2=1, weighted=False, index=False): - """ - Convenience method to navigate large tensor - - Parameters - ---------- - level1 : int, optional - indexes columns - level2 : int, optional - indexes rows - weighted : bool, optional - index : bool, optional - - Returns - ------- - scipy.sparse.csr.csr_matrix - - think level1 = edges, level2 = nodes - """ - if not weighted: - temp = remove_row_duplicates(self.data[:, [level2, level1]]) - else: - temp = self.data[:, [level2, level1]] - result = csr_matrix((np.ones(len(temp)), temp.transpose()), dtype=int) - - if index: # give index of rows then columns - return ( - result, - {k: v for k, v in enumerate(self._labs(level2))}, - {k: v for k, v in enumerate(self._labs(level1))}, - ) - else: - return result
- -
[docs] def restrict_to_levels(self, levels, uid=None): - """ - Limit Static Entity data to specific labels - - Parameters - ---------- - levels : array - index of labels in data - uid : None, optional - - Returns - ------- - Static Entity class - hnx.classes.staticentity.StaticEntity - """ - - if len(levels) == 1: - if levels[0] >= self.dimsize: - return self.__class__() - else: - newlabels = OrderedDict( - [(self.keys[lev], self._labs(lev)) for lev in levels] - ) - return self.__class__(labels=newlabels) - temp = remove_row_duplicates(self.data[:, levels]) - newlabels = OrderedDict([(self.keys[lev], self._labs(lev)) for lev in levels]) - return self.__class__(data=temp, labels=newlabels, uid=uid)
- -
[docs] def turn_entity_data_into_dataframe( - self, data_subset - ): # add option to include multiplicities stored in properties - """ - Convert rows of original data in StaticEntity to dataframe - - Parameters - ---------- - data : numpy.ndarray - Subset of the rows in the original data held in the StaticEntity - - Returns - ------- - pandas.core.frame.DataFrame - Columns and cell entries are derived from data and self.labels - """ - df = pd.DataFrame(data=data_subset, columns=self.keys) - width = data_subset.shape[1] - for ddx, row in enumerate(data_subset): - nrow = [self.labs(idx)[row[idx]] for idx in range(width)] - df.iloc[ddx] = nrow - return df
- -
[docs] def restrict_to_indices( - self, indices, level=0, uid=None - ): # restricting to indices requires renumbering the labels. - """ - Limit Static Entity data to specific indices of keys - - Parameters - ---------- - indices : array - array of category indices - level : int, optional - index of label - uid : None, optional - - Returns - ------- - Static Entity class - hnx.classes.staticentity.StaticEntity - """ - indices = list(indices) - idx = np.concatenate( - [np.argwhere(self.data[:, level] == k) for k in indices], axis=0 - ).transpose()[0] - temp = self.data[idx] - df = self.turn_entity_data_into_dataframe(temp) - return self.__class__(entity=df, uid=uid)
- -
[docs] def translate(self, level, index): - """ - Replaces a category index and value index with label - - Parameters - ---------- - level : int - category index of label - index : int - value index of label - - Returns - ------- - : numpy.array(str) - """ - if isinstance(index, int): - return self._labs(level)[index] - else: - return [self._labs(level)[idx] for idx in index]
- -
[docs] def translate_arr(self, coords): - """ - Translates a single cell in the entity array - - Parameters - ---------- - coords : tuple of ints - - Returns - ------- - list - """ - assert len(coords) == self.dimsize - translation = list() - for idx in range(self.dimsize): - translation.append(self.translate(idx, coords[idx])) - return translation
- -
[docs] def index(self, category, value=None): - """ - Returns dimension of category and index of value - - Parameters - ---------- - category : string - value : string, optional - - Returns - ------- - int or tuple of ints - """ - if value is not None: - return self._keyindex(category), self._index(category, value) - else: - return self._keyindex(category)
- -
[docs] def indices(self, category, values): - """ - Returns dimension of category and index of values (array) - - Parameters - ---------- - category : string - values : single string or array of strings - - Returns - ------- - list - """ - return [self._index(category, value) for value in values]
- -
[docs] def level(self, item, min_level=0, max_level=None, return_index=True): - """ - Returns first level item appears by order of keys from minlevel to maxlevel - inclusive - - Parameters - ---------- - item : string - min_level : int, optional - max_level : int, optional - - return_index : bool, optional - - Returns - ------- - tuple - """ - n = len(self.dimensions) - if max_level is not None: - n = min([max_level + 1, n]) - for lev in range(min_level, n): - if item in self._labs(lev): - if return_index: - return lev, self._index(self._keys[lev], item) - else: - return lev - else: - print(f'"{item}" not found') - return None
- - # note the depth and registry methods may or may not be useful. We can add these later. - - -
[docs]class StaticEntitySet(StaticEntity): - - """ - .. _staticentityset: - """ - - def __init__( - self, - entity=None, - data=None, - arr=None, - labels=None, - uid=None, - level1=0, - level2=1, - **props, - ): - - if entity is None: - if data is not None: - data = data[:, [level1, level2]] - arr = None - elif arr is not None: - data = _turn_tensor_to_data(arr) - data = data[:, [level1, level2]] - arr = None - if labels is not None: - keys = np.array(list(labels.keys())) - temp = OrderedDict() - for lev in [level1, level2]: - if lev < len(keys): - temp[keys[lev]] = labels[keys[lev]] - labels = temp - super().__init__(data=data, arr=arr, labels=labels, uid=uid, **props) - else: - E = StaticEntity(entity=entity) - E = E.restrict_to_levels([level1, level2]) - super().__init__(entity=E, uid=uid, **props) - - def __repr__(self): - """ - Returns a string resembling the constructor for entityset without any - children - - Returns - ------- - string - """ - return f"StaticEntitySet({self._uid},{list(self.uidset)},{self.properties})" - -
[docs] def incidence_matrix(self, sparse=True, weighted=False, index=False): - """ - Incidence matrix of StaticEntitySet indexed by uidset - - Parameters - ---------- - sparse : bool, optional - weighted : bool, optional - index : bool, optional - give index of rows then columns - - Returns - ------- - matrix - scipy.sparse.csr.csr_matrix - """ - if not weighted: - temp = remove_row_duplicates(self.data[:, [1, 0]]) - else: - temp = self.data[:, [1, 0]] - result = csr_matrix((np.ones(len(temp)), temp.transpose()), dtype=int) - - if index: - return ( - result, - {k: v for k, v in enumerate(self._labs(1))}, - {k: v for k, v in enumerate(self._labs(0))}, - ) - else: - return result
- -
[docs] def restrict_to(self, indices, uid=None): - """ - Limit Static Entityset data to specific indices of keys - - Parameters - ---------- - indices : array - array of indices in keys - uid : None, optional - - Returns - ------- - StaticEntitySet - hnx.classes.staticentity.StaticEntitySet - - """ - return self.restrict_to_indices(indices, level=0, uid=uid)
- -
[docs] def convert_to_entityset(self, uid): - """ - Convert given uid of Static EntitySet into EntitySet - - Parameters - ---------- - uid : string - - Returns - ------- - EntitySet - hnx.classes.entity.EntitySet - """ - return EntitySet(uid, self.incidence_dict)
- -
[docs] def collapse_identical_elements( - self, - uid=None, - return_equivalence_classes=False, - ): - """ - Returns StaticEntitySet after collapsing elements if they have same children - If no elements share same children, a copy of the original StaticEntitySet is returned - Parameters - ---------- - uid : None, optional - return_equivalence_classes : bool, optional - If True, return a dictionary of equivalence classes keyed by new edge names - - - Returns - ------- - StaticEntitySet - hnx.classes.staticentity.StaticEntitySet - """ - shared_children = DefaultOrderedDict(list) - for k, v in self.elements.items(): - shared_children[frozenset(v)].append(k) - new_entity_dict = OrderedDict( - [ - ( - f"{next(iter(v))}:{len(v)}", - sorted(set(k), key=lambda x: list(self.labs(1)).index(x)), - ) - for k, v in shared_children.items() - ] - ) - if return_equivalence_classes: - eq_classes = OrderedDict( - [ - ( - f"{next(iter(v))}:{len(v)}", - sorted(v, key=lambda x: list(self.labs(0)).index(x)), - ) - for k, v in shared_children.items() - ] - ) - return StaticEntitySet(uid=uid, entity=new_entity_dict), eq_classes - else: - return StaticEntitySet(uid=uid, entity=new_entity_dict)
- - -def _turn_tensor_to_data(arr, remove_duplicates=True): - """ - Return list of nonzero coordinates in arr. - - Parameters - ---------- - arr : numpy.ndarray - Tensor corresponding to incidence of co-occurring labels. - """ - return np.array(arr.nonzero()).transpose() - - -def _turn_dict_to_staticentity(dict_object, remove_duplicates=True): - """Create a static entity directly from a dictionary of hashables""" - d = OrderedDict(dict_object) - level2ctr = HNXCount() - level1ctr = HNXCount() - level2 = DefaultOrderedDict(level2ctr) - level1 = DefaultOrderedDict(level1ctr) - coords = list() - for k, val in d.items(): - level1[k] - for v in val: - level2[v] - coords.append((level1[k], level2[v])) - if remove_duplicates: - coords = remove_row_duplicates(coords) - level1 = list(level1) - level2 = list(level2) - data = np.array(coords, dtype=int) - labels = OrderedDict({"0": level1, "1": level2}) - return data, labels - - -def _turn_iterable_to_staticentity(iter_object, remove_duplicates=True): - for s in iter_object: - if not isinstance(s, Iterable): - raise HyperNetXError( - "StaticEntity constructor requires an iterable of iterables." - ) - else: - labels = [f"e{str(x)}" for x in range(len(iter_object))] - dict_object = dict(zip(labels, iter_object)) - return _turn_dict_to_staticentity(dict_object, remove_duplicates=remove_duplicates) - - -def _turn_dataframe_into_entity(df, return_counts=False, include_unknowns=False): - """ - Convenience method to reformat dataframe object into data,labels format - for construction of a static entity - - Parameters - ---------- - df : pandas.DataFrame - May not contain nans - return_counts : bool, optional, default : False - Used for keeping weights - include_unknowns : bool, optional, default : False - If Unknown <column name> was used to fill in nans - - Returns - ------- - outputdata : numpy.ndarray - counts : numpy.array of ints - slabels : numpy.array of strings - - """ - columns = df.columns - ctr = [HNXCount() for c in range(len(columns))] - ldict = OrderedDict() - rdict = OrderedDict() - for idx, c in enumerate(columns): - ldict[c] = defaultdict(ctr[idx]) # TODO make this an Ordered default dict - rdict[c] = OrderedDict() - if include_unknowns: - ldict[c][ - f"Unknown {c}" - ] # TODO: update this to take a dict assign for each column - rdict[c][0] = f"Unknown {c}" - for k in df[c]: - ldict[c][k] - rdict[c][ldict[c][k]] = k - ldict[c] = dict(ldict[c]) - dims = tuple([len(ldict[c]) for c in columns]) - - m = len(df) - n = len(columns) - data = np.zeros((m, n), dtype=int) - for rid in range(m): - for cid in range(n): - c = columns[cid] - data[rid, cid] = ldict[c][df.iloc[rid][c]] - - output_data = remove_row_duplicates(data, return_counts=return_counts) - - slabels = OrderedDict() - for cdx, c in enumerate(columns): - slabels.update({c: np.array(list(ldict[c].keys()))}) - if return_counts: - return output_data[0], slabels, output_data[1] - else: - return output_data, slabels -
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/drawing/rubber_band.html b/docs/build/_modules/drawing/rubber_band.html deleted file mode 100644 index 7f56ef56..00000000 --- a/docs/build/_modules/drawing/rubber_band.html +++ /dev/null @@ -1,715 +0,0 @@ - - - - - - - - - - drawing.rubber_band — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • drawing.rubber_band
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for drawing.rubber_band

-# Copyright © 2018 Battelle Memorial Institute
-# All rights reserved.
-
-from hypernetx import Hypergraph
-from .util import (
-    get_frozenset_label,
-    get_set_layering,
-    inflate_kwargs,
-    transpose_inflated_kwargs,
-)
-
-import matplotlib.pyplot as plt
-from matplotlib.collections import PolyCollection, LineCollection, CircleCollection
-
-import networkx as nx
-
-from itertools import combinations
-from collections import defaultdict
-
-import numpy as np
-from scipy.spatial.distance import pdist
-from scipy.spatial import ConvexHull
-from scipy.spatial import Voronoi
-
-# increases the default figure size to 8in square.
-plt.rcParams["figure.figsize"] = (8, 8)
-
-N_CONTROL_POINTS = 24
-
-theta = np.linspace(0, 2 * np.pi, N_CONTROL_POINTS + 1)[:-1]
-
-cp = np.vstack((np.cos(theta), np.sin(theta))).T
-
-
-
-
-
-
[docs]def get_default_radius(H, pos): - """ - Calculate a reasonable default node radius - - This function iterates over the hyper edges and finds the most distant - pair of points given the positions provided. Then, the node radius is a fraction - of the median of this distance take across all hyper-edges. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - - Returns - ------- - float - the recommended radius - - """ - if len(H) > 1: - return 0.0125 * np.median( - [pdist(np.vstack(list(map(pos.get, H.nodes)))).max() for nodes in H.edges()] - ) - return 1
- - -
[docs]def draw_hyper_edge_labels(H, polys, labels={}, ax=None, **kwargs): - """ - Draws a label on the hyper edge boundary. - - Should be passed Matplotlib PolyCollection representing the hyper-edges, see - the return value of draw_hyper_edges. - - The label will be draw on the least curvy part of the polygon, and will be - aligned parallel to the orientation of the polygon where it is drawn. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - polys: PolyCollection - collection of polygons returned by draw_hyper_edges - labels: dict - mapping of node id to string label - ax: Axis - matplotlib axis on which the plot is rendered - kwargs: dict - Keyword arguments are passed through to Matplotlib's annotate function. - - """ - ax = ax or plt.gca() - - params = transpose_inflated_kwargs(inflate_kwargs(H.edges, kwargs)) - - for edge, path, params in zip(H.edges, polys.get_paths(), params): - s = labels.get(edge, edge) - - # calculate the xy location of the annotation - # this is the midpoint of the pair of adjacent points the most distant - d = ((path.vertices[:-1] - path.vertices[1:]) ** 2).sum(axis=1) - i = d.argmax() - - x1, x2 = path.vertices[i : i + 2] - x, y = x2 - x1 - theta = 360 * np.arctan2(y, x) / (2 * np.pi) - theta = (theta + 360) % 360 - - while theta > 90: - theta -= 180 - - # the string is a comma separated list of the edge uid - ax.annotate( - s, (x1 + x2) / 2, rotation=theta, ha="center", va="center", **params - )
- - -
[docs]def layout_hyper_edges(H, pos, node_radius={}, dr=None): - """ - Draws a convex hull for each edge in H. - - Position of the nodes in the graph is specified by the position dictionary, - pos. Convex hulls are spaced out such that if one set contains another, the - convex hull will surround the contained set. The amount of spacing added - between hulls is specified by the parameter, dr. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - node_radius: dict - mapping of node to R^1 (radius of each node) - dr: float - the spacing between concentric rings - ax: Axis - matplotlib axis on which the plot is rendered - - Returns - ------- - dict - A mapping from hyper edge ids to paths (Nx2 numpy matrices) - """ - - if len(node_radius): - r0 = min(node_radius.values()) - else: - r0 = get_default_radius(H, pos) - - dr = dr or r0 - - levels = get_set_layering(H) - - radii = { - v: {v: i for i, v in enumerate(sorted(e, key=levels.get))} - for v, e in H.dual().edges.elements.items() - } - - def get_padded_hull(uid, edge): - # make sure the edge contains at least one node - if len(edge): - points = np.vstack( - [ - cp * (node_radius.get(v, r0) + dr * (2 + radii[v][uid])) + pos[v] - for v in edge - ] - ) - # if not, draw an empty edge centered around the location of the edge node (in the bipartite graph) - else: - points = 4 * r0 * cp + pos[uid] - - hull = ConvexHull(points) - - return hull.points[hull.vertices] - - return [get_padded_hull(uid, list(H.edges[uid])) for uid in H.edges]
- - -
[docs]def draw_hyper_edges(H, pos, ax=None, node_radius={}, dr=None, **kwargs): - """ - Draws a convex hull around the nodes contained within each edge in H - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - node_radius: dict - mapping of node to R^1 (radius of each node) - dr: float - the spacing between concentric rings - ax: Axis - matplotlib axis on which the plot is rendered - kwargs: dict - keyword arguments, e.g., linewidth, facecolors, are passed through to the PolyCollection constructor - - Returns - ------- - PolyCollection - a Matplotlib PolyCollection that can be further styled - """ - points = layout_hyper_edges(H, pos, node_radius=node_radius, dr=dr) - - polys = PolyCollection(points, **inflate_kwargs(H.edges, kwargs)) - - (ax or plt.gca()).add_collection(polys) - - return polys
- - -
[docs]def draw_hyper_nodes(H, pos, node_radius={}, r0=None, ax=None, **kwargs): - """ - Draws a circle for each node in H. - - The position of each node is specified by the a dictionary/list-like, pos, - where pos[v] is the xy-coordinate for the vertex. The radius of each node - can be specified as a dictionary where node_radius[v] is the radius. If a - node is missing from this dictionary, or the node_radius is not specified at - all, a sensible default radius is chosen based on distances between nodes - given by pos. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - node_radius: dict - mapping of node to R^1 (radius of each node) - r0: float - minimum distance that concentric rings start from the node position - ax: Axis - matplotlib axis on which the plot is rendered - kwargs: dict - keyword arguments, e.g., linewidth, facecolors, are passed through to the PolyCollection constructor - - Returns - ------- - PolyCollection - a Matplotlib PolyCollection that can be further styled - """ - - ax = ax or plt.gca() - - r0 = r0 or get_default_radius(H, pos) - - points = [node_radius.get(v, r0) * cp + pos[v] for v in H.nodes] - - kwargs.setdefault("facecolors", "black") - - circles = PolyCollection(points, **inflate_kwargs(H, kwargs)) - - ax.add_collection(circles) - - return circles
- - -
[docs]def draw_hyper_labels(H, pos, node_radius={}, ax=None, labels={}, **kwargs): - """ - Draws text labels for the hypergraph nodes. - - The label is drawn to the right of the node. The node radius is needed (see - draw_hyper_nodes) so the text can be offset appropriately as the node size - changes. - - The text label can be customized by passing in a dictionary, labels, mapping - a node to its custom label. By default, the label is the string - representation of the node. - - Keyword arguments are passed through to Matplotlib's annotate function. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - node_radius: dict - mapping of node to R^1 (radius of each node) - ax: Axis - matplotlib axis on which the plot is rendered - labels: dict - mapping of node to text label - kwargs: dict - keyword arguments passed to matplotlib.annotate - - """ - ax = ax or plt.gca() - - params = transpose_inflated_kwargs(inflate_kwargs(H.nodes, kwargs)) - - for v, v_kwargs in zip(H.nodes, params): - xy = np.array([node_radius.get(v, 0), 0]) + pos[v] - ax.annotate( - labels.get(v, v), - xy, - **{ - k: ( - d[v] - if hasattr(d, "__getitem__") and type(d) not in {str, tuple} - else d - ) - for k, d in kwargs.items() - } - )
- - -
[docs]def draw( - H, - pos=None, - with_color=True, - with_node_counts=False, - with_edge_counts=False, - layout=nx.spring_layout, - layout_kwargs={}, - ax=None, - node_radius=None, - edges_kwargs={}, - nodes_kwargs={}, - edge_labels={}, - edge_labels_kwargs={}, - node_labels={}, - node_labels_kwargs={}, - with_edge_labels=True, - with_node_labels=True, - label_alpha=0.35, - return_pos=False, -): - """ - Draw a hypergraph as a Matplotlib figure - - By default this will draw a colorful "rubber band" like hypergraph, where - convex hulls represent edges and are drawn around the nodes they contain. - - This is a convenience function that wraps calls with sensible parameters to - the following lower-level drawing functions: - - * draw_hyper_edges, - * draw_hyper_edge_labels, - * draw_hyper_labels, and - * draw_hyper_nodes - - The default layout algorithm is nx.spring_layout, but other layouts can be - passed in. The Hypergraph is converted to a bipartite graph, and the layout - algorithm is passed the bipartite graph. - - If you have a pre-determined layout, you can pass in a "pos" dictionary. - This is a dictionary mapping from node id's to x-y coordinates. For example: - - >>> pos = { - >>> 'A': (0, 0), - >>> 'B': (1, 2), - >>> 'C': (5, -3) - >>> } - - will position the nodes {A, B, C} manually at the locations specified. The - coordinate system is in Matplotlib "data coordinates", and the figure will - be centered within the figure. - - By default, this will draw in a new figure, but the axis to render in can be - specified using :code:`ax`. - - This approach works well for small hypergraphs, and does not guarantee - a rigorously "correct" drawing. Overlapping of sets in the drawing generally - implies that the sets intersect, but sometimes sets overlap if there is no - intersection. It is not possible, in general, to draw a "correct" hypergraph - this way for an arbitrary hypergraph, in the same way that not all graphs - have planar drawings. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - with_color: bool - set to False to disable color cycling of edges - with_node_counts: bool - set to True to label collapsed nodes with number of elements - with_edge_counts: bool - set to True to label collapsed edges with number of elements - layout: function - layout algorithm to compute - layout_kwargs: dict - keyword arguments passed to layout function - ax: Axis - matplotlib axis on which the plot is rendered - edges_kwargs: dict - keyword arguments passed to matplotlib.collections.PolyCollection for edges - node_radius: None, int, float, or dict - radius of all nodes, or dictionary of node:value; the default (None) calculates radius based on number of collapsed nodes; reasonable values range between 1 and 3 - nodes_kwargs: dict - keyword arguments passed to matplotlib.collections.PolyCollection for nodes - edge_labels_kwargs: dict - keyword arguments passed to matplotlib.annotate for edge labels - node_labels_kwargs: dict - keyword argumetns passed to matplotlib.annotate for node labels - with_edge_labels: bool - set to False to make edge labels invisible - with_node_labels: bool - set to False to make node labels invisible - label_alpha: float - the transparency (alpha) of the box behind text drawn in the figure - """ - - ax = ax or plt.gca() - - if pos is None: - pos = layout_node_link(H, layout=layout, **layout_kwargs) - - r0 = get_default_radius(H, pos) - a0 = np.pi * r0 ** 2 - - def get_node_radius(v): - if node_radius is None: - return np.sqrt(a0 * (len(v) if type(v) == frozenset else 1) / np.pi) - elif hasattr(node_radius, "get"): - return node_radius.get(v, 1) * r0 - return node_radius * r0 - - # guarantee that node radius is a dictionary mapping nodes to values - node_radius = {v: get_node_radius(v) for v in H.nodes} - - # for convenience, we are using setdefault to mutate the argument - # however, we need to copy this to prevent side-effects - edges_kwargs = edges_kwargs.copy() - edges_kwargs.setdefault("edgecolors", plt.cm.tab10(np.arange(len(H.edges)) % 10)) - edges_kwargs.setdefault("facecolors", "none") - - polys = draw_hyper_edges(H, pos, node_radius=node_radius, ax=ax, **edges_kwargs) - - if with_edge_labels: - labels = get_frozenset_label( - H.edges, count=with_edge_counts, override=edge_labels - ) - - draw_hyper_edge_labels( - H, - polys, - color=edges_kwargs["edgecolors"], - backgroundcolor=(1, 1, 1, label_alpha), - labels=labels, - ax=ax, - **edge_labels_kwargs - ) - - if with_node_labels: - labels = get_frozenset_label( - H.nodes, count=with_node_counts, override=node_labels - ) - - draw_hyper_labels( - H, - pos, - node_radius=node_radius, - labels=labels, - ax=ax, - va="center", - xytext=(5, 0), - textcoords="offset points", - backgroundcolor=(1, 1, 1, label_alpha), - **node_labels_kwargs - ) - - draw_hyper_nodes(H, pos, node_radius=node_radius, ax=ax, **nodes_kwargs) - - if len(H.nodes) == 1: - x, y = pos[list(H.nodes)[0]] - s = 20 - - ax.axis([x - s, x + s, y - s, y + s]) - else: - ax.axis("equal") - - ax.axis("off") - if return_pos: - return pos
-
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/drawing/two_column.html b/docs/build/_modules/drawing/two_column.html deleted file mode 100644 index cbc4eaf2..00000000 --- a/docs/build/_modules/drawing/two_column.html +++ /dev/null @@ -1,426 +0,0 @@ - - - - - - - - - - drawing.two_column — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • drawing.two_column
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for drawing.two_column

-# Copyright © 2018 Battelle Memorial Institute
-# All rights reserved.
-
-import matplotlib.pyplot as plt
-from matplotlib.collections import LineCollection
-
-import networkx as nx
-
-from .util import get_frozenset_label
-
-
-
[docs]def layout_two_column(H, spacing=2): - """ - Two column (bipartite) layout algorithm. - - This algorithm first converts the hypergraph into a bipartite graph and - then computes connected components. Disonneccted components are handled - independently and then stacked together. - - Within a connected component, the spectral ordering of the bipartite graph - provides a quick and dirty ordering that minimizes edge crossings in the - diagram. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - spacing: float - amount of whitespace between disconnected components - """ - offset = 0 - pos = {} - - def stack(vertices, x, height): - for i, v in enumerate(vertices): - pos[v] = (x, i + offset + (height - len(vertices)) / 2) - - G = H.bipartite() - for ci in nx.connected_components(G): - Gi = G.subgraph(ci) - key = {v: i for i, v in enumerate(nx.spectral_ordering(Gi))}.get - ci_vertices, ci_edges = [ - sorted([v for v, d in Gi.nodes(data=True) if d["bipartite"] == j], key=key) - for j in [0, 1] - ] - - height = max(len(ci_vertices), len(ci_edges)) - - stack(ci_vertices, 0, height) - stack(ci_edges, 1, height) - - offset += height + spacing - - return pos
- - -
[docs]def draw_hyper_edges(H, pos, ax=None, **kwargs): - """ - Renders hyper edges for the two column layout. - - Each node-hyper edge membership is rendered as a line connecting the node - in the left column to the edge in the right column. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - ax: Axis - matplotlib axis on which the plot is rendered - kwargs: dict - keyword arguments passed to matplotlib.LineCollection - - Returns - ------- - LineCollection - the hyper edges - """ - ax = ax or plt.gca() - - pairs = [(v, e.uid) for e in H.edges() for v in e] - - kwargs = { - k: v if type(v) != dict else [v.get(e) for _, e in pairs] - for k, v in kwargs.items() - } - - lines = LineCollection([(pos[u], pos[v]) for u, v in pairs], **kwargs) - - ax.add_collection(lines) - - return lines
- - -
[docs]def draw_hyper_labels( - H, pos, labels={}, with_node_labels=True, with_edge_labels=True, ax=None -): - """ - Renders hyper labels (nodes and edges) for the two column layout. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - pos: dict - mapping of node and edge positions to R^2 - labels: dict - custom labels for nodes and edges can be supplied - with_node_labels: bool - False to disable node labels - with_edge_labels: bool - False to disable edge labels - ax: Axis - matplotlib axis on which the plot is rendered - kwargs: dict - keyword arguments passed to matplotlib.LineCollection - - """ - - ax = ax or plt.gca() - - edges = [e.uid for e in H.edges()] - - to_draw = [] - if with_node_labels: - to_draw.append((H.nodes(), "right")) - - if with_edge_labels: - to_draw.append((H.edges(), "left")) - - for points, ha in to_draw: - for p in points: - ax.annotate(labels.get(p.uid, p.uid), pos[p.uid], ha=ha, va="center")
- - -
[docs]def draw( - H, - with_node_labels=True, - with_edge_labels=True, - with_node_counts=False, - with_edge_counts=False, - with_color=True, - edge_kwargs=None, - ax=None, -): - """ - Draw a hypergraph using a two-collumn layout. - - This is intended reproduce an illustrative technique for bipartite graphs - and hypergraphs that is typically used in papers and textbooks. - - The left column is reserved for nodes and the right column is reserved for - edges. A line is drawn between a node an an edge - - The order of nodes and edges is optimized to reduce line crossings between - the two columns. Spacing between disconnected components is adjusted to make - the diagram easier to read, by reducing the angle of the lines. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - with_node_labels: bool - False to disable node labels - with_edge_labels: bool - False to disable edge labels - with_node_counts: bool - set to True to label collapsed nodes with number of elements - with_edge_counts: bool - set to True to label collapsed edges with number of elements - with_color: bool - set to False to disable color cycling of hyper edges - edge_kwargs: dict - keyword arguments to pass to matplotlib.LineCollection - ax: Axis - matplotlib axis on which the plot is rendered - """ - - edge_kwargs = edge_kwargs or {} - - ax = ax or plt.gca() - - pos = layout_two_column(H) - - V = [v.uid for v in H.nodes()] - E = [e.uid for e in H.edges()] - - labels = {} - labels.update(get_frozenset_label(V, count=with_node_counts)) - labels.update(get_frozenset_label(E, count=with_edge_counts)) - - if with_color: - edge_kwargs["color"] = { - e.uid: plt.cm.tab10(i % 10) for i, e in enumerate(H.edges()) - } - - draw_hyper_edges(H, pos, ax=ax, **edge_kwargs) - draw_hyper_labels( - H, - pos, - labels, - ax=ax, - with_node_labels=with_node_labels, - with_edge_labels=with_edge_labels, - ) - ax.autoscale_view() - - ax.axis("off")
-
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/drawing/util.html b/docs/build/_modules/drawing/util.html deleted file mode 100644 index 31a5dcab..00000000 --- a/docs/build/_modules/drawing/util.html +++ /dev/null @@ -1,353 +0,0 @@ - - - - - - - - - - drawing.util — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • drawing.util
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for drawing.util

-# Copyright © 2018 Battelle Memorial Institute
-# All rights reserved.
-
-from itertools import combinations
-
-import numpy as np
-
-import networkx as nx
-
-
-
[docs]def inflate(items, v): - if type(v) in {str, tuple, int, float}: - return [v] * len(items) - elif callable(v): - return [v(i) for i in items] - elif type(v) not in {list, np.ndarray} and hasattr(v, "__getitem__"): - return [v[i] for i in items] - return v
- - -
[docs]def inflate_kwargs(items, kwargs): - """ - Helper function to expand keyword arguments. - - Parameters - ---------- - n: int - length of resulting list if argument is expanded - kwargs: dict - keyword arguments to be expanded - - Returns - ------- - dict - dictionary with same keys as kwargs and whose values are lists of length n - """ - - return {k: inflate(items, v) for k, v in kwargs.items()}
- - -
[docs]def transpose_inflated_kwargs(inflated): - return [dict(zip(inflated, v)) for v in zip(*inflated.values())]
- - -
[docs]def get_frozenset_label(S, count=False, override={}): - """ - Helper function for rendering the labels of possibly collapsed nodes and edges - - Parameters - ---------- - S: iterable - list of entities to be labeled - count: bool - True if labels should be counts of entities instead of list - - Returns - ------- - dict - mapping of entity to its string representation - """ - - def helper(v): - if type(v) == frozenset: - if count and len(v) > 1: - return f"x {len(v)}" - elif count: - return "" - else: - return ", ".join([str(override.get(s, s)) for s in v]) - return str(v) - - return {v: override.get(v, helper(v)) for v in S}
- - -
[docs]def get_line_graph(H, collapse=True): - """ - Computes the line graph, a directed graph, where a directed edge (u, v) - exists if the edge u is a subset of the edge v in the hypergraph. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - collapse: bool - True if edges should be added if hyper edges are identical - - Returns - ------- - networkx.DiGraph - A directed graph - """ - D = nx.DiGraph() - - V = {edge: set(nodes) for edge, nodes in H.edges.elements.items()} - - D.add_nodes_from(V) - - for u, v in combinations(V, 2): - if V[u] != V[v] or not collapse: - if V[u].issubset(V[v]): - D.add_edge(u, v) - elif V[v].issubset(V[u]): - D.add_edge(v, u) - - return D
- - -
[docs]def get_set_layering(H, collapse=True): - """ - Computes a layering of the edges in the hyper graph. - - In this layering, each edge is assigned a level. An edge u will be above - (e.g., have a smaller level value) another edge v if v is a subset of u. - - Parameters - ---------- - H: Hypergraph - the entity to be drawn - collapse: bool - True if edges should be added if hyper edges are identical - - Returns - ------- - dict - a mapping of vertices in H to integer levels - """ - - D = get_line_graph(H, collapse=collapse) - - levels = {} - - for v in nx.topological_sort(D): - parent_levels = [levels[u] for u, _ in D.in_edges(v)] - levels[v] = max(parent_levels) + 1 if len(parent_levels) else 0 - - return levels
-
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/index.html b/docs/build/_modules/index.html deleted file mode 100644 index 63f60b69..00000000 --- a/docs/build/_modules/index.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - - - Overview: module code — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Overview: module code
  • - - -
  • - -
  • - -
- - -
-
- -
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_modules/reports/descriptive_stats.html b/docs/build/_modules/reports/descriptive_stats.html deleted file mode 100644 index 66ea9889..00000000 --- a/docs/build/_modules/reports/descriptive_stats.html +++ /dev/null @@ -1,629 +0,0 @@ - - - - - - - - - - reports.descriptive_stats — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - - - - -
- -
- - - - - - - - - - - - - - - - - - - -
- -
    - -
  • »
  • - -
  • Module code »
  • - -
  • reports.descriptive_stats
  • - - -
  • - -
  • - -
- - -
-
-
-
- -

Source code for reports.descriptive_stats

-"""
-This module contains methods which compute various distributions for hypergraphs:
-    * Edge size distribution
-    * Node degree distribution
-    * Component size distribution
-    * Toplex size distribution
-    * Diameter
-
-Also computes general hypergraph information: number of nodes, edges, cells, aspect ratio, incidence matrix density
-"""
-from collections import Counter
-import numpy as np
-import networkx as nx
-from hypernetx import *
-from hypernetx.utils.decorators import not_implemented_for
-
-__all__ = [
-    "centrality_stats",
-    "edge_size_dist",
-    "degree_dist",
-    "comp_dist",
-    "s_comp_dist",
-    "toplex_dist",
-    "s_node_diameter_dist",
-    "s_edge_diameter_dist",
-    "info",
-    "info_dict",
-    "dist_stats",
-]
-
-
-
[docs]def centrality_stats(X): - """ - Computes basic centrality statistics for X - - Parameters - ---------- - X : - an iterable of numbers - - Returns - ------- - [min, max, mean, median, standard deviation] : list - List of centrality statistics for X - """ - return [min(X), max(X), np.mean(X), np.median(X), np.std(X)]
- - -
[docs]def edge_size_dist(H, aggregated=False): - """ - Computes edge sizes of a hypergraph. - - Parameters - ---------- - H : Hypergraph - aggregated : - If aggregated is True, returns a dictionary of - edge sizes and counts. If aggregated is False, returns a - list of edge sizes in H. - - Returns - ------- - edge_size_dist : list or dict - List of edge sizes or dictionary of edge size distribution. - - """ - if aggregated: - return Counter(H.edge_size_dist()) - else: - return H.edge_size_dist()
- - -
[docs]def degree_dist(H, aggregated=False): - """ - Computes degrees of nodes of a hypergraph. - - Parameters - ---------- - H : Hypergraph - aggregated : - If aggregated is True, returns a dictionary of - degrees and counts. If aggregated is False, returns a - list of degrees in H. - - Returns - ------- - degree_dist : list or dict - List of degrees or dictionary of degree distribution - """ - if H.nwhy: - distr = H.g.node_size_dist() - else: - distr = [H.degree(n) for n in H.nodes] - if aggregated: - return Counter(distr) - else: - return distr
- - -
[docs]def comp_dist(H, aggregated=False): - """ - Computes component sizes, number of nodes. - - Parameters - ---------- - H : Hypergraph - aggregated : - If aggregated is True, returns a dictionary of - component sizes (number of nodes) and counts. If aggregated - is False, returns a list of components sizes in H. - - Returns - ------- - comp_dist : list or dictionary - List of component sizes or dictionary of component size distribution - - See Also - -------- - s_comp_dist - - """ - - distr = [len(c) for c in H.components()] - if aggregated: - return Counter(distr) - else: - return distr
- - -
[docs]def s_comp_dist(H, s=1, aggregated=False, edges=True, return_singletons=True): - """ - Computes s-component sizes, counting nodes or edges. - - Parameters - ---------- - H : Hypergraph - s : positive integer, default is 1 - aggregated : - If aggregated is True, returns a dictionary of - s-component sizes and counts in H. If aggregated is - False, returns a list of s-component sizes in H. - edges : - If edges is True, the component size is number of edges. - If edges is False, the component size is number of nodes. - return_singletons : bool, optional, default=True - - Returns - ------- - s_comp_dist : list or dictionary - List of component sizes or dictionary of component size distribution in H - - See Also - -------- - comp_dist - - """ - distr = list() - comps = H.s_connected_components( - s=s, edges=edges, return_singletons=return_singletons - ) - - distr = [len(c) for c in comps] - - if aggregated: - return Counter(distr) - else: - return distr
- - -
[docs]@not_implemented_for("static") -def toplex_dist(H, aggregated=False): - """ - - Computes toplex sizes for hypergraph H. - - Parameters - ---------- - H : Hypergraph - aggregated : - If aggregated is True, returns a dictionary of - toplex sizes and counts in H. If aggregated - is False, returns a list of toplex sizes in H. - - Returns - ------- - toplex_dist : list or dictionary - List of toplex sizes or dictionary of toplex size distribution in H - """ - distr = [H.size(e) for e in H.toplexes().edges] - if aggregated: - return Counter(distr) - else: - return distr
- - -
[docs]def s_node_diameter_dist(H): - """ - Parameters - ---------- - H : Hypergraph - - Returns - ------- - s_node_diameter_dist : list - List of s-node-diameters for hypergraph H starting with s=1 - and going up as long as the hypergraph is s-node-connected - """ - i = 1 - diams = [] - while H.is_connected(s=i): - diams.append(H.diameter(s=i)) - i += 1 - return diams
- - -
[docs]def s_edge_diameter_dist(H): - """ - Parameters - ---------- - H : Hypergraph - - Returns - ------- - s_edge_diameter_dist : list - List of s-edge-diameters for hypergraph H starting with s=1 - and going up as long as the hypergraph is s-edge-connected - """ - i = 1 - diams = [] - while H.is_connected(s=i, edges=True): - diams.append(H.edge_diameter(s=i)) - i += 1 - return diams
- - -
[docs]def info(H, node=None, edge=None): - """ - Print a summary of simple statistics for H - - Parameters - ---------- - H : Hypergraph - obj : optional - either a node or edge uid from the hypergraph - dictionary : optional - If True then returns the info as a dictionary rather - than a string - If False (default) returns the info as a string - - Returns - ------- - info : string - Returns a string of statistics of the size, - aspect ratio, and density of the hypergraph. - Print the string to see it formatted. - - """ - if not H.edges.elements: - return f"Hypergraph {H.name} is empty." - report = info_dict(H, node=node, edge=edge) - info = "" - if node: - info += f"Node '{node}' has the following properties:\n" - info += f"Degree: {report['degree']}\n" - info += f"Contained in: {report['membs']}\n" - info += f"Neighbors: {report['neighbors']}" - elif edge: - info += f"Edge '{edge}' has the following properties:\n" - info += f"Size: {report['size']}\n" - info += f"Elements: {report['elements']}" - else: - info += f"Number of Rows: {report['nrows']}\n" - info += f"Number of Columns: {report['ncols']}\n" - info += f"Aspect Ratio: {report['aspect ratio']}\n" - info += f"Number of non-empty Cells: {report['ncells']}\n" - info += f"Density: {report['density']}" - return info
- - -
[docs]def info_dict(H, node=None, edge=None): - """ - Create a summary of simple statistics for H - - Parameters - ---------- - H : Hypergraph - obj : optional - either a node or edge uid from the hypergraph - - Returns - ------- - info_dict : dict - Returns a dictionary of statistics of the size, - aspect ratio, and density of the hypergraph. - - """ - report = dict() - if len(H.edges.elements) == 0: - return {} - - if node: - report["membs"] = list(H.dual().edges[node]) - report["degree"] = len(report["membs"]) - report["neighbors"] = H.neighbors(node) - return report - if edge: - report["size"] = H.size(edge) - report["elements"] = list(H.edges[edge]) - return report - else: - lnodes, ledges = H.shape - M = H.incidence_matrix(index=False) - ncells = M.nnz - - report["nrows"] = lnodes - report["ncols"] = ledges - report["aspect ratio"] = lnodes / ledges - report["ncells"] = ncells - report["density"] = ncells / (lnodes * ledges) - return report
- - -
[docs]def dist_stats(H): - """ - Computes many basic hypergraph stats and puts them all into a single dictionary object - - * nrows = number of nodes (rows in the incidence matrix) - * ncols = number of edges (columns in the incidence matrix) - * aspect ratio = nrows/ncols - * ncells = number of filled cells in incidence matrix - * density = ncells/(nrows*ncols) - * node degree list = degree_dist(H) - * node degree dist = centrality_stats(degree_dist(H)) - * node degree hist = Counter(degree_dist(H)) - * max node degree = max(degree_dist(H)) - * edge size list = edge_size_dist(H) - * edge size dist = centrality_stats(edge_size_dist(H)) - * edge size hist = Counter(edge_size_dist(H)) - * max edge size = max(edge_size_dist(H)) - * comp nodes list = s_comp_dist(H, s=1, edges=False) - * comp nodes dist = centrality_stats(s_comp_dist(H, s=1, edges=False)) - * comp nodes hist = Counter(s_comp_dist(H, s=1, edges=False)) - * comp edges list = s_comp_dist(H, s=1, edges=True) - * comp edges dist = centrality_stats(s_comp_dist(H, s=1, edges=True)) - * comp edges hist = Counter(s_comp_dist(H, s=1, edges=True)) - * num comps = len(s_comp_dist(H)) - - Parameters - ---------- - H : Hypergraph - - Returns - ------- - dist_stats : dict - Dictionary which keeps track of each of the above items (e.g., basic['nrows'] = the number of nodes in H) - """ - stats = H.state_dict.get("dist_stats", None) - if stats is not None: - return H.state_dict["dist_stats"] - else: - cstats = ["min", "max", "mean", "median", "std"] - basic = dict() - - # Number of rows (nodes), columns (edges), and aspect ratio - basic["nrows"] = len(H.nodes) - basic["ncols"] = len(H.edges) - basic["aspect ratio"] = basic["nrows"] / basic["ncols"] - - # Number of cells and density - M = H.incidence_matrix(index=False) - basic["ncells"] = M.nnz - basic["density"] = basic["ncells"] / (basic["nrows"] * basic["ncols"]) - - # Node degree distribution - basic["node degree list"] = sorted(degree_dist(H), reverse=True) - basic["node degree centrality stats"] = dict( - zip(cstats, centrality_stats(basic["node degree list"])) - ) - basic["node degree hist"] = Counter(basic["node degree list"]) - basic["max node degree"] = max(basic["node degree list"]) - - # Edge size distribution - basic["edge size list"] = sorted(H.edge_size_dist(), reverse=True) - basic["edge size centrality stats"] = dict( - zip(cstats, centrality_stats(basic["edge size list"])) - ) - basic["edge size hist"] = Counter(basic["edge size list"]) - basic["max edge size"] = max(basic["edge size hist"]) - - # Component size distribution (nodes) - basic["comp nodes list"] = sorted(s_comp_dist(H, edges=False), reverse=True) - basic["comp nodes hist"] = Counter(basic["comp nodes list"]) - basic["comp nodes centrality stats"] = dict( - zip(cstats, centrality_stats(basic["comp nodes list"])) - ) - - # Component size distribution (edges) - basic["comp edges list"] = sorted(s_comp_dist(H, edges=True), reverse=True) - basic["comp edges hist"] = Counter(basic["comp edges list"]) - basic["comp edges centrality stats"] = dict( - zip(cstats, centrality_stats(basic["comp edges list"])) - ) - - # Number of components - basic["num comps"] = len(basic["comp nodes list"]) - - # # Diameters - # basic['s edge diam list'] = s_edge_diameter_dist(H) - # basic['s node diam list'] = s_node_diameter_dist(H) - if H.isstatic: - H.set_state(dist_stats=basic) - return basic
-
- -
- -
-
- -
- -
-

- © Copyright 2018 Battelle Memorial Institute. - -

-
- - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
-
-
- -
- -
- - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/_static/documentation_options.js b/docs/build/_static/documentation_options.js index 2f08d25f..9fd31e43 100644 --- a/docs/build/_static/documentation_options.js +++ b/docs/build/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '1.0.0', + VERSION: '1.0.1', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/build/algorithms/algorithms.html b/docs/build/algorithms/algorithms.html index 5a70cab4..4549a2f7 100644 --- a/docs/build/algorithms/algorithms.html +++ b/docs/build/algorithms/algorithms.html @@ -7,7 +7,7 @@ - algorithms package — HyperNetX 1.0.0 documentation + algorithms package — HyperNetX 1.0.1 documentation @@ -36,7 +36,6 @@ - @@ -106,9 +105,9 @@
  • Algorithms @@ -197,667 +196,14 @@

    algorithms package

    Submodules

    -
    -

    algorithms.homology_mod2 module

    -
    -

    Homology and Smith Normal Form

    -

    The purpose of computing the Homology groups for data generated -hypergraphs is to identify data sources that correspond to interesting -features in the topology of the hypergraph.

    -

    The elements of one of these Homology groups are generated by \(k\) -dimensional cycles of relationships in the original data that are not -bound together by higher order relationships. Ideally, we want the -briefest description of these cycles; we want a minimal set of -relationships exhibiting interesting cyclic behavior. This minimal set -will be a bases for the Homology group.

    -

    The cyclic relationships in the data are discovered using a boundary -map represented as a matrix. To discover the bases we compute the -Smith Normal Form of the boundary map.

    -
    -

    Homology Mod2

    -

    This module computes the homology groups for data represented as an -abstract simplicial complex with chain groups \(\{C_k\}\) and \(Z_2\) additions. -The boundary matrices are represented as rectangular matrices over \(Z_2\). -These matrices are diagonalized and represented in Smith -Normal Form. The kernel and image bases are computed and the Betti -numbers and homology bases are returned.

    -

    Methods for obtaining SNF for Z/2Z are based on Ferrario’s work: -http://www.dlfer.xyz/post/2016-10-27-smith-normal-form/

    -
    -
    -algorithms.homology_mod2.add_to_column(M, i, j)[source]
    -

    Replaces column i (of M) with logical xor between column i and j

    -
    -
    Parameters
    -
      -
    • M (np.array) – matrix

    • -
    • i (int) – index of column being altered

    • -
    • j (int) – index of column being added to altered

    • -
    -
    -
    Returns
    -

    N

    -
    -
    Return type
    -

    np.array

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.add_to_row(M, i, j)[source]
    -

    Replaces row i with logical xor between row i and j

    -
    -
    Parameters
    -
      -
    • M (np.array) –

    • -
    • i (int) – index of row being altered

    • -
    • j (int) – index of row being added to altered

    • -
    -
    -
    Returns
    -

    N

    -
    -
    Return type
    -

    np.array

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.betti(bd, k=None)[source]
    -

    Generate the kth-betti numbers for a chain complex with boundary -matrices given by bd

    -
    -
    Parameters
    -
      -
    • bd (dict of k-boundary matrices keyed on dimension of domain) –

    • -
    • k (int, list or tuple, optional, default=None) – list must be min value and max value of k values inclusive -if None, then all betti numbers for dimensions of existing cells will be -computed.

    • -
    -
    -
    Returns
    -

    betti – Description

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.betti_numbers(h, k=None)[source]
    -

    Return the kth betti numbers for the simplicial homology of the ASC -associated to h

    -
    -
    Parameters
    -
      -
    • h (hnx.Hypergraph) – Hypergraph to compute the betti numbers from

    • -
    • k (int or list, optional, default=None) – list must be min value and max value of k values inclusive -if None, then all betti numbers for dimensions of existing cells will be -computed.

    • -
    -
    -
    Returns
    -

    betti – A dictionary of betti numbers keyed by dimension

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.bkMatrix(km1basis, kbasis)[source]
    -

    Compute the boundary map from \(C_{k-1}\)-basis to \(C_k\) basis with -respect to \(Z_2\)

    -
    -
    Parameters
    -
      -
    • km1basis (indexable iterable) – Ordered list of \(k-1\) dimensional cell

    • -
    • kbasis (indexable iterable) – Ordered list of \(k\) dimensional cells

    • -
    -
    -
    Returns
    -

    bk – boundary matrix in \(Z_2\) stored as boolean

    -
    -
    Return type
    -

    np.array

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.boundary_group(image_basis)[source]
    -

    Returns a csr_matrix with rows corresponding to the elements of the -group generated by image basis over \(\mathbb{Z}_2\)

    -
    -
    Parameters
    -

    image_basis (numpy.ndarray or scipy.sparse.csr_matrix) – 2d-array of basis elements

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    scipy.sparse.csr_matrix

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.chain_complex(h, k=None)[source]
    -

    Compute the k-chains and k-boundary maps required to compute homology -for all values in k

    -
    -
    Parameters
    -
      -
    • h (hnx.Hypergraph) –

    • -
    • k (int or list of length 2, optional, default=None) – k must be an integer greater than 0 or a list of -length 2 indicating min and max dimensions to be -computed. eg. if k = [1,2] then 0,1,2,3-chains -and boundary maps for k=1,2,3 will be returned, -if None than k = [1,max dimension of edge in h]

    • -
    -
    -
    Returns
    -

    C, bd – C is a dictionary of lists -bd is a dictionary of numpy arrays

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.homology_basis(bd, k=None, boundary=False, **kwargs)[source]
    -

    Compute a basis for the kth-simplicial homology group, \(H_k\), defined by a -chain complex \(C\) with boundary maps given by bd \(= \{k:\partial_k\)}$

    -
    -
    Parameters
    -
      -
    • bd (dict) – dict of boundary matrices on k-chains to k-1 chains keyed on k -if krange is a tuple then all boundary matrices k in [krange[0],..,krange[1]] -inclusive must be in the dictionary

    • -
    • k (int or list of ints, optional, default=None) – k must be a positive integer or a list of -2 integers indicating min and max dimensions to be -computed, if none given all homology groups will be computed from -available boundary matrices in bd

    • -
    • boundary (bool) – option to return a basis for the boundary group from each dimension. -Needed to compute the shortest generators in the homology group.

    • -
    -
    -
    Returns
    -

      -
    • basis (dict) – dict of generators as 0-1 tuples keyed by dim -basis for dimension k will be returned only if bd[k] and bd[k+1] have -been provided.

    • -
    • im (dict) – dict of boundary group generators keyed by dim

    • -
    -

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.hypergraph_homology_basis(h, k=None, shortest=False, interpreted=True)[source]
    -

    Computes the kth-homology groups mod 2 for the ASC -associated with the hypergraph h for k in krange inclusive

    -
    -
    Parameters
    -
      -
    • h (hnx.Hypergraph) –

    • -
    • k (int or list of length 2, optional, default = None) – k must be an integer greater than 0 or a list of -length 2 indicating min and max dimensions to be -computed

    • -
    • shortest (bool, optional, default=False) – option to look for shortest representative for each coset in the -homology group, only good for relatively small examples

    • -
    • interpreted (bool, optional, default = True) – if True will return an explicit basis in terms of the k-chains

    • -
    -
    -
    Returns
    -

      -
    • basis (list) – list of generators as k-chains as boolean vectors

    • -
    • interpreted_basis – lists of kchains in basis

    • -
    -

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.interpret(Ck, arr, labels=None)[source]
    -

    Returns the data as represented in Ck associated with the arr

    -
    -
    Parameters
    -
      -
    • Ck (list) – a list of k-cells being referenced by arr

    • -
    • arr (np.array) – array of 0-1 vectors

    • -
    • labels (dict, optional) – dictionary of labels to associate to the nodes in the cells

    • -
    -
    -
    Returns
    -

    list of k-cells referenced by data in Ck

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.kchainbasis(h, k)[source]
    -

    Compute the set of k dimensional cells in the abstract simplicial -complex associated with the hypergraph.

    -
    -
    Parameters
    -
      -
    • h (hnx.Hypergraph) –

    • -
    • k (int) – dimension of cell

    • -
    -
    -
    Returns
    -

    an ordered list of kchains represented as tuples of length k+1

    -
    -
    Return type
    -

    list

    -
    -
    -
    -

    See also

    -

    hnx.hypergraph.toplexes

    -
    -

    Notes

    -
      -
    • Method works best if h is simple [Berge], i.e. no edge contains another and there are no duplicate edges (toplexes).

    • -
    • Hypergraph node uids must be sortable.

    • -
    -
    - -
    -
    -algorithms.homology_mod2.logical_dot(ar1, ar2)[source]
    -

    Returns the boolean equivalent of the dot product mod 2 on two 1-d arrays of -the same length.

    -
    -
    Parameters
    -
      -
    • ar1 (numpy.ndarray) – 1-d array

    • -
    • ar2 (numpy.ndarray) – 1-d array

    • -
    -
    -
    Returns
    -

    boolean value associated with dot product mod 2

    -
    -
    Return type
    -

    bool

    -
    -
    Raises
    -

    HyperNetXError – If arrays are not of the same length an error will be raised.

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.logical_matadd(mat1, mat2)[source]
    -

    Returns the boolean equivalent of matrix addition mod 2 on two -binary arrays stored as type boolean

    -
    -
    Parameters
    -
      -
    • mat1 (np.ndarray) – 2-d array of boolean values

    • -
    • mat2 (np.ndarray) – 2-d array of boolean values

    • -
    -
    -
    Returns
    -

    mat – boolean matrix equivalent to the mod 2 matrix addition of the -matrices as matrices over Z/2Z

    -
    -
    Return type
    -

    np.ndarray

    -
    -
    Raises
    -

    HyperNetXError – If dimensions are not equal an error will be raised.

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.logical_matmul(mat1, mat2)[source]
    -

    Returns the boolean equivalent of matrix multiplication mod 2 on two -binary arrays stored as type boolean

    -
    -
    Parameters
    -
      -
    • mat1 (np.ndarray) – 2-d array of boolean values

    • -
    • mat2 (np.ndarray) – 2-d array of boolean values

    • -
    -
    -
    Returns
    -

    mat – boolean matrix equivalent to the mod 2 matrix multiplication of the -matrices as matrices over Z/2Z

    -
    -
    Return type
    -

    np.ndarray

    -
    -
    Raises
    -

    HyperNetXError – If inner dimensions are not equal an error will be raised.

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.matmulreduce(arr, reverse=False)[source]
    -

    Recursively applies a ‘logical multiplication’ to a list of boolean arrays.

    -

    For arr = [arr[0],arr[1],arr[2]…arr[n]] returns product arr[0]arr[1]…arr[n] -If reverse = True, returns product arr[n]arr[n-1]…arr[0]

    -
    -
    Parameters
    -
      -
    • arr (list of np.array) – list of nxm matrices represented as np.array

    • -
    • reverse (bool, optional) – order to multiply the matrices

    • -
    -
    -
    Returns
    -

    P – Product of matrices in the list

    -
    -
    Return type
    -

    np.array

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.reduced_row_echelon_form_mod2(M)[source]
    -

    Computes the invertible transformation matrices needed to compute -the reduced row echelon form of M modulo 2

    -
    -
    Parameters
    -

    M (np.array) – a rectangular matrix with elements in \(Z_2\)

    -
    -
    Returns
    -

    L, S, Linv – LM = S where S is the reduced echelon form of M -and M = LinvS

    -
    -
    Return type
    -

    np.arrays

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.smith_normal_form_mod2(M)[source]
    -

    Computes the invertible transformation matrices needed to compute the -Smith Normal Form of M modulo 2

    -
    -
    Parameters
    -
      -
    • M (np.array) – a rectangular matrix with data type bool

    • -
    • track (bool) – if track=True will print out the transformation as Z/2Z matrix as it -discovers L[i] and R[j]

    • -
    -
    -
    Returns
    -

    L, R, S, Linv – LMR = S is the Smith Normal Form of the matrix M.

    -
    -
    Return type
    -

    np.arrays

    -
    -
    -
    -

    Note

    -

    Given a mxn matrix \(M\) with -entries in \(Z_2\) we start with the equation: \(L M R = S\), where -\(L = I_m\), and \(R=I_n\) are identity matrices and \(S = M\). We -repeatedly apply actions to the left and right side of the equation -to transform S into a diagonal matrix. -For each action applied to the left side we apply its inverse -action to the right side of I_m to generate \(L^{-1}\). -Finally we verify: -\(L M R = S\) and \(LLinv = I_m\).

    -
    -
    - -
    -
    -algorithms.homology_mod2.swap_columns(i, j, *args)[source]
    -

    Swaps ith and jth column of each matrix in args -Returns a list of new matrices

    -
    -
    Parameters
    -
      -
    • i (int) –

    • -
    • j (int) –

    • -
    • args (np.arrays) –

    • -
    -
    -
    Returns
    -

    list of copies of args with ith and jth row swapped

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -algorithms.homology_mod2.swap_rows(i, j, *args)[source]
    -

    Swaps ith and jth row of each matrix in args -Returns a list of new matrices

    -
    -
    Parameters
    -
      -
    • i (int) –

    • -
    • j (int) –

    • -
    • args (np.arrays) –

    • -
    -
    -
    Returns
    -

    list of copies of args with ith and jth row swapped

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -
    -
    -

    algorithms.s_centrality_measures module

    -
    -

    S-Centrality Measures

    -

    We generalize graph metrics to s-metrics for a hypergraph by using its s-connected -components. This is accomplished by computing the s edge-adjacency matrix and -constructing the corresponding graph of the matrix. We then use existing graph metrics -on this representation of the hypergraph. In essence we construct an s-line graph -corresponding to the hypergraph on which to apply our methods.

    -

    S-Metrics for hypergraphs are discussed in depth in: -Aksoy, S.G., Joslyn, C., Ortiz Marrero, C. et al. Hypernetwork science via high-order hypergraph walks. -EPJ Data Sci. 9, 16 (2020). https://doi.org/10.1140/epjds/s13688-020-00231-0

    -
    -
    -algorithms.s_centrality_measures.s_betweenness_centrality(H, s=1, edges=True, normalized=True, return_singletons=True, use_nwhy=True)[source]
    -
    -

    A centrality measure for an s-edge(node) subgraph of H based on shortest paths. -Equals the betweenness centrality of vertices in the edge(node) s-linegraph.

    -

    In a graph (2-uniform hypergraph) the betweenness centrality of a vertex \(v\) -is the ratio of the number of non-trivial shortest paths between any pair of -vertices in the graph that pass through \(v\) divided by the total number of -non-trivial shortest paths in the graph.

    -

    The centrality of edge to all shortest s-edge paths -\(V\) = the set of vertices in the linegraph. -\(\sigma(s,t)\) = the number of shortest paths between vertices \(s\) and \(t\). -\(\sigma(s, t|v)\) = the number of those paths that pass through vertex \(v\) -$$c_B(v) =sum_{s

    -
    -

    eq t in V} -rac{sigma(s, t|v)}{sigma(s, t)}$$

    -
    -

    H : hnx.Hypergraph -s : int

    -
    -

    s connectedness requirement

    -
    -
    -
    edgesbool, optional

    determines if edge or node linegraph

    -
    -
    normalized

    bool, default=False, -If true the betweenness values are normalized by 2/((n-1)(n-2)), -where n is the number of edges in H

    -
    -
    return_singletonsbool, optional

    if False will ignore singleton components of linegraph

    -
    -
    -
    -
    -
    : dict

    A dictionary of s-betweenness centrality value of the edges.

    -
    -
    -
    -
    -
    - -
    -
    -algorithms.s_centrality_measures.s_closeness_centrality(H, s=1, edges=True, return_singletons=True, source=None, use_nwhy=True)[source]
    -
    -

    In a connected component the reciprocal of the sum of the distance between an -edge(node) and all other edges(nodes) in the component times the number of edges(nodes) -in the component minus 1.

    -

    \(V\) = the set of vertices in the linegraph. -\(n = |V|\) -\(d\) = shortest path distance -$$C(u) =

    -
    -

    rac{n - 1}{sum_{v -eq u in V} d(v, u)}$$

    -
    -

    H : hnx.Hypergraph

    -

    s : int, optional

    -
    -
    edgesbool, optional

    Indicates if method should compute edge linegraph (default) or node linegraph.

    -
    -
    return_singletonsbool, optional

    Indicates if method should return values for singleton components.

    -
    -
    sourcestr, optional

    Identifier of node or edge of interest for computing centrality

    -
    -
    use_nwhybool, optional

    If true will use the NWHy library if available.

    -
    -
    -
    -
    -
    : dict or float

    returns the s-closeness centrality value of the edges(nodes). -If source=None a dictionary of values for each s-edge in H is returned. -If source then a single value is returned.

    -
    -
    -
    -
    -
    - -
    -
    -algorithms.s_centrality_measures.s_eccentricity(H, s=1, edges=True, source=None, return_singletons=True, use_nwhy=True)[source]
    -

    The length of the longest shortest path from a vertex \(u\) to every other vertex in the linegraph. -\(V\) = set of vertices in the linegraph -\(d\) = shortest path distance -$$ ext{s-ecc}(u) = ext{max}{d(u,v): v in V} $$

    -
    -
    Parameters
    -
      -
    • H (hnx.Hypergraph) –

    • -
    • s (int, optional) –

    • -
    • edges (bool, optional) – Indicates if method should compute edge linegraph (default) or node linegraph.

    • -
    • return_singletons (bool, optional) – Indicates if method should return values for singleton components.

    • -
    • source (str, optional) – Identifier of node or edge of interest for computing centrality

    • -
    • use_nwhy (bool, optional) – If true will use the NWHy library if available.

    • -
    -
    -
    Returns
    -

    returns the s-eccentricity value of the edges(nodes). -If source=None a dictionary of values for each s-edge in H is returned. -If source then a single value is returned.

    -
    -
    Return type
    -

    dict or float

    -
    -
    -
    - -
    -
    -algorithms.s_centrality_measures.s_harmonic_centrality(H, s=1, edges=True, source=None, normalized=False, return_singletons=True, use_nwhy=True)[source]
    -
    -

    A centrality measure for an s-edge subgraph of H. A value equal to 1 means the s-edge -intersects every other s-edge in H. All values range between 0 and 1. -Edges of size less than s return 0. If H contains only one s-edge a 0 is returned.

    -

    The denormalized reciprocal of the harmonic mean of all distances from \(u\) to all other vertices. -\(V\) = the set of vertices in the linegraph. -\(d\) = shortest path distance -$$C(u) = sum_{v

    -
    -

    eq u in V} -rac{1}{d(v, u)}$$

    -
    -

    Normalized this becomes: -$$C(u) = sum_{v

    -
    -

    eq u in V} -rac{1}{d(v, u)}cdot -rac{2}{(n-1)(n-2)}$$

    -
    -

    where \(n\) is the number vertices.

    -

    H : hnx.Hypergraph

    -

    s : int, optional

    -
    -
    edgesbool, optional

    Indicates if method should compute edge linegraph (default) or node linegraph.

    -
    -
    return_singletonsbool, optional

    Indicates if method should return values for singleton components.

    -
    -
    sourcestr, optional

    Identifier of node or edge of interest for computing centrality

    -
    -
    use_nwhybool, optional

    If true will use the NWHy library if available.

    -
    -
    -
    -
    -
    : dict or float

    returns the s-harmonic closeness centrality value of the edges, a number between 0 and 1 inclusive. -If source=None a dictionary of values for each s-edge in H is returned. -If source then a single value is returned.

    -
    -
    -
    -
    -
    - -
    -
    -algorithms.s_centrality_measures.s_harmonic_closeness_centrality(H, s=1, edge=None, use_nwhy=True)[source]
    -
    - +
    +

    algorithms.homology_mod2 module

    +
    +

    algorithms.s_centrality_measures module

    -
    -

    Module contents

    +
    +

    Module contents

    diff --git a/docs/build/algorithms/modules.html b/docs/build/algorithms/modules.html index f1aa2059..b14efc19 100644 --- a/docs/build/algorithms/modules.html +++ b/docs/build/algorithms/modules.html @@ -7,7 +7,7 @@ - algorithms — HyperNetX 1.0.0 documentation + algorithms — HyperNetX 1.0.1 documentation @@ -189,18 +189,9 @@

    algorithmsalgorithms package

  • diff --git a/docs/build/classes/classes.html b/docs/build/classes/classes.html index 973749c0..7c35d04f 100644 --- a/docs/build/classes/classes.html +++ b/docs/build/classes/classes.html @@ -7,7 +7,7 @@ - classes package — HyperNetX 1.0.0 documentation + classes package — HyperNetX 1.0.1 documentation @@ -104,10 +104,10 @@
  • Hypergraphs @@ -197,2692 +197,17 @@

    classes package

    Submodules

    -
    -

    classes.entity module

    -
    -
    -class classes.entity.Entity(uid, elements=[], entity=None, **props)[source]
    -

    Bases: object

    -

    Base class for objects used in building network-like objects including -Hypergraphs, Posets, Cell Complexes.

    -
    -
    Parameters
    -
      -
    • uid (hashable) – a unique identifier

    • -
    • elements (list or dict, optional, default: None) – a list of entities with identifiers different than uid and/or -hashables different than uid, see Honor System

    • -
    • entity (Entity) – an Entity object to be cloned into a new Entity with uid. If the uid is the same as -Entity.uid then the entities will not be distinguishable and error will be raised. -The elements in the signature will be added to the cloned entity.

    • -
    • props (keyword arguments, optional, default: {}) – properties belonging to the entity added as key=value pairs. -Both key and value must be hashable.

    • -
    -
    -
    -

    Notes

    -

    An Entity is a container-like object, which has a unique identifier and -may contain elements and have properties. -The Entity class was created as a generic object providing structure for -Hypergraph nodes and edges.

    - -

    Honor System

    -

    HyperNetX has an Honor System that applies to Entity uid values. -Two entities are equal if their __dict__ objects match. -For performance reasons many methods distinguish entities by their uids. -It is, therefore, up to the user to ensure entities with the same uids are indeed the same. -Not doing so may cause undesirable side effects. -In particular, the methods in the Hypergraph class assume distinct nodes and edges -have distinct uids.

    -

    Examples

    -
    >>> x = Entity('x')
    ->>> y = Entity('y',[x])
    ->>> z = Entity('z',[x,y],weight=1)
    ->>> z
    -Entity(z,['y', 'x'],{'weight': 1})
    ->>> z.uid
    -'z'
    ->>> z.elements
    -{'x': Entity(x,[],{}), 'y': Entity(y,['x'],{})}
    ->>> z.properties
    -{'weight': 1}
    ->>> z.children
    -{'x'}
    ->>> x.memberships
    -{'y': Entity(y,['x'],{}), 'z': Entity(z,['y', 'x'],{'weight': 1})}
    ->>> z.fullregistry()
    -{'x': Entity(x,[],{}), 'y': Entity(y,['x'],{})}
    -
    -
    -
    -

    See also

    -

    EntitySet

    -
    -
    -
    -add(*args)[source]
    -

    Adds unpacked args to entity elements. Depends on add_element()

    -
    -
    Parameters
    -

    args (One or more entities or hashables) –

    -
    -
    Returns
    -

    self

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    -

    Note

    -

    Adding an element to an object in a hypergraph will not add the -element to the hypergraph and will cause an error. Use Hypergraph.add_edge -or Hypergraph.add_node_to_edge instead.

    -
    -
    - -
    -
    -add_element(item)[source]
    -

    Adds item to entity elements and adds entity to item.memberships.

    -
    -
    Parameters
    -

    item (hashable or Entity) – If hashable, will be replaced with empty Entity using hashable as uid

    -
    -
    Returns
    -

    self

    -
    -
    Return type
    -

    Entity

    -
    -
    -

    Notes

    -

    If item is in entity elements, no new element is added but properties -will be updated. -If item is in complete_registry(), only the item already known to self will be added. -This method employs the Honor System since membership in complete_registry is checked -using the item’s uid. It is assumed that the user will only use the same uid -for identical instances within the entities registry.

    -
    - -
    -
    -add_elements_from(arg_set)[source]
    -

    Similar to add() it allows for adding from an interable.

    -
    -
    Parameters
    -

    arg_set (Iterable of hashables or entities) –

    -
    -
    Returns
    -

    self

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    - -
    -
    -property children
    -

    Set of uids of the elements of elements of entity.

    -

    To return set of ids for deeper level use: -Entity.levelset(level).keys() -see: Entity.levelset()

    -
    - -
    -
    -clone(newuid)[source]
    -

    Returns shallow copy of entity with newuid. Entity’s elements will -belong to two distinct Entities.

    -
    -
    Parameters
    -

    newuid (hashable) – Name of the new entity

    -
    -
    Returns
    -

    clone

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    - -
    -
    -complete_registry()[source]
    -

    A dictionary of all entities appearing in any level of -entity

    -
    -
    Returns
    -

    complete_registry

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -depth(max_depth=10)[source]
    -

    Returns the number of nonempty level sets of level <= max_depth

    -
    -
    Parameters
    -

    max_depth (int, optional, default: 10) – If full depth is desired set max_depth to number of entities in -system + 1.

    -
    -
    Returns
    -

    depth – If max_depth is exceeded output will be numpy infinity. -If there is a cycle output will be numpy infinity.

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -property elements
    -

    Dictionary of elements belonging to entity.

    -
    - -
    -
    -fullregistry(lastlevel=10, firstlevel=1)[source]
    -

    A dictionary of all entities appearing in levels firstlevel -to lastlevel.

    -
    -
    Parameters
    -
      -
    • lastlevel (int, optional, default: 10) –

    • -
    • firstlevel (int, optional, default: 1) –

    • -
    -
    -
    Returns
    -

    fullregistry

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -property incidence_dict
    -

    element.uidset for each element in entity

    -

    To return an incidence dictionary of all nested entities in entity -use nested_incidence_dict

    -
    -
    Type
    -

    Dictionary of element.uid

    -
    -
    -
    - -
    -
    -intersection(other)[source]
    -

    A dictionary of elements belonging to entity and other.

    -
    -
    Parameters
    -

    other (Entity) –

    -
    -
    Returns
    -

    Dictionary of elements

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -property is_bipartite
    -

    Returns boolean indicating if the entity satisfies the Bipartite Condition

    -
    - -
    -
    -property is_empty
    -

    Boolean indicating if entity.elements is empty

    -
    - -
    -
    -level(item, max_depth=10)[source]
    -

    The first level where item appears in self.

    -
    -
    Parameters
    -
      -
    • item (hashable) – uid for an entity

    • -
    • max_depth (int, default: 10) – last level to check for entity

    • -
    -
    -
    Returns
    -

    level

    -
    -
    Return type
    -

    int

    -
    -
    -
    -

    Note

    -

    Item must be the uid of an entity listed -in fullregistry()

    +
    +

    classes.entity module

    -
    - -
    -
    -levelset(k=1)[source]
    -

    A dictionary of level k of self.

    -
    -
    Parameters
    -

    k (int, optional, default: 1) –

    -
    -
    Returns
    -

    levelset

    -
    -
    Return type
    -

    dict

    -
    -
    -
    -

    Note

    -

    An Entity contains other entities, hence the relationships between entities -and their elements may be represented in a directed graph with entity as root. -The levelsets are sets of entities which make up the elements appearing at -a certain level.

    +
    +

    classes.hypergraph module

    -
    - -
    -
    -property memberships
    -

    Dictionary of elements to which entity belongs.

    -

    This assignment is done on construction and controlled by -Entity.add_element() -and Entity.remove_element() methods.

    -
    - -
    -
    -static merge_entities(name, ent1, ent2)[source]
    -

    Merge two entities making sure they do not conflict.

    -
    -
    Parameters
    -
      -
    • name (hashable) –

    • -
    • ent1 (Entity) – First entity to have elements and properties added to new -entity

    • -
    • ent2 (Entity) – elements of ent2 will be checked against ent1.complete_registry() -and only nonexisting elements will be added using add() method. -Properties of ent2 will update properties of ent1 in new entity.

    • -
    -
    -
    Returns
    -

    a new entity

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    - -
    -
    -nested_incidence_dict(level=10)[source]
    -

    Returns a nested dictionary with keys up to level

    -
    -
    Parameters
    -

    level (int, optional, default: 10) – If level<=1, returns the incidence_dict.

    -
    -
    Returns
    -

    nested_incidence_dict

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -property properties
    -

    Dictionary of properties of entity

    -
    - -
    -
    -property registry
    -

    Entity pairs for children entity.

    -

    To return a dictionary of all entities at all depths -Entity.complete_registry()

    -
    -
    Type
    -

    Dictionary of uid

    -
    -
    -
    - -
    -
    -remove(*args)[source]
    -

    Removes args from entitie’s elements if they belong. -Does nothing with args not in entity.

    -
    -
    Parameters
    -

    args (One or more hashables or entities) –

    -
    -
    Returns
    -

    self

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    - -
    -
    -remove_element(item)[source]
    -

    Removes item from entity and reference to entity from -item.memberships

    -
    -
    Parameters
    -

    item (Hashable or Entity) –

    -
    -
    Returns
    -

    self

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    - -
    -
    -remove_elements_from(arg_set)[source]
    -

    Similar to remove(). Removes elements in arg_set.

    -
    -
    Parameters
    -

    arg_set (Iterable of hashables or entities) –

    -
    -
    Returns
    -

    self

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    - -
    -
    -restrict_to(element_subset, name=None)[source]
    -

    Shallow copy of entity removing elements not in element_subset.

    -
    -
    Parameters
    -
      -
    • element_subset (iterable) – A subset of entities elements

    • -
    • name (hashable, optional) – If not given, a name is generated to reflect entity uid

    • -
    -
    -
    Returns
    -

    New Entity – Could be empty.

    -
    -
    Return type
    -

    Entity

    -
    -
    -
    - -
    -
    -size()[source]
    -

    Returns the number of elements in entity

    -
    - -
    -
    -property uid
    -

    String identifier for entity

    -
    - -
    -
    -property uidset
    -

    Set of uids of elements of entity.

    -
    - -
    - -
    -
    -class classes.entity.EntitySet(uid, elements=[], **props)[source]
    -

    Bases: classes.entity.Entity

    -
    -
    Parameters
    -
      -
    • uid (hashable) – a unique identifier

    • -
    • elements (list or dict, optional, default: None) – a list of entities with identifiers different than uid and/or -hashables different than uid, see Honor System

    • -
    • props (keyword arguments, optional, default: {}) – properties belonging to the entity added as key=value pairs. -Both key and value must be hashable.

    • -
    -
    -
    -

    Notes

    -

    The EntitySet class was created to distinguish Entities satifying the Bipartite Condition.

    -

    Bipartite Condition

    -

    Entities that are elements of the same EntitySet, may not contain each other as elements. -The elements and children of an EntitySet generate a specific partition for a bipartite graph. -The partition is isomorphic to a Hypergraph where the elements correspond to hyperedges and -the children correspond to the nodes. EntitySets are the basic objects used to construct hypergraphs -in HNX.

    -

    Example:

    -
    >>> y = Entity('y')
    ->>> x = Entity('x')
    ->>> x.add(y)
    ->>> y.add(x)
    ->>> w = EntitySet('w',[x,y])
    -HyperNetXError: Error: Fails the Bipartite Condition for EntitySet.
    -y references a child of an existing Entity in the EntitySet.
    -
    -
    -
    -
    -add(*args)[source]
    -

    Adds args to entityset’s elements, checking to make sure no self references are -made to element ids. -Ensures Bipartite Condition of EntitySet.

    -
    -
    Parameters
    -

    args (One or more entities or hashables) –

    -
    -
    Returns
    -

    self

    -
    -
    Return type
    -

    EntitySet

    -
    -
    -
    - -
    -
    -clone(newuid)[source]
    -

    Returns shallow copy of entityset with newuid. Entityset’s -elements will belong to two distinct entitysets.

    -
    -
    Parameters
    -

    newuid (hashable) – Name of the new entityset

    -
    -
    Returns
    -

    clone

    -
    -
    Return type
    -

    EntitySet

    -
    -
    -
    - -
    -
    -collapse_identical_elements(newuid, return_equivalence_classes=False)[source]
    -

    Returns a deduped copy of the entityset, using representatives of equivalence classes as element keys. -Two elements of an EntitySet are collapsed if they share the same children.

    -
    -
    Parameters
    -
      -
    • newuid (hashable) –

    • -
    • return_equivalence_classes (boolean, default=False) – If True, return a dictionary of equivalence classes keyed by new edge names

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    EntitySet

    -
    -
    -
    -
    eq_classesdict

    if return_equivalence_classes = True

    -
    -
    -

    Notes

    -

    Treats elements of the entityset as equal if they have the same uidsets. Using this -as an equivalence relation, the entityset’s uidset is partitioned into equivalence classes. -The equivalent elements are identified using a single entity by using the -frozenset of uids associated to these elements as the uid for the new element -and dropping the properties. -If use_reps is set to True a representative element of the equivalence class is -used as identifier instead of the frozenset.

    -

    Example:

    -
    >>> E = EntitySet('E',elements=[Entity('E1', ['a','b']),Entity('E2',['a','b'])])
    ->>> E.incidence_dict
    -{'E1': {'a', 'b'}, 'E2': {'a', 'b'}}
    ->>> E.collapse_identical_elements('_',).incidence_dict
    -{'E2': {'a', 'b'}}
    -
    -
    -
    - -
    -
    -incidence_matrix(sparse=True, index=False)[source]
    -

    An incidence matrix for the EntitySet indexed by children x uidset.

    -
    -
    Parameters
    -
      -
    • sparse (boolean, optional, default: True) –

    • -
    • index (boolean, optional, default False) – If True return will include a dictionary of children uid : row number -and element uid : column number

    • -
    -
    -
    Returns
    -

      -
    • incidence_matrix (scipy.sparse.csr.csr_matrix or np.ndarray)

    • -
    • row dictionary (dict) – Dictionary identifying row with item in entityset’s children

    • -
    • column dictionary (dict) – Dictionary identifying column with item in entityset’s uidset

    • -
    -

    -
    -
    -

    Notes

    -

    Example:

    -
    >>> E = EntitySet('E',{'a':{1,2,3},'b':{2,3},'c':{1,4}})
    ->>> E.incidence_matrix(sparse=False, index=True)
    -(array([[0, 1, 1],
    -        [1, 1, 0],
    -        [1, 1, 0],
    -        [0, 0, 1]]), {0: 1, 1: 2, 2: 3, 3: 4}, {0: 'b', 1: 'a', 2: 'c'})
    -
    -
    -
    - -
    -
    -restrict_to(element_subset, name=None)[source]
    -

    Shallow copy of entityset removing elements not in element_subset.

    -
    -
    Parameters
    -
      -
    • element_subset (iterable) – A subset of the entityset’s elements

    • -
    • name (hashable, optional) – If not given, a name is generated to reflect entity uid

    • -
    -
    -
    Returns
    -

    new entityset – Could be empty.

    -
    -
    Return type
    -

    EntitySet

    -
    -
    -
    -

    See also

    -

    Entity.restrict_to

    -
    -
    - -
    - -
    -
    -

    classes.hypergraph module

    -
    -
    -class classes.hypergraph.Hypergraph(setsystem=None, name=None, static=False, use_nwhy=False, filepath=None)[source]
    -

    Bases: object

    -

    Hypergraph H = (V,E) references a pair of disjoint sets: -V = nodes (vertices) and E = (hyper)edges.

    -

    An HNX Hypergraph is either dynamic or static. -Dynamic hypergraphs can change by adding or subtracting objects -from them. Static hypergraphs require that all of the nodes and edges -be known at creation. A hypergraph is dynamic by default.

    -

    Dynamic hypergraphs require the user to keep track of its objects, -by using a unique names for each node and edge. This allows for multi-edge graphs and -inseperable nodes.

    -

    For example: Let V = {1,2,3} and E = {e1,e2,e3}, -where e1 = {1,2}, e2 = {1,2}, and e3 = {1,2,3}. -The edges e1 and e2 contain the same set of nodes and yet -are distinct and must be distinguishable within H.

    -

    In a dynamic hypergraph each node and edge is -instantiated as an Entity and given an identifier or uid. Entities -keep track of inclusion relationships and can be nested. Since -hypergraphs can be quite large, only the entity identifiers will be used -for computation intensive methods, this means the user must take care -to keep a one to one correspondence between their set of uids and -the objects in their hypergraph. See Honor System -Dynamic hypergraphs are most practical for small to modestly sized -hypergraphs (<1000 objects).

    -

    Static hypergraphs store node and edge information in numpy arrays and -are immutable. Each node and edge receives a class generated internal -identifier used for computations so do not require the user to create -different ids for nodes and edges. To create a static hypergraph set -static = True in the signature.

    -

    We will create hypergraphs in multiple ways:

    -
      -
    1. As an empty instance:

      -
      >>> H = hnx.Hypergraph()
      ->>> H.nodes, H.edges
      -({}, {})
      -
      -
      -
    2. -
    3. From a dictionary of iterables (elements of iterables must be of type hypernetx.Entity or hashable):

      -
      >>> H = Hypergraph({'a':[1,2,3],'b':[4,5,6]})
      ->>> H.nodes, H.edges
      -# output: (EntitySet(_:Nodes,[1, 2, 3, 4, 5, 6],{}), EntitySet(_:Edges,['b', 'a'],{}))
      -
      -
      -
    4. -
    5. From an iterable of iterables: (elements of iterables must be of type hypernetx.Entity or hashable):

      -
      >>> H = Hypergraph([{'a','b'},{'b','c'},{'a','c','d'}])
      ->>> H.nodes, H.edges
      -# output: (EntitySet(_:Nodes,['d', 'b', 'c', 'a'],{}), EntitySet(_:Edges,['_1', '_2', '_0'],{}))
      -
      -
      -
    6. -
    7. From a hypernetx.EntitySet or StaticEntitySet:

      -
      >>> a = Entity('a',{1,2}); b = Entity('b',{2,3})
      ->>> E = EntitySet('sample',elements=[a,b])
      ->>> H = Hypergraph(E)
      ->>> H.nodes, H.edges.
      -# output: (EntitySet(_:Nodes,[1, 2, 3],{}), EntitySet(_:Edges,['b', 'a'],{}))
      -
      -
      -
    8. -
    -

    All of these constructions apply for both dynamic and static hypergraphs. To -create a static hypergraph set the parameter static=True. In addition a static -hypergraph is automatically created if a StaticEntity, StaticEntitySet, or pandas.DataFrame object -is passed to the Hypergraph constructor.

    -
      -
    1. -
      From a pandas.DataFrame. The dataframe must have at least two columns with headers and there can be no nans.
      -
      By default the first column corresponds to the edge names and the second column to the node names.
      -
      You can specify the columns by restricting the dataframe to the columns of interest in the order:
      -
      hnx.Hypergraph(df[[edge_column_name,node_column_name]])
      -
      See Colab Tutorials Tutorial 6 - Static Hypergraphs and Entities for additional information.
      -
      -
    2. -
    -
    -
    Parameters
    -
      -
    • setsystem ((optional) EntitySet, StaticEntitySet, dict, iterable, pandas.dataframe, default: None) – See notes above for setsystem requirements.

    • -
    • name (hashable, optional, default: None) – If None then a placeholder ‘_’ will be inserted as name

    • -
    • static (boolean, optional, default: False) – If True the hypergraph will be immutable, edges and nodes may not be changed.

    • -
    • use_nwhy (boolean, optional, default = False) – If True hypergraph will be static and computations will be done using -C++ backend offered by NWHypergraph. This requires installation of the -NWHypergraph C++ library. Please see the NWHy documentation for more information.

    • -
    -
    -
    -
    -
    -add_edge(edge)[source]
    -

    Adds a single edge to hypergraph.

    -
    -
    Parameters
    -

    edge (hashable or Entity) – If hashable the edge returned will be empty.

    -
    -
    Returns
    -

    hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -

    Notes

    -

    When adding an edge to a hypergraph children must be removed -so that nodes do not have elements. -Each node (element of edge) must be instantiated as a node, -making sure its uid isn’t already present in the self. -If an added edge contains nodes that cannot be added to hypergraph -then an error will be raised.

    -
    - -
    -
    -add_edges_from(edge_set)[source]
    -

    Add edges to hypergraph.

    -
    -
    Parameters
    -

    edge_set (iterable of hashables or Entities) – For hashables the edges returned will be empty.

    -
    -
    Returns
    -

    hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -add_node_to_edge(node, edge)[source]
    -

    Adds node to an edge in hypergraph edges

    -
    -
    Parameters
    -
      -
    • node (hashable or Entity) – If Entity, only uid and properties will be used. -If uid is already in nodes then the known node will -be used

    • -
    • edge (uid of edge or edge, must belong to self.edges) –

    • -
    -
    -
    Returns
    -

    hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -classmethod add_nwhy(h, fpath=None)[source]
    -

    Add nwhy functionality to a hypergraph.

    -
    -
    Parameters
    -
      -
    • h (hnx.Hypergraph) –

    • -
    • fpath (file path for storage of hypergraph state dictionary) –

    • -
    -
    -
    Returns
    -

    Returns a copy of h with static set to true and nwhy set to True -if it is available.

    -
    -
    Return type
    -

    hnx.Hypergraph

    -
    -
    -
    - -
    -
    -adjacency_matrix(index=False, s=1, weighted=True)[source]
    -

    The sparse weighted s-adjacency matrix

    -
    -
    Parameters
    -
      -
    • s (int, optional, default: 1) –

    • -
    • index (boolean, optional, default: False) – if True, will return a rowdict of row to node uid

    • -
    • weighted (boolean, optional, default: True) –

    • -
    -
    -
    Returns
    -

      -
    • adjacency_matrix (scipy.sparse.csr.csr_matrix)

    • -
    • row dictionary (dict)

    • -
    -

    -
    -
    -

    Notes

    -

    If weighted is True each off diagonal cell will equal the number -of edges shared by the nodes indexing the row and column if that number is -greater than s, otherwise the cell will equal 0. If weighted is False, the off -diagonal cell will equal 1 if the nodes indexed by the row and column share at -least s edges and 0 otherwise.

    -
    - -
    -
    -auxiliary_matrix(s=1, index=False)[source]
    -

    The unweighted s-auxiliary matrix for hypergraph

    -
    -
    Parameters
    -
      -
    • s (int) –

    • -
    • index (bool, optional, default: False) – return a dictionary of labels for the rows of the matrix

    • -
    -
    -
    Returns
    -

    auxiliary_matrix – Will return the same type of matrix as self.arr

    -
    -
    Return type
    -

    scipy.sparse.csr.csr_matrix or numpy.ndarray

    -
    -
    -

    Notes

    -

    Creates subgraph by restricting to edges of cardinality at least s. -Returns the unweighted s-edge adjacency matrix for the subgraph.

    -
    - -
    -
    -bipartite()[source]
    -

    Constructs the networkX bipartite graph associated to hypergraph.

    -
    -
    Returns
    -

    bipartite

    -
    -
    Return type
    -

    nx.Graph()

    -
    -
    -

    Notes

    -

    Creates a bipartite networkx graph from hypergraph. -The nodes and (hyper)edges of hypergraph become the nodes of bipartite graph. -For every (hyper)edge e in the hypergraph and node n in e there is an edge (n,e) -in the graph.

    -
    - -
    -
    -collapse_edges(name=None, use_reps=None, return_counts=None, return_equivalence_classes=False)[source]
    -

    Constructs a new hypergraph gotten by identifying edges containing the same nodes

    -
    -
    Parameters
    -
      -
    • name (hashable, optional, default: None) –

    • -
    • return_equivalence_classes (boolean, optional, default: False) – Returns a dictionary of edge equivalence classes keyed by frozen sets of nodes

    • -
    -
    -
    Returns
    -

      -
    • new hypergraph (Hypergraph) – Equivalent edges are collapsed to a single edge named by a representative of the equivalent -edges followed by a colon and the number of edges it represents.

    • -
    • equivalence_classes (dict) – A dictionary keyed by representative edge names with values equal to the edges in -its equivalence class

    • -
    -

    -
    -
    -

    Notes

    -

    Two edges are identified if their respective elements are the same. -Using this as an equivalence relation, the uids of the edges are partitioned into -equivalence classes.

    -

    A single edge from the collapsed edges followed by a colon and the number of elements -in its equivalence class as uid for the new edge

    -
    - -
    -
    -collapse_nodes(name=None, use_reps=True, return_counts=True, return_equivalence_classes=False)[source]
    -

    Constructs a new hypergraph gotten by identifying nodes contained by the same edges

    -
    -
    Parameters
    -
      -
    • name (str, optional, default: None) –

    • -
    • return_equivalence_classes (boolean, optional, default: False) – Returns a dictionary of node equivalence classes keyed by frozen sets of edges

    • -
    • use_reps (boolean, optional, default: False - Deprecated, this no longer works and will be removed) – Choose a single element from the collapsed nodes as uid for the new node, otherwise uses -a frozen set of the uids of nodes in the equivalence class

    • -
    • return_counts (boolean, - Deprecated, this no longer works and will be removed) – if use_reps is True the new nodes have uids given by a tuple of the rep -and the count

    • -
    -
    -
    Returns
    -

    new hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -

    Notes

    -

    Two nodes are identified if their respective memberships are the same. -Using this as an equivalence relation, the uids of the nodes are partitioned into -equivalence classes. A single member of the equivalence class is chosen to represent -the class followed by the number of members of the class.

    -

    Example

    -
    >>> h = Hypergraph(EntitySet('example',elements=[Entity('E1', ['a','b']),Entity('E2',['a','b'])]))
    ->>> h.incidence_dict
    -{'E1': {'a', 'b'}, 'E2': {'a', 'b'}}
    ->>> h.collapse_nodes().incidence_dict
    -{'E1': {frozenset({'a', 'b'})}, 'E2': {frozenset({'a', 'b'})}} ### Fix this
    ->>> h.collapse_nodes(use_reps=True).incidence_dict
    -{'E1': {('a', 2)}, 'E2': {('a', 2)}}
    -
    -
    -
    - -
    -
    -collapse_nodes_and_edges(name=None, use_reps=True, return_counts=True, return_equivalence_classes=False)[source]
    -

    Returns a new hypergraph by collapsing nodes and edges.

    -
    -
    Parameters
    -
      -
    • name (str, optional, default: None) –

    • -
    • use_reps (boolean, optional, default: False) – Choose a single element from the collapsed elements as a representative

    • -
    • return_counts (boolean, optional, default: True) – if use_reps is True the new elements are keyed by a tuple of the rep -and the count

    • -
    • return_equivalence_classes (boolean, optional, default: False) – Returns a dictionary of edge equivalence classes keyed by frozen sets of nodes

    • -
    -
    -
    Returns
    -

    new hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -

    Notes

    -

    Collapses the Nodes and Edges EntitySets. Two nodes(edges) are duplicates -if their respective memberships(elements) are the same. Using this as an -equivalence relation, the uids of the nodes(edges) are partitioned into -equivalence classes. A single member of the equivalence class is chosen to represent -the class followed by the number of members of the class.

    -

    Example

    -
    >>> h = Hypergraph(EntitySet('example',elements=[Entity('E1', ['a','b']),Entity('E2',['a','b'])]))
    ->>> h.incidence_dict
    -{'E1': {'a', 'b'}, 'E2': {'a', 'b'}}
    ->>> h.collapse_nodes_and_edges().incidence_dict   ### Fix this
    -{('E1', 2): {('a', 2)}}
    -
    -
    -
    - -
    -
    -component_subgraphs(return_singletons=False)[source]
    -

    Same as s_components_subgraphs() with s=1. Returns iterator.

    -
    -

    See also

    -

    s_component_subgraphs

    -
    -
    - -
    -
    -components(edges=False, return_singletons=True)[source]
    -

    Same as s_connected_components() with s=1, but nodes are returned -by default. Return iterator.

    - -
    - -
    -
    -connected_component_subgraphs(return_singletons=True)[source]
    -

    Same as s_component_subgraphs() with s=1. Returns iterator

    -
    -

    See also

    -

    s_component_subgraphs

    -
    -
    - -
    -
    -connected_components(edges=False, return_singletons=True)[source]
    -

    Same as s_connected_components() with s=1, but nodes are returned -by default. Return iterator.

    - -
    - -
    -
    -convert_to_static(name=None, nodes_name='nodes', edges_name='edges', use_nwhy=False, filepath=None)[source]
    -

    Returns new static hypergraph with the same dictionary as original hypergraph

    -
    -
    Parameters
    -
      -
    • name (None, optional) – Name

    • -
    • nodes_name (str, optional) – name for list of node labels

    • -
    • edges_name (str, optional) – name for list of edge labels

    • -
    -
    -
    Returns
    -

    Will have attribute static = True

    -
    -
    Return type
    -

    hnx.Hypergraph

    -
    -
    -
    -

    Note

    -

    Static hypergraphs store the user defined node and edge names in -a dictionary of labeled lists. The order of the lists provides an -index, which the hypergraph uses in place of the node and edge names -for fast processing.

    -
    -
    - -
    -
    -dataframe(sort_rows=False, sort_columns=False)[source]
    -

    Returns a pandas dataframe for hypergraph indexed by the nodes and -with column headers given by the edge names.

    -
    -
    Parameters
    -
      -
    • sort_rows (bool, optional, default=True) – sort rows based on hashable node names

    • -
    • sort_columns (bool, optional, default=True) – sort columns based on hashable edge names

    • -
    -
    -
    -
    - -
    -
    -degree(node, s=1, max_size=None)[source]
    -

    The number of edges of size s that contain node.

    -
    -
    Parameters
    -
      -
    • node (hashable) – identifier for the node.

    • -
    • s (positive integer, optional, default: 1) – smallest size of edge to consider in degree

    • -
    • max_size (positive integer or None, optional, default: None) – largest size of edge to consider in degree

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -diameter(s=1)[source]
    -

    Returns the length of the longest shortest s-walk between nodes in hypergraph

    -
    -
    Parameters
    -

    s (int, optional, default: 1) –

    -
    -
    Returns
    -

    diameter

    -
    -
    Return type
    -

    int

    -
    -
    Raises
    -

    HyperNetXError – If hypergraph is not s-edge-connected

    -
    -
    -

    Notes

    -

    Two nodes are s-adjacent if they share s edges. -Two nodes v_start and v_end are s-walk connected if there is a sequence of -nodes v_start, v_1, v_2, … v_n-1, v_end such that consecutive nodes -are s-adjacent. If the graph is not connected, an error will be raised.

    -
    - -
    -
    -dim(edge)[source]
    -

    Same as size(edge)-1.

    -
    - -
    -
    -distance(source, target, s=1)[source]
    -

    Returns the shortest s-walk distance between two nodes in the hypergraph.

    -
    -
    Parameters
    -
      -
    • source (node.uid or node) – a node in the hypergraph

    • -
    • target (node.uid or node) – a node in the hypergraph

    • -
    • s (positive integer) – the number of edges

    • -
    -
    -
    Returns
    -

    s-walk distance

    -
    -
    Return type
    -

    int

    -
    -
    -
    -

    See also

    -

    edge_distance

    -
    -

    Notes

    -

    The s-distance is the shortest s-walk length between the nodes. -An s-walk between nodes is a sequence of nodes that pairwise share -at least s edges. The length of the shortest s-walk is 1 less than -the number of nodes in the path sequence.

    -

    Uses the networkx shortest_path_length method on the graph -generated by the s-adjacency matrix.

    -
    - -
    -
    -dual(name=None)[source]
    -

    Constructs a new hypergraph with roles of edges and nodes of hypergraph reversed.

    -
    -
    Parameters
    -

    name (hashable) –

    -
    -
    Returns
    -

    dual

    -
    -
    Return type
    -

    hypergraph

    -
    -
    -
    - -
    -
    -edge_adjacency_matrix(index=False, s=1, weighted=True)[source]
    -

    The weighted s-adjacency matrix for the dual hypergraph.

    -
    -
    Parameters
    -
      -
    • s (int, optional, default: 1) –

    • -
    • index (boolean, optional, default: False) – if True, will return a coldict of column to edge uid

    • -
    • sparse (boolean, optional, default: True) –

    • -
    • weighted (boolean, optional, default: True) –

    • -
    -
    -
    Returns
    -

      -
    • edge_adjacency_matrix (scipy.sparse.csr.csr_matrix or numpy.ndarray)

    • -
    • column dictionary (dict)

    • -
    -

    -
    -
    -

    Notes

    -

    This is also the adjacency matrix for the line graph. -Two edges are s-adjacent if they share at least s nodes. -If index=True, returns a dictionary column_index:edge_uid

    -
    - -
    -
    -edge_diameter(s=1)[source]
    -

    Returns the length of the longest shortest s-walk between edges in hypergraph

    -
    -
    Parameters
    -

    s (int, optional, default: 1) –

    -
    -
    Returns
    -

    edge_diameter

    -
    -
    Return type
    -

    int

    -
    -
    Raises
    -

    HyperNetXError – If hypergraph is not s-edge-connected

    -
    -
    -

    Notes

    -

    Two edges are s-adjacent if they share s nodes. -Two nodes e_start and e_end are s-walk connected if there is a sequence of -edges e_start, e_1, e_2, … e_n-1, e_end such that consecutive edges -are s-adjacent. If the graph is not connected, an error will be raised.

    -
    - -
    -
    -edge_diameters(s=1)[source]
    -

    Returns the edge diameters of the s_edge_connected component subgraphs -in hypergraph.

    -
    -
    Parameters
    -

    s (int, optional, default: 1) –

    -
    -
    Returns
    -

      -
    • maximum diameter (int)

    • -
    • list of diameters (list) – List of edge_diameters for s-edge component subgraphs in hypergraph

    • -
    • list of component (list) – List of the edge uids in the s-edge component subgraphs.

    • -
    -

    -
    -
    -
    - -
    -
    -edge_distance(source, target, s=1)[source]
    -

    XX TODO: still need to return path and translate into user defined nodes and edges -Returns the shortest s-walk distance between two edges in the hypergraph.

    -
    -
    Parameters
    -
      -
    • source (edge.uid or edge) – an edge in the hypergraph

    • -
    • target (edge.uid or edge) – an edge in the hypergraph

    • -
    • s (positive integer) – the number of intersections between pairwise consecutive edges

    • -
    • TODO (add edge weights) –

    • -
    • weight (None or string, optional, default: None) – if None then all edges have weight 1. If string then edge attribute -string is used if available.

    • -
    -
    -
    Returns
    -

    s- walk distance – A shortest s-walk is computed as a sequence of edges, -the s-walk distance is the number of edges in the sequence -minus 1. If no such path exists returns np.inf.

    -
    -
    Return type
    -

    the shortest s-walk edge distance

    -
    -
    -
    -

    See also

    -

    distance

    -
    -

    Notes

    -

    The s-distance is the shortest s-walk length between the edges. -An s-walk between edges is a sequence of edges such that consecutive pairwise -edges intersect in at least s nodes. The length of the shortest s-walk is 1 less than -the number of edges in the path sequence.

    -

    Uses the networkx shortest_path_length method on the graph -generated by the s-edge_adjacency matrix.

    -
    - -
    -
    -edge_neighbors(edge, s=1)[source]
    -

    The edges in hypergraph which share s nodes(s) with edge.

    -
    -
    Parameters
    -
      -
    • edge (hashable or Entity) – uid for a edge in hypergraph or the edge Entity

    • -
    • s (int, list, optional, default : 1) – Minimum number of nodes shared by neighbors edge node.

    • -
    -
    -
    Returns
    -

    List of edge neighbors

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -edge_size_dist()[source]
    -

    Returns the size for each edge

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    np.array

    -
    -
    -
    - -
    -
    -property edges
    -

    Object associated with self._edges.

    -
    -
    Returns
    -

    If self.isstatic the StaticEntitySet, otherwise EntitySet.

    -
    -
    Return type
    -

    StaticEntitySet or EntitySet

    -
    -
    -
    - -
    -
    -classmethod from_bipartite(B, set_names=('nodes', 'edges'), name=None, static=False, use_nwhy=False)[source]
    -

    Static method creates a Hypergraph from a bipartite graph.

    -
    -
    Parameters
    -
      -
    • B (nx.Graph()) – A networkx bipartite graph. Each node in the graph has a property -‘bipartite’ taking the value of 0 or 1 indicating a 2-coloring of the graph.

    • -
    • set_names (iterable of length 2, optional, default = ['nodes','edges']) – Category names assigned to the graph nodes associated to each bipartite set

    • -
    • name (hashable) –

    • -
    • static (bool) –

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -

    Notes

    -

    A partition for the nodes in a bipartite graph generates a hypergraph.

    -
    >>> import networkx as nx
    ->>> B = nx.Graph()
    ->>> B.add_nodes_from([1, 2, 3, 4], bipartite=0)
    ->>> B.add_nodes_from(['a', 'b', 'c'], bipartite=1)
    ->>> B.add_edges_from([(1, 'a'), (1, 'b'), (2, 'b'), (2, 'c'), (3, 'c'), (4, 'a')])
    ->>> H = Hypergraph.from_bipartite(B)
    ->>> H.nodes, H.edges
    -# output: (EntitySet(_:Nodes,[1, 2, 3, 4],{}), EntitySet(_:Edges,['b', 'c', 'a'],{}))
    -
    -
    -
    - -
    -
    -classmethod from_dataframe(df, columns=None, rows=None, name=None, fillna=0, transpose=False, transforms=[], key=None, node_label='nodes', edge_label='edges', static=False, use_nwhy=False)[source]
    -

    Create a hypergraph from a Pandas Dataframe object using index to label vertices -and Columns to label edges. The values of the dataframe are transformed into an -incidence matrix. -Note this is different than passing a dataframe directly -into the Hypergraph constructor. The latter automatically generates a static hypergraph -with edge and node labels given by the cell values.

    -
    -
    Parameters
    -
      -
    • df (Pandas.Dataframe) – a real valued dataframe with a single index

    • -
    • columns ((optional) list, default = None) – restricts df to the columns with headers in this list.

    • -
    • rows ((optional) list, default = None) – restricts df to the rows indexed by the elements in this list.

    • -
    • name ((optional) string, default = None) –

    • -
    • fillna (float, default = 0) – a real value to place in empty cell, all-zero columns will not generate -an edge.

    • -
    • transpose ((optional) bool, default = False) – option to transpose the dataframe, in this case df.Index will label the edges -and df.columns will label the nodes, transpose is applied before transforms and -key

    • -
    • transforms ((optional) list, default = []) – optional list of transformations to apply to each column, -of the dataframe using pd.DataFrame.apply(). -Transformations are applied in the order they are -given (ex. abs). To apply transforms to rows or for additional -functionality, consider transforming df using pandas.DataFrame methods -prior to generating the hypergraph.

    • -
    • key ((optional) function, default = None) – boolean function to be applied to dataframe. Must be defined on numpy -arrays.

    • -
    -
    -
    -
    -

    See also

    -

    from_numpy_array

    -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -

    Notes

    -

    The from_dataframe constructor does not generate empty edges. -All-zero columns in df are removed and the names corresponding to these -edges are discarded. -Restrictions and data processing will occur in this order:

    -
    -
      -
    1. column and row restrictions

    2. -
    3. fillna replace NaNs in dataframe

    4. -
    5. transpose the dataframe

    6. -
    7. transforms in the order listed

    8. -
    9. boolean key

    10. -
    -
    -

    This method offers the above options for wrangling a dataframe into an incidence -matrix for a hypergraph. For more flexibility we recommend you use the Pandas -library to format the values of your dataframe before submitting it to this -constructor.

    -
    - -
    -
    -classmethod from_numpy_array(M, node_names=None, edge_names=None, node_label='nodes', edge_label='edges', name=None, key=None, static=False, use_nwhy=False)[source]
    -

    Create a hypergraph from a real valued matrix represented as a 2 dimensionsl numpy array. -The matrix is converted to a matrix of 0’s and 1’s so that any truthy cells are converted to 1’s and -all others to 0’s.

    -
    -
    Parameters
    -
      -
    • M (real valued array-like object, 2 dimensions) – representing a real valued matrix with rows corresponding to nodes and columns to edges

    • -
    • node_names (object, array-like, default=None) – List of node names must be the same length as M.shape[0]. -If None then the node names correspond to row indices with ‘v’ prepended.

    • -
    • edge_names (object, array-like, default=None) – List of edge names must have the same length as M.shape[1]. -If None then the edge names correspond to column indices with ‘e’ prepended.

    • -
    • name (hashable) –

    • -
    • key ((optional) function) – boolean function to be evaluated on each cell of the array, -must be applicable to numpy.array

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    -

    Note

    -

    The constructor does not generate empty edges. -All zero columns in M are removed and the names corresponding to these -edges are discarded.

    -
    -
    - -
    -
    -get_id(uid, edges=False)[source]
    -

    Return the internally assigned id associated with a label.

    -
    -
    Parameters
    -
      -
    • uid (string) – User provided name/id/label for hypergraph object

    • -
    • edges (bool, optional) – Determines if uid is an edge or node name

    • -
    -
    -
    Returns
    -

    internal id assigned at construction

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -get_linegraph(s, edges=True, use_nwhy=True)[source]
    -

    Creates an ::term::s-linegraph for the Hypergraph. -If edges=True (default)then the edges will be the vertices of the line graph. -Two vertices are connected by an s-line-graph edge if the corresponding -hypergraphedges intersect in at least s hypergraph nodes. -If edges=False, the hypergraph nodes will be the vertices of the line graph. -Two vertices are connected if the nodes they correspond to share at least s -incident hyper edges.

    -
    -
    Parameters
    -
      -
    • s (int) – The width of the connections.

    • -
    • edges (bool, optional) – Determine if edges or nodes will be the vertices in the linegraph.

    • -
    • use_nwhy (bool, optional) – Requests that nwhy be used to construct the linegraph. If NWHy is not available this is ignored.

    • -
    -
    -
    Returns
    -

    A NetworkX graph.

    -
    -
    Return type
    -

    nx.Graph

    -
    -
    -
    - -
    -
    -get_name(id, edges=False)[source]
    -

    Return the user defined name/id/label associated to an -internally assigned id.

    -
    -
    Parameters
    -
      -
    • id (int) – Internally assigned id

    • -
    • edges (bool, optional) – Determines if id references an edge or node

    • -
    -
    -
    Returns
    -

    User provided name/id/label for hypergraph object

    -
    -
    Return type
    -

    str

    -
    -
    -
    - -
    -
    -property incidence_dict
    -

    Dictionary keyed by edge uids with values the uids of nodes in each edge

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -incidence_matrix(index=False)[source]
    -

    An incidence matrix for the hypergraph indexed by nodes x edges.

    -
    -
    Parameters
    -

    index (boolean, optional, default False) – If True return will include a dictionary of node uid : row number -and edge uid : column number

    -
    -
    Returns
    -

      -
    • incidence_matrix (scipy.sparse.csr.csr_matrix or np.ndarray)

    • -
    • row dictionary (dict) – Dictionary identifying rows with nodes

    • -
    • column dictionary (dict) – Dictionary identifying columns with edges

    • -
    -

    -
    -
    -
    - -
    -
    -static incidence_to_adjacency(M, s=1, weighted=True)[source]
    -

    Helper method to obtain adjacency matrix from incidence matrix.

    -
    -
    Parameters
    -
      -
    • M (scipy.sparse.csr.csr_matrix) –

    • -
    • s (int, optional, default: 1) –

    • -
    • weighted (boolean, optional, default: True) –

    • -
    -
    -
    Returns
    -

    a matrix

    -
    -
    Return type
    -

    scipy.sparse.csr.csr_matrix

    -
    -
    -
    - -
    -
    -is_connected(s=1, edges=False)[source]
    -

    Determines if hypergraph is s-connected.

    -
    -
    Parameters
    -
      -
    • s (int, optional, default: 1) –

    • -
    • edges (boolean, optional, default: False) – If True, will determine if s-edge-connected. -For s=1 s-edge-connected is the same as s-connected.

    • -
    -
    -
    Returns
    -

    is_connected

    -
    -
    Return type
    -

    boolean

    -
    -
    -

    Notes

    -

    A hypergraph is s node connected if for any two nodes v0,vn -there exists a sequence of nodes v0,v1,v2,…,v(n-1),vn -such that every consecutive pair of nodes v(i),v(i+1) -share at least s edges.

    -

    A hypergraph is s edge connected if for any two edges e0,en -there exists a sequence of edges e0,e1,e2,…,e(n-1),en -such that every consecutive pair of edges e(i),e(i+1) -share at least s nodes.

    -
    - -
    -
    -property isstatic
    -

    Checks whether nodes and edges are immutable

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    Boolean

    -
    -
    -
    - -
    -
    -neighbors(node, s=1)[source]
    -

    The nodes in hypergraph which share s edge(s) with node.

    -
    -
    Parameters
    -
      -
    • node (hashable or Entity) – uid for a node in hypergraph or the node Entity

    • -
    • s (int, list, optional, default : 1) – Minimum number of edges shared by neighbors with node.

    • -
    -
    -
    Returns
    -

    List of neighbors

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -node_diameters(s=1)[source]
    -

    Returns the node diameters of the connected components in hypergraph.

    -
    -
    Parameters
    -
      -
    • of the diameters of the s-components and (list) –

    • -
    • of the s-component nodes (list) –

    • -
    -
    -
    -
    - -
    -
    -property nodes
    -

    Object associated with self._nodes.

    -
    -
    Returns
    -

    If self.isstatic the StaticEntitySet, otherwise EntitySet.

    -
    -
    Return type
    -

    StaticEntitySet or EntitySet

    -
    -
    -
    - -
    -
    -number_of_edges(edgeset=None)[source]
    -

    The number of edges in edgeset belonging to hypergraph.

    -
    -
    Parameters
    -

    edgeset (an interable of Entities, optional, default: None) – If None, then return the number of edges in hypergraph.

    -
    -
    Returns
    -

    number_of_edges

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -number_of_nodes(nodeset=None)[source]
    -

    The number of nodes in nodeset belonging to hypergraph.

    -
    -
    Parameters
    -

    nodeset (an interable of Entities, optional, default: None) – If None, then return the number of nodes in hypergraph.

    -
    -
    Returns
    -

    number_of_nodes

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -order()[source]
    -

    The number of nodes in hypergraph.

    -
    -
    Returns
    -

    order

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -classmethod recover_from_state(fpath='current_state.p', newfpath=None, use_nwhy=True)[source]
    -

    Recover a static hypergraph pickled using save_state.

    -
    -
    Parameters
    -

    fpath (str) – Full path to pickle file containing state_dict and labels -of hypergraph

    -
    -
    Returns
    -

    H – static hypergraph with state dictionary prefilled

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -remove_edge(edge)[source]
    -

    Removes a single edge from hypergraph.

    -
    -
    Parameters
    -

    edge (hashable or Entity) –

    -
    -
    Returns
    -

    hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -

    Notes

    -

    Deletes reference to edge from all of its nodes. -If any of its nodes do not belong to any other edges -the node is dropped from self.

    -
    - -
    -
    -remove_edges(edge_set)[source]
    -

    Removes edges from hypergraph.

    -
    -
    Parameters
    -

    edge_set (iterable of hashables or Entities) –

    -
    -
    Returns
    -

    hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -remove_node(node)[source]
    -

    Removes node from edges and deletes reference in hypergraph nodes

    -
    -
    Parameters
    -

    node (hashable or Entity) – a node in hypergraph

    -
    -
    Returns
    -

    hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -remove_nodes(node_set)[source]
    -

    Removes nodes from edges and deletes references in hypergraph nodes

    -
    -
    Parameters
    -

    node_set (an iterable of hashables or Entities) – Nodes in hypergraph

    -
    -
    Returns
    -

    hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -remove_singletons(name=None)[source]
    -

    XX -Constructs clone of hypergraph with singleton edges removed.

    -
    -
    Parameters
    -

    name (str, optional, default: None) –

    -
    -
    Returns
    -

    new hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -remove_static(name=None)[source]
    -

    Returns dynamic hypergraph

    -
    -
    Parameters
    -

    name (None, optional) – User defined namae of hypergraph

    -
    -
    Returns
    -

    A new hypergraph with the same dictionary as self but allowing dynamic -changes to nodes and edges. -If hypergraph is not static, returns self.

    -
    -
    Return type
    -

    hnx.Hypergraph

    -
    -
    -
    - -
    -
    -restrict_to_edges(edgeset, name=None)[source]
    -

    Constructs a hypergraph using a subset of the edges in hypergraph

    -
    -
    Parameters
    -
      -
    • edgeset (iterable of hashables or Entities) – A subset of elements of the hypergraph edges

    • -
    • name (str, optional) –

    • -
    -
    -
    Returns
    -

    new hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -restrict_to_nodes(nodeset, name=None)[source]
    -

    Constructs a new hypergraph by restricting the edges in the hypergraph to -the nodes referenced by nodeset.

    -
    -
    Parameters
    -
      -
    • nodeset (iterable of hashables) – References a subset of elements of self.nodes

    • -
    • name (string, optional, default: None) –

    • -
    -
    -
    Returns
    -

    new hypergraph

    -
    -
    Return type
    -

    Hypergraph

    -
    -
    -
    - -
    -
    -s_component_subgraphs(s=1, edges=True, return_singletons=False)[source]
    -

    Returns a generator for the induced subgraphs of s_connected components. -Removes singletons unless return_singletons is set to True. Computed using -s-linegraph generated either by the hypergraph (edges=True) or its dual -(edges = False)

    -
    -
    Parameters
    -
      -
    • s (int, optional, default: 1) –

    • -
    • edges (boolean, optional, edges=False) – Determines if edge or node components are desired. Returns -subgraphs equal to the hypergraph restricted to each set of nodes(edges) in the -s-connected components or s-edge-connected components

    • -
    • return_singletons (bool, optional) –

    • -
    -
    -
    Yields
    -

    s_component_subgraphs (iterator) – Iterator returns subgraphs generated by the edges (or nodes) in the -s-edge(node) components of hypergraph.

    -
    -
    -
    - -
    -
    -s_components(s=1, edges=True, return_singletons=True)[source]
    -

    Same as s_connected_components

    - -
    - -
    -
    -s_connected_components(s=1, edges=True, return_singletons=False)[source]
    -

    Returns a generator for the s-edge-connected components -or the s-node-connected components -of the hypergraph.

    -
    -
    Parameters
    -
      -
    • s (int, optional, default: 1) –

    • -
    • edges (boolean, optional, default: True) – If True will return edge components, if False will return node components

    • -
    • return_singletons (bool, optional, default : False) –

    • -
    -
    -
    -

    Notes

    -

    If edges=True, this method returns the s-edge-connected components as -lists of lists of edge uids. -An s-edge-component has the property that for any two edges e1 and e2 -there is a sequence of edges starting with e1 and ending with e2 -such that pairwise adjacent edges in the sequence intersect in at least -s nodes. If s=1 these are the path components of the hypergraph.

    -

    If edges=False this method returns s-node-connected components. -A list of sets of uids of the nodes which are s-walk connected. -Two nodes v1 and v2 are s-walk-connected if there is a -sequence of nodes starting with v1 and ending with v2 such that pairwise -adjacent nodes in the sequence share s edges. If s=1 these are the -path components of the hypergraph.

    -

    Example

    -
    >>> S = {'A':{1,2,3},'B':{2,3,4},'C':{5,6},'D':{6}}
    ->>> H = Hypergraph(S)
    -
    -
    -
    >>> list(H.s_components(edges=True))
    -[{'C', 'D'}, {'A', 'B'}]
    ->>> list(H.s_components(edges=False))
    -[{1, 2, 3, 4}, {5, 6}]
    -
    -
    -
    -
    Yields
    -

    s_connected_components (iterator) – Iterator returns sets of uids of the edges (or nodes) in the s-edge(node) -components of hypergraph.

    -
    -
    -
    - -
    -
    -s_degree(node, s=1)[source]
    -

    Same as degree

    -
    -
    Parameters
    -
      -
    • node (Entity or hashable) – If hashable, then must be uid of node in hypergraph

    • -
    • s (positive integer, optional, default: 1) –

    • -
    -
    -
    Returns
    -

    s_degree – The degree of a node in the subgraph induced by edges -of size s

    -
    -
    Return type
    -

    int

    -
    -
    -
    -

    Note

    -

    The s-degree of a node is the number of edges of size -at least s that contain the node.

    -
    -
    - -
    -
    -save_state(fpath=None)[source]
    -

    Save the hypergraph as an ordered pair: [state_dict,labels] -The hypergraph can be recovered using the command:

    -
    >>> H = hnx.Hypergraph.recover_from_state(fpath)
    -
    -
    -
    -
    Parameters
    -

    fpath (str, optional) –

    -
    -
    -
    - -
    -
    -set_state(**kwargs)[source]
    -

    Allow state_dict updates from outside of class. Use with caution.

    -
    -
    Parameters
    -

    **kwargs – key=value pairs to save in state dictionary

    -
    -
    -
    - -
    -
    -property shape
    -

    (number of nodes, number of edges)

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    tuple

    -
    -
    -
    - -
    -
    -singletons()[source]
    -

    Returns a list of singleton edges. A singleton edge is an edge of -size 1 with a node of degree 1.

    -
    -
    Returns
    -

    singles – A list of edge uids.

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -size(edge)[source]
    -

    The number of nodes that belong to edge.

    -
    -
    Parameters
    -

    edge (hashable) – The uid of an edge in the hypergraph

    -
    -
    Returns
    -

    size

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -toplexes(name=None, collapse=False, use_reps=False, return_counts=True)[source]
    -

    Returns a simple hypergraph corresponding to self.

    -
    -

    Warning

    -

    Collapsing is no longer supported inside the toplexes method. Instead generate a new -collapsed hypergraph and compute the toplexes of the new hypergraph.

    -
    -
    -
    Parameters
    -
      -
    • name (str, optional, default: None) –

    • -
    • collapse (#) –

    • -
    • Should the hypergraph be collapsed? This would preserve a link between duplicate maximal sets. (#) –

    • -
    • If False then only one of these sets will be used and uniqueness will be up to sets of equal size. (#) –

    • -
    • use_reps (#) –

    • -
    • If collapse=True then each toplex will be named by a representative of the set of (#) –

    • -
    • equivalent edges (#) –

    • -
    • is False (see collapse_edges) (default) –

    • -
    • return_counts (boolean, optional, default: True) – # If collapse=True then each toplex will be named by a tuple of the representative -# of the set of equivalent edges and their count

    • -
    -
    -
    -
    - -
    -
    -translate(idx, edges=False)[source]
    -

    Returns the translation of numeric values associated with hypergraph. -Only needed if exposing the static identifiers assigned by the class. -If not static then the idx is returned.

    -
    -
    Parameters
    -
      -
    • idx (int) – class assigned integer for internal manipulation of Hypergraph data

    • -
    • edges (bool, optional, default: True) – If True then translates from edge index. Otherwise will translate from -node index, default=False

    • -
    -
    -
    Returns
    -

    User assigned identifier corresponding to idx

    -
    -
    Return type
    -

    int or string

    -
    -
    -
    - -
    - -
    -
    -

    classes.staticentity module

    -
    -
    -class classes.staticentity.StaticEntity(entity=None, data=None, arr=None, labels=None, uid=None, **props)[source]
    -

    Bases: object

    -
    -
    Parameters
    -
      -
    • entity (StaticEntity, StaticEntitySet, Entity, EntitySet, pandas.DataFrame, dict, or list of lists) – If a pandas.DataFrame, an error will be raised if there are nans.

    • -
    • data (array or array-like) – Two dimensional array of integers. Provides sparse tensor indices for incidence -tensor.

    • -
    • arr (numpy.ndarray or scip.sparse.matrix, optional, default=None) – Incidence tensor of data.

    • -
    • labels (OrderedDict of lists, optional, default=None) – User defined labels corresponding to integers in data.

    • -
    • uid (hashable, optional, default=None) –

    • -
    • props (user defined keyword arguments to be added to a properties dictionary, optional) –

    • -
    -
    -
    -
    -
    -properties
    -

    Description

    -
    -
    Type
    -

    dict

    -
    -
    -
    - -
    -
    -property arr
    -
    - -
    -
    -property children
    -

    Labels of keys of first index

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    set

    -
    -
    -
    - -
    -
    -property data
    -

    Data array or tensor array of Static Entity

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    np.ndarray

    -
    -
    -
    - -
    -
    -property dataframe
    -

    Returns the entity data in DataFrame format

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    pandas.core.frame.DataFrame

    -
    -
    -
    - -
    -
    -property dimensions
    -

    Dimension of Static Entity data

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    tuple

    -
    -
    -
    - -
    -
    -property dimsize
    -

    Number of categories in the data

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -property elements
    -

    Keys and values in the order of insertion

    -
    -
    Returns
    -

      -
    • collections.OrderedDict

    • -
    • level1 = elements, level2 = children

    • -
    -

    -
    -
    -
    - -
    -
    -elements_by_level(level1=0, level2=None, translate=False)[source]
    -

    Elements of staticentity by specified column

    -
    -
    Parameters
    -
      -
    • level1 (int, optional) – edges

    • -
    • level2 (int, optional) – nodes

    • -
    • translate (bool, optional) – whether to replace indices with labels

    • -
    -
    -
    Returns
    -

      -
    • collections.defaultdict

    • -
    • think (level1 = edges, level2 = nodes)

    • -
    -

    -
    -
    -
    - -
    -
    -property incidence_dict
    -

    Dictionary using index 0 as nodes and index 1 as edges

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    collections.OrderedDict

    -
    -
    -
    - -
    -
    -incidence_matrix(level1=0, level2=1, weighted=False, index=False)[source]
    -

    Convenience method to navigate large tensor

    -
    -
    Parameters
    -
      -
    • level1 (int, optional) – indexes columns

    • -
    • level2 (int, optional) – indexes rows

    • -
    • weighted (bool, optional) –

    • -
    • index (bool, optional) –

    • -
    -
    -
    Returns
    -

      -
    • scipy.sparse.csr.csr_matrix

    • -
    • think level1 = edges, level2 = nodes

    • -
    -

    -
    -
    -
    - -
    -
    -index(category, value=None)[source]
    -

    Returns dimension of category and index of value

    -
    -
    Parameters
    -
      -
    • category (string) –

    • -
    • value (string, optional) –

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    int or tuple of ints

    -
    -
    -
    - -
    -
    -indices(category, values)[source]
    -

    Returns dimension of category and index of values (array)

    -
    -
    Parameters
    -
      -
    • category (string) –

    • -
    • values (single string or array of strings) –

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -is_empty(level=0)[source]
    -

    Boolean indicating if entity.elements is empty

    -
    -
    Parameters
    -

    level (int, optional) –

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    bool

    -
    -
    -
    - -
    -
    -property keyindex
    -

    Returns the index of a category in keys array

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -property keys
    -

    Array of keys of labels

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    np.ndarray

    -
    -
    -
    - -
    -
    -property labels
    -

    Ordered dictionary of labels

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    collections.OrderedDict

    -
    -
    -
    - -
    -
    -labs(kdx)[source]
    -

    Retrieve labels by index in keys

    -
    -
    Parameters
    -

    kdx (int) – index of key in E.keys

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    np.ndarray

    -
    -
    -
    - -
    -
    -level(item, min_level=0, max_level=None, return_index=True)[source]
    -

    Returns first level item appears by order of keys from minlevel to maxlevel -inclusive

    -
    -
    Parameters
    -
      -
    • item (string) –

    • -
    • min_level (int, optional) –

    • -
    • max_level (int, optional) –

    • -
    • return_index (bool, optional) –

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    tuple

    -
    -
    -
    - -
    -
    -restrict_to_indices(indices, level=0, uid=None)[source]
    -

    Limit Static Entity data to specific indices of keys

    -
    -
    Parameters
    -
      -
    • indices (array) – array of category indices

    • -
    • level (int, optional) – index of label

    • -
    • uid (None, optional) –

    • -
    -
    -
    Returns
    -

    hnx.classes.staticentity.StaticEntity

    -
    -
    Return type
    -

    Static Entity class

    -
    -
    -
    - -
    -
    -restrict_to_levels(levels, uid=None)[source]
    -

    Limit Static Entity data to specific labels

    -
    -
    Parameters
    -
      -
    • levels (array) – index of labels in data

    • -
    • uid (None, optional) –

    • -
    -
    -
    Returns
    -

    hnx.classes.staticentity.StaticEntity

    -
    -
    Return type
    -

    Static Entity class

    -
    -
    -
    - -
    -
    -size()[source]
    -

    The number of elements in E, the size of dimension 0 in the E.arr

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    int

    -
    -
    -
    - -
    -
    -translate(level, index)[source]
    -

    Replaces a category index and value index with label

    -
    -
    Parameters
    -
      -
    • level (int) – category index of label

    • -
    • index (int) – value index of label

    • -
    -
    -
    Returns
    -

    -
    -
    Return type
    -

    numpy.array(str)

    -
    -
    -
    - -
    -
    -translate_arr(coords)[source]
    -

    Translates a single cell in the entity array

    -
    -
    Parameters
    -

    coords (tuple of ints) –

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -turn_entity_data_into_dataframe(data_subset)[source]
    -

    Convert rows of original data in StaticEntity to dataframe

    -
    -
    Parameters
    -

    data (numpy.ndarray) – Subset of the rows in the original data held in the StaticEntity

    -
    -
    Returns
    -

    Columns and cell entries are derived from data and self.labels

    -
    -
    Return type
    -

    pandas.core.frame.DataFrame

    -
    -
    -
    - -
    -
    -property uid
    -
    - -
    -
    -property uidset
    -

    Returns a set of the string identifiers for Static Entity

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    frozenset

    -
    -
    -
    - -
    -
    -uidset_by_level(level=0)[source]
    -

    The labels found in columns = level

    -
    -
    Parameters
    -

    level (int, optional) –

    -
    -
    Returns
    -

    -
    -
    Return type
    -

    frozenset

    -
    -
    -
    - -
    - -
    -
    -class classes.staticentity.StaticEntitySet(entity=None, data=None, arr=None, labels=None, uid=None, level1=0, level2=1, **props)[source]
    -

    Bases: classes.staticentity.StaticEntity

    -
    -
    -collapse_identical_elements(uid=None, return_equivalence_classes=False)[source]
    -

    Returns StaticEntitySet after collapsing elements if they have same children -If no elements share same children, a copy of the original StaticEntitySet is returned -:param uid: -:type uid: None, optional -:param return_equivalence_classes: If True, return a dictionary of equivalence classes keyed by new edge names -:type return_equivalence_classes: bool, optional

    -
    -
    Returns
    -

    hnx.classes.staticentity.StaticEntitySet

    -
    -
    Return type
    -

    StaticEntitySet

    -
    -
    -
    - -
    -
    -convert_to_entityset(uid)[source]
    -

    Convert given uid of Static EntitySet into EntitySet

    -
    -
    Parameters
    -

    uid (string) –

    -
    -
    Returns
    -

    hnx.classes.entity.EntitySet

    -
    -
    Return type
    -

    EntitySet

    -
    -
    -
    - -
    -
    -incidence_matrix(sparse=True, weighted=False, index=False)[source]
    -

    Incidence matrix of StaticEntitySet indexed by uidset

    -
    -
    Parameters
    -
      -
    • sparse (bool, optional) –

    • -
    • weighted (bool, optional) –

    • -
    • index (bool, optional) – give index of rows then columns

    • -
    -
    -
    Returns
    -

    scipy.sparse.csr.csr_matrix

    -
    -
    Return type
    -

    matrix

    -
    -
    -
    - -
    -
    -restrict_to(indices, uid=None)[source]
    -

    Limit Static Entityset data to specific indices of keys

    -
    -
    Parameters
    -
      -
    • indices (array) – array of indices in keys

    • -
    • uid (None, optional) –

    • -
    -
    -
    Returns
    -

    hnx.classes.staticentity.StaticEntitySet

    -
    -
    Return type
    -

    StaticEntitySet

    -
    -
    -
    - -
    - +
    +

    classes.staticentity module

    -
    -

    Module contents

    +
    +

    Module contents

    diff --git a/docs/build/classes/modules.html b/docs/build/classes/modules.html index 63cbb63b..77a1e982 100644 --- a/docs/build/classes/modules.html +++ b/docs/build/classes/modules.html @@ -7,7 +7,7 @@ - classes — HyperNetX 1.0.0 documentation + classes — HyperNetX 1.0.1 documentation @@ -189,10 +189,10 @@

    classesclasses package

  • diff --git a/docs/build/core.html b/docs/build/core.html index 67b1f3ef..59a72922 100644 --- a/docs/build/core.html +++ b/docs/build/core.html @@ -7,7 +7,7 @@ - HyperNetX Packages — HyperNetX 1.0.0 documentation + HyperNetX Packages — HyperNetX 1.0.1 documentation @@ -185,10 +185,10 @@
  • Hypergraphs @@ -196,18 +196,9 @@
  • Algorithms @@ -215,10 +206,10 @@
  • Drawing @@ -226,8 +217,8 @@
  • Reports diff --git a/docs/build/drawing/drawing.html b/docs/build/drawing/drawing.html index bdf61e6e..6b70eb59 100644 --- a/docs/build/drawing/drawing.html +++ b/docs/build/drawing/drawing.html @@ -7,7 +7,7 @@ - drawing package — HyperNetX 1.0.0 documentation + drawing package — HyperNetX 1.0.1 documentation @@ -106,10 +106,10 @@
  • Drawing @@ -197,434 +197,17 @@

    drawing package

    Submodules

    -
    -

    drawing.rubber_band module

    -
    -
    -drawing.rubber_band.draw(H, pos=None, with_color=True, with_node_counts=False, with_edge_counts=False, layout=<function fruchterman_reingold_layout>, layout_kwargs={}, ax=None, node_radius=None, edges_kwargs={}, nodes_kwargs={}, edge_labels={}, edge_labels_kwargs={}, node_labels={}, node_labels_kwargs={}, with_edge_labels=True, with_node_labels=True, label_alpha=0.35, return_pos=False)[source]
    -

    Draw a hypergraph as a Matplotlib figure

    -

    By default this will draw a colorful “rubber band” like hypergraph, where -convex hulls represent edges and are drawn around the nodes they contain.

    -

    This is a convenience function that wraps calls with sensible parameters to -the following lower-level drawing functions:

    -
      -
    • draw_hyper_edges,

    • -
    • draw_hyper_edge_labels,

    • -
    • draw_hyper_labels, and

    • -
    • draw_hyper_nodes

    • -
    -

    The default layout algorithm is nx.spring_layout, but other layouts can be -passed in. The Hypergraph is converted to a bipartite graph, and the layout -algorithm is passed the bipartite graph.

    -

    If you have a pre-determined layout, you can pass in a “pos” dictionary. -This is a dictionary mapping from node id’s to x-y coordinates. For example:

    -
    >>> pos = {
    ->>> 'A': (0, 0),
    ->>> 'B': (1, 2),
    ->>> 'C': (5, -3)
    ->>> }
    -
    +
    +

    drawing.rubber_band module

    -

    will position the nodes {A, B, C} manually at the locations specified. The -coordinate system is in Matplotlib “data coordinates”, and the figure will -be centered within the figure.

    -

    By default, this will draw in a new figure, but the axis to render in can be -specified using ax.

    -

    This approach works well for small hypergraphs, and does not guarantee -a rigorously “correct” drawing. Overlapping of sets in the drawing generally -implies that the sets intersect, but sometimes sets overlap if there is no -intersection. It is not possible, in general, to draw a “correct” hypergraph -this way for an arbitrary hypergraph, in the same way that not all graphs -have planar drawings.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    • with_color (bool) – set to False to disable color cycling of edges

    • -
    • with_node_counts (bool) – set to True to label collapsed nodes with number of elements

    • -
    • with_edge_counts (bool) – set to True to label collapsed edges with number of elements

    • -
    • layout (function) – layout algorithm to compute

    • -
    • layout_kwargs (dict) – keyword arguments passed to layout function

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    • edges_kwargs (dict) – keyword arguments passed to matplotlib.collections.PolyCollection for edges

    • -
    • node_radius (None, int, float, or dict) – radius of all nodes, or dictionary of node:value; the default (None) calculates radius based on number of collapsed nodes; reasonable values range between 1 and 3

    • -
    • nodes_kwargs (dict) – keyword arguments passed to matplotlib.collections.PolyCollection for nodes

    • -
    • edge_labels_kwargs (dict) – keyword arguments passed to matplotlib.annotate for edge labels

    • -
    • node_labels_kwargs (dict) – keyword argumetns passed to matplotlib.annotate for node labels

    • -
    • with_edge_labels (bool) – set to False to make edge labels invisible

    • -
    • with_node_labels (bool) – set to False to make node labels invisible

    • -
    • label_alpha (float) – the transparency (alpha) of the box behind text drawn in the figure

    • -
    -
    -
    -
    - -
    -
    -drawing.rubber_band.draw_hyper_edge_labels(H, polys, labels={}, ax=None, **kwargs)[source]
    -

    Draws a label on the hyper edge boundary.

    -

    Should be passed Matplotlib PolyCollection representing the hyper-edges, see -the return value of draw_hyper_edges.

    -

    The label will be draw on the least curvy part of the polygon, and will be -aligned parallel to the orientation of the polygon where it is drawn.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • polys (PolyCollection) – collection of polygons returned by draw_hyper_edges

    • -
    • labels (dict) – mapping of node id to string label

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    • kwargs (dict) – Keyword arguments are passed through to Matplotlib’s annotate function.

    • -
    -
    -
    -
    - -
    -
    -drawing.rubber_band.draw_hyper_edges(H, pos, ax=None, node_radius={}, dr=None, **kwargs)[source]
    -

    Draws a convex hull around the nodes contained within each edge in H

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    • node_radius (dict) – mapping of node to R^1 (radius of each node)

    • -
    • dr (float) – the spacing between concentric rings

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    • kwargs (dict) – keyword arguments, e.g., linewidth, facecolors, are passed through to the PolyCollection constructor

    • -
    -
    -
    Returns
    -

    a Matplotlib PolyCollection that can be further styled

    -
    -
    Return type
    -

    PolyCollection

    -
    -
    -
    - -
    -
    -drawing.rubber_band.draw_hyper_labels(H, pos, node_radius={}, ax=None, labels={}, **kwargs)[source]
    -

    Draws text labels for the hypergraph nodes.

    -

    The label is drawn to the right of the node. The node radius is needed (see -draw_hyper_nodes) so the text can be offset appropriately as the node size -changes.

    -

    The text label can be customized by passing in a dictionary, labels, mapping -a node to its custom label. By default, the label is the string -representation of the node.

    -

    Keyword arguments are passed through to Matplotlib’s annotate function.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    • node_radius (dict) – mapping of node to R^1 (radius of each node)

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    • labels (dict) – mapping of node to text label

    • -
    • kwargs (dict) – keyword arguments passed to matplotlib.annotate

    • -
    -
    -
    -
    - -
    -
    -drawing.rubber_band.draw_hyper_nodes(H, pos, node_radius={}, r0=None, ax=None, **kwargs)[source]
    -

    Draws a circle for each node in H.

    -

    The position of each node is specified by the a dictionary/list-like, pos, -where pos[v] is the xy-coordinate for the vertex. The radius of each node -can be specified as a dictionary where node_radius[v] is the radius. If a -node is missing from this dictionary, or the node_radius is not specified at -all, a sensible default radius is chosen based on distances between nodes -given by pos.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    • node_radius (dict) – mapping of node to R^1 (radius of each node)

    • -
    • r0 (float) – minimum distance that concentric rings start from the node position

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    • kwargs (dict) – keyword arguments, e.g., linewidth, facecolors, are passed through to the PolyCollection constructor

    • -
    -
    -
    Returns
    -

    a Matplotlib PolyCollection that can be further styled

    -
    -
    Return type
    -

    PolyCollection

    -
    -
    -
    - -
    -
    -drawing.rubber_band.get_default_radius(H, pos)[source]
    -

    Calculate a reasonable default node radius

    -

    This function iterates over the hyper edges and finds the most distant -pair of points given the positions provided. Then, the node radius is a fraction -of the median of this distance take across all hyper-edges.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    -
    -
    Returns
    -

    the recommended radius

    -
    -
    Return type
    -

    float

    -
    -
    -
    - -
    -
    -drawing.rubber_band.layout_hyper_edges(H, pos, node_radius={}, dr=None)[source]
    -

    Draws a convex hull for each edge in H.

    -

    Position of the nodes in the graph is specified by the position dictionary, -pos. Convex hulls are spaced out such that if one set contains another, the -convex hull will surround the contained set. The amount of spacing added -between hulls is specified by the parameter, dr.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    • node_radius (dict) – mapping of node to R^1 (radius of each node)

    • -
    • dr (float) – the spacing between concentric rings

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    -
    -
    Returns
    -

    A mapping from hyper edge ids to paths (Nx2 numpy matrices)

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    - -

    Helper function to use a NetwrokX-like graph layout algorithm on a Hypergraph

    -

    The hypergraph is converted to a bipartite graph, allowing the usual graph layout -techniques to be applied.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • layout (function) – the layout algorithm which accepts a NetworkX graph and keyword arguments

    • -
    • kwargs (dict) – Keyword arguments are passed through to the layout algorithm

    • -
    -
    -
    Returns
    -

    mapping of node and edge positions to R^2

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - +
    +

    drawing.two_column module

    -
    -

    drawing.two_column module

    -
    -
    -drawing.two_column.draw(H, with_node_labels=True, with_edge_labels=True, with_node_counts=False, with_edge_counts=False, with_color=True, edge_kwargs=None, ax=None)[source]
    -

    Draw a hypergraph using a two-collumn layout.

    -

    This is intended reproduce an illustrative technique for bipartite graphs -and hypergraphs that is typically used in papers and textbooks.

    -

    The left column is reserved for nodes and the right column is reserved for -edges. A line is drawn between a node an an edge

    -

    The order of nodes and edges is optimized to reduce line crossings between -the two columns. Spacing between disconnected components is adjusted to make -the diagram easier to read, by reducing the angle of the lines.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • with_node_labels (bool) – False to disable node labels

    • -
    • with_edge_labels (bool) – False to disable edge labels

    • -
    • with_node_counts (bool) – set to True to label collapsed nodes with number of elements

    • -
    • with_edge_counts (bool) – set to True to label collapsed edges with number of elements

    • -
    • with_color (bool) – set to False to disable color cycling of hyper edges

    • -
    • edge_kwargs (dict) – keyword arguments to pass to matplotlib.LineCollection

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    -
    -
    -
    - -
    -
    -drawing.two_column.draw_hyper_edges(H, pos, ax=None, **kwargs)[source]
    -

    Renders hyper edges for the two column layout.

    -

    Each node-hyper edge membership is rendered as a line connecting the node -in the left column to the edge in the right column.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    • kwargs (dict) – keyword arguments passed to matplotlib.LineCollection

    • -
    -
    -
    Returns
    -

    the hyper edges

    -
    -
    Return type
    -

    LineCollection

    -
    -
    -
    - -
    -
    -drawing.two_column.draw_hyper_labels(H, pos, labels={}, with_node_labels=True, with_edge_labels=True, ax=None)[source]
    -

    Renders hyper labels (nodes and edges) for the two column layout.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • pos (dict) – mapping of node and edge positions to R^2

    • -
    • labels (dict) – custom labels for nodes and edges can be supplied

    • -
    • with_node_labels (bool) – False to disable node labels

    • -
    • with_edge_labels (bool) – False to disable edge labels

    • -
    • ax (Axis) – matplotlib axis on which the plot is rendered

    • -
    • kwargs (dict) – keyword arguments passed to matplotlib.LineCollection

    • -
    -
    -
    -
    - -
    -
    -drawing.two_column.layout_two_column(H, spacing=2)[source]
    -

    Two column (bipartite) layout algorithm.

    -

    This algorithm first converts the hypergraph into a bipartite graph and -then computes connected components. Disonneccted components are handled -independently and then stacked together.

    -

    Within a connected component, the spectral ordering of the bipartite graph -provides a quick and dirty ordering that minimizes edge crossings in the -diagram.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • spacing (float) – amount of whitespace between disconnected components

    • -
    -
    -
    -
    - -
    -
    -

    drawing.util module

    -
    -
    -drawing.util.get_frozenset_label(S, count=False, override={})[source]
    -

    Helper function for rendering the labels of possibly collapsed nodes and edges

    -
    -
    Parameters
    -
      -
    • S (iterable) – list of entities to be labeled

    • -
    • count (bool) – True if labels should be counts of entities instead of list

    • -
    -
    -
    Returns
    -

    mapping of entity to its string representation

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -drawing.util.get_line_graph(H, collapse=True)[source]
    -

    Computes the line graph, a directed graph, where a directed edge (u, v) -exists if the edge u is a subset of the edge v in the hypergraph.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • collapse (bool) – True if edges should be added if hyper edges are identical

    • -
    -
    -
    Returns
    -

    A directed graph

    -
    -
    Return type
    -

    networkx.DiGraph

    -
    -
    -
    - -
    -
    -drawing.util.get_set_layering(H, collapse=True)[source]
    -

    Computes a layering of the edges in the hyper graph.

    -

    In this layering, each edge is assigned a level. An edge u will be above -(e.g., have a smaller level value) another edge v if v is a subset of u.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) – the entity to be drawn

    • -
    • collapse (bool) – True if edges should be added if hyper edges are identical

    • -
    -
    -
    Returns
    -

    a mapping of vertices in H to integer levels

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -drawing.util.inflate(items, v)[source]
    -
    - -
    -
    -drawing.util.inflate_kwargs(items, kwargs)[source]
    -

    Helper function to expand keyword arguments.

    -
    -
    Parameters
    -
      -
    • n (int) – length of resulting list if argument is expanded

    • -
    • kwargs (dict) – keyword arguments to be expanded

    • -
    -
    -
    Returns
    -

    dictionary with same keys as kwargs and whose values are lists of length n

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -drawing.util.transpose_inflated_kwargs(inflated)[source]
    -
    - +
    +

    drawing.util module

    -
    -

    Module contents

    +
    +

    Module contents

    diff --git a/docs/build/drawing/modules.html b/docs/build/drawing/modules.html index 6e693e5b..68658da8 100644 --- a/docs/build/drawing/modules.html +++ b/docs/build/drawing/modules.html @@ -7,7 +7,7 @@ - drawing — HyperNetX 1.0.0 documentation + drawing — HyperNetX 1.0.1 documentation @@ -189,10 +189,10 @@

    drawingdrawing package

  • diff --git a/docs/build/genindex.html b/docs/build/genindex.html index ebd20a43..1986b9ff 100644 --- a/docs/build/genindex.html +++ b/docs/build/genindex.html @@ -7,7 +7,7 @@ - Index — HyperNetX 1.0.0 documentation + Index — HyperNetX 1.0.1 documentation @@ -172,180 +172,19 @@

    Index

    - A - | B - | C + B | D | E - | F - | G | H | I - | K - | L - | M - | N - | O - | P - | R | S | T - | U
    -

    A

    - - - -
    -

    B

    - -
    - -

    C

    - - -
    @@ -353,87 +192,11 @@

    C

    D

    @@ -441,46 +204,16 @@

    D

    E

    - + -
    - -

    F

    - - - -
    - -

    G

    - - -
    @@ -535,15 +228,7 @@

    G

    H

    -
    @@ -552,245 +237,6 @@

    I

    - -
    - -

    K

    - - - -
    - -

    L

    - - - -
    - -

    M

    - - -
    - -

    N

    - - - -
    - -

    O

    - - -
    - -

    P

    - - -
    - -

    R

    - - -
    @@ -814,6 +260,8 @@

    S

  • s-edge
  • + + - @@ -891,46 +287,6 @@

    T

    - -
    - -

    U

    - - -
    diff --git a/docs/build/glossary.html b/docs/build/glossary.html index 8e761813..002f174a 100644 --- a/docs/build/glossary.html +++ b/docs/build/glossary.html @@ -7,7 +7,7 @@ - Glossary of HNX terms — HyperNetX 1.0.0 documentation + Glossary of HNX terms — HyperNetX 1.0.1 documentation @@ -180,7 +180,7 @@ elements and children of an EntitySet generate a specific partition for a bipartite graph. partition is isomorphic to a Hypergraph where the elements correspond to hyperedges and children correspond to the nodes. EntitySets are the basic objects used to construct dynamic hypergraphs -NX. See methods classes.hypergraph.Hypergraph.bipartite() and classes.hypergraph.Hypergraph.from_bipartite().

    +NX. See methods classes.hypergraph.Hypergraph.bipartite() and classes.hypergraph.Hypergraph.from_bipartite().

    degree

    Given a hypergraph (Nodes,Edges), the degree of a node in Nodes is the number of edges in Edges to which the node belongs. See also: s-degree

    diff --git a/docs/build/home.html b/docs/build/home.html index 54efa90f..4417d511 100644 --- a/docs/build/home.html +++ b/docs/build/home.html @@ -7,7 +7,7 @@ - HyperNetX (HNX) — HyperNetX 1.0.0 documentation + HyperNetX (HNX) — HyperNetX 1.0.1 documentation diff --git a/docs/build/index.html b/docs/build/index.html index 8f1e5140..c2f68ceb 100644 --- a/docs/build/index.html +++ b/docs/build/index.html @@ -7,7 +7,7 @@ - HyperNetX (HNX) — HyperNetX 1.0.0 documentation + HyperNetX (HNX) — HyperNetX 1.0.1 documentation diff --git a/docs/build/install.html b/docs/build/install.html index 2298e562..5db68008 100644 --- a/docs/build/install.html +++ b/docs/build/install.html @@ -7,7 +7,7 @@ - Installing HyperNetX — HyperNetX 1.0.0 documentation + Installing HyperNetX — HyperNetX 1.0.1 documentation diff --git a/docs/build/license.html b/docs/build/license.html index 17945953..5a6e5945 100644 --- a/docs/build/license.html +++ b/docs/build/license.html @@ -7,7 +7,7 @@ - License — HyperNetX 1.0.0 documentation + License — HyperNetX 1.0.1 documentation diff --git a/docs/build/nwhy.html b/docs/build/nwhy.html index a23e0b53..f0df4dac 100644 --- a/docs/build/nwhy.html +++ b/docs/build/nwhy.html @@ -7,7 +7,7 @@ - NWHy — HyperNetX 1.0.0 documentation + NWHy — HyperNetX 1.0.1 documentation diff --git a/docs/build/objects.inv b/docs/build/objects.inv index dbfe48516051fedd999df5ac121544606394bcf2..1e17783551bb7e1c664ee08ac8eca265679057ec 100644 GIT binary patch delta 767 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 F&Qe>mK9~Ri diff --git a/docs/build/overview/index.html b/docs/build/overview/index.html index 84cebc51..9affbefc 100644 --- a/docs/build/overview/index.html +++ b/docs/build/overview/index.html @@ -7,7 +7,7 @@ - Overview — HyperNetX 1.0.0 documentation + Overview — HyperNetX 1.0.1 documentation diff --git a/docs/build/publications.html b/docs/build/publications.html index 7d60efe3..d920d5be 100644 --- a/docs/build/publications.html +++ b/docs/build/publications.html @@ -7,7 +7,7 @@ - Publications — HyperNetX 1.0.0 documentation + Publications — HyperNetX 1.0.1 documentation diff --git a/docs/build/py-modindex.html b/docs/build/py-modindex.html deleted file mode 100644 index c761f75c..00000000 --- a/docs/build/py-modindex.html +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - - - - - Python Module Index — HyperNetX 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - - - -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
      - -
    • »
    • - -
    • Python Module Index
    • - - -
    • - -
    • - -
    - - -
    -
    -
    -
    - - -

    Python Module Index

    - -
    - a | - c | - d | - r -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     
    - a
    - algorithms -
        - algorithms.homology_mod2 -
        - algorithms.s_centrality_measures -
     
    - c
    - classes -
        - classes.entity -
        - classes.hypergraph -
        - classes.staticentity -
     
    - d
    - drawing -
        - drawing.rubber_band -
        - drawing.two_column -
        - drawing.util -
     
    - r
    - reports -
        - reports.descriptive_stats -
    - - -
    - -
    -
    - -
    - -
    -

    - © Copyright 2018 Battelle Memorial Institute. - -

    -
    - - - - Built with Sphinx using a - - theme - - provided by Read the Docs. - -
    -
    -
    - -
    - -
    - - - - - - - - - - - \ No newline at end of file diff --git a/docs/build/reports/modules.html b/docs/build/reports/modules.html index 39710e6d..7ff9b3c5 100644 --- a/docs/build/reports/modules.html +++ b/docs/build/reports/modules.html @@ -7,7 +7,7 @@ - reports — HyperNetX 1.0.0 documentation + reports — HyperNetX 1.0.1 documentation @@ -189,8 +189,8 @@

    reportsreports package diff --git a/docs/build/reports/reports.html b/docs/build/reports/reports.html index ce30a973..edb5cc4b 100644 --- a/docs/build/reports/reports.html +++ b/docs/build/reports/reports.html @@ -7,7 +7,7 @@ - reports package — HyperNetX 1.0.0 documentation + reports package — HyperNetX 1.0.1 documentation @@ -107,8 +107,8 @@
  • Reports @@ -195,282 +195,11 @@

    reports package

    Submodules

    -
    -

    reports.descriptive_stats module

    -
    -
    This module contains methods which compute various distributions for hypergraphs:
      -
    • Edge size distribution

    • -
    • Node degree distribution

    • -
    • Component size distribution

    • -
    • Toplex size distribution

    • -
    • Diameter

    • -
    -
    -
    -

    Also computes general hypergraph information: number of nodes, edges, cells, aspect ratio, incidence matrix density

    -
    -
    -reports.descriptive_stats.centrality_stats(X)[source]
    -

    Computes basic centrality statistics for X

    -
    -
    Parameters
    -

    X – an iterable of numbers

    -
    -
    Returns
    -

    [min, max, mean, median, standard deviation] – List of centrality statistics for X

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.comp_dist(H, aggregated=False)[source]
    -

    Computes component sizes, number of nodes.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) –

    • -
    • aggregated – If aggregated is True, returns a dictionary of -component sizes (number of nodes) and counts. If aggregated -is False, returns a list of components sizes in H.

    • -
    -
    -
    Returns
    -

    comp_dist – List of component sizes or dictionary of component size distribution

    -
    -
    Return type
    -

    list or dictionary

    -
    -
    -
    -

    See also

    -

    s_comp_dist

    -
    -
    - -
    -
    -reports.descriptive_stats.degree_dist(H, aggregated=False)[source]
    -

    Computes degrees of nodes of a hypergraph.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) –

    • -
    • aggregated – If aggregated is True, returns a dictionary of -degrees and counts. If aggregated is False, returns a -list of degrees in H.

    • -
    -
    -
    Returns
    -

    degree_dist – List of degrees or dictionary of degree distribution

    -
    -
    Return type
    -

    list or dict

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.dist_stats(H)[source]
    -

    Computes many basic hypergraph stats and puts them all into a single dictionary object

    -
    -
      -
    • nrows = number of nodes (rows in the incidence matrix)

    • -
    • ncols = number of edges (columns in the incidence matrix)

    • -
    • aspect ratio = nrows/ncols

    • -
    • ncells = number of filled cells in incidence matrix

    • -
    • density = ncells/(nrows*ncols)

    • -
    • node degree list = degree_dist(H)

    • -
    • node degree dist = centrality_stats(degree_dist(H))

    • -
    • node degree hist = Counter(degree_dist(H))

    • -
    • max node degree = max(degree_dist(H))

    • -
    • edge size list = edge_size_dist(H)

    • -
    • edge size dist = centrality_stats(edge_size_dist(H))

    • -
    • edge size hist = Counter(edge_size_dist(H))

    • -
    • max edge size = max(edge_size_dist(H))

    • -
    • comp nodes list = s_comp_dist(H, s=1, edges=False)

    • -
    • comp nodes dist = centrality_stats(s_comp_dist(H, s=1, edges=False))

    • -
    • comp nodes hist = Counter(s_comp_dist(H, s=1, edges=False))

    • -
    • comp edges list = s_comp_dist(H, s=1, edges=True)

    • -
    • comp edges dist = centrality_stats(s_comp_dist(H, s=1, edges=True))

    • -
    • comp edges hist = Counter(s_comp_dist(H, s=1, edges=True))

    • -
    • num comps = len(s_comp_dist(H))

    • -
    -
    -
    -
    Parameters
    -

    H (Hypergraph) –

    -
    -
    Returns
    -

    dist_stats – Dictionary which keeps track of each of the above items (e.g., basic[‘nrows’] = the number of nodes in H)

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.edge_size_dist(H, aggregated=False)[source]
    -

    Computes edge sizes of a hypergraph.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) –

    • -
    • aggregated – If aggregated is True, returns a dictionary of -edge sizes and counts. If aggregated is False, returns a -list of edge sizes in H.

    • -
    -
    -
    Returns
    -

    edge_size_dist – List of edge sizes or dictionary of edge size distribution.

    -
    -
    Return type
    -

    list or dict

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.info(H, node=None, edge=None)[source]
    -

    Print a summary of simple statistics for H

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) –

    • -
    • obj (optional) – either a node or edge uid from the hypergraph

    • -
    • dictionary (optional) – If True then returns the info as a dictionary rather -than a string -If False (default) returns the info as a string

    • -
    -
    -
    Returns
    -

    info – Returns a string of statistics of the size, -aspect ratio, and density of the hypergraph. -Print the string to see it formatted.

    -
    -
    Return type
    -

    string

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.info_dict(H, node=None, edge=None)[source]
    -

    Create a summary of simple statistics for H

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) –

    • -
    • obj (optional) – either a node or edge uid from the hypergraph

    • -
    -
    -
    Returns
    -

    info_dict – Returns a dictionary of statistics of the size, -aspect ratio, and density of the hypergraph.

    -
    -
    Return type
    -

    dict

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.s_comp_dist(H, s=1, aggregated=False, edges=True, return_singletons=True)[source]
    -

    Computes s-component sizes, counting nodes or edges.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) –

    • -
    • s (positive integer, default is 1) –

    • -
    • aggregated – If aggregated is True, returns a dictionary of -s-component sizes and counts in H. If aggregated is -False, returns a list of s-component sizes in H.

    • -
    • edges – If edges is True, the component size is number of edges. -If edges is False, the component size is number of nodes.

    • -
    • return_singletons (bool, optional, default=True) –

    • -
    -
    -
    Returns
    -

    s_comp_dist – List of component sizes or dictionary of component size distribution in H

    -
    -
    Return type
    -

    list or dictionary

    -
    -
    -
    -

    See also

    -

    comp_dist

    -
    -
    - -
    -
    -reports.descriptive_stats.s_edge_diameter_dist(H)[source]
    -
    -
    Parameters
    -

    H (Hypergraph) –

    -
    -
    Returns
    -

    s_edge_diameter_dist – List of s-edge-diameters for hypergraph H starting with s=1 -and going up as long as the hypergraph is s-edge-connected

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.s_node_diameter_dist(H)[source]
    -
    -
    Parameters
    -

    H (Hypergraph) –

    -
    -
    Returns
    -

    s_node_diameter_dist – List of s-node-diameters for hypergraph H starting with s=1 -and going up as long as the hypergraph is s-node-connected

    -
    -
    Return type
    -

    list

    -
    -
    -
    - -
    -
    -reports.descriptive_stats.toplex_dist(H, aggregated=False)[source]
    -

    Computes toplex sizes for hypergraph H.

    -
    -
    Parameters
    -
      -
    • H (Hypergraph) –

    • -
    • aggregated – If aggregated is True, returns a dictionary of -toplex sizes and counts in H. If aggregated -is False, returns a list of toplex sizes in H.

    • -
    -
    -
    Returns
    -

    toplex_dist – List of toplex sizes or dictionary of toplex size distribution in H

    -
    -
    Return type
    -

    list or dictionary

    -
    -
    -
    - +
    +

    reports.descriptive_stats module

    -
    -

    Module contents

    +
    +

    Module contents

    diff --git a/docs/build/search.html b/docs/build/search.html index eaeb5f89..7243c541 100644 --- a/docs/build/search.html +++ b/docs/build/search.html @@ -7,7 +7,7 @@ - Search — HyperNetX 1.0.0 documentation + Search — HyperNetX 1.0.1 documentation diff --git a/docs/build/searchindex.js b/docs/build/searchindex.js index 61908666..7f405576 100644 --- a/docs/build/searchindex.js +++ b/docs/build/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["algorithms/algorithms","algorithms/modules","classes/classes","classes/modules","core","drawing/drawing","drawing/modules","glossary","home","index","install","license","nwhy","overview/index","publications","reports/modules","reports/reports","widget"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["algorithms/algorithms.rst","algorithms/modules.rst","classes/classes.rst","classes/modules.rst","core.rst","drawing/drawing.rst","drawing/modules.rst","glossary.rst","home.rst","index.rst","install.rst","license.rst","nwhy.rst","overview/index.rst","publications.rst","reports/modules.rst","reports/reports.rst","widget.rst"],objects:{"":{algorithms:[0,0,0,"-"],classes:[2,0,0,"-"],drawing:[5,0,0,"-"],reports:[16,0,0,"-"]},"algorithms.homology_mod2":{add_to_column:[0,1,1,""],add_to_row:[0,1,1,""],betti:[0,1,1,""],betti_numbers:[0,1,1,""],bkMatrix:[0,1,1,""],boundary_group:[0,1,1,""],chain_complex:[0,1,1,""],homology_basis:[0,1,1,""],hypergraph_homology_basis:[0,1,1,""],interpret:[0,1,1,""],kchainbasis:[0,1,1,""],logical_dot:[0,1,1,""],logical_matadd:[0,1,1,""],logical_matmul:[0,1,1,""],matmulreduce:[0,1,1,""],reduced_row_echelon_form_mod2:[0,1,1,""],smith_normal_form_mod2:[0,1,1,""],swap_columns:[0,1,1,""],swap_rows:[0,1,1,""]},"algorithms.s_centrality_measures":{s_betweenness_centrality:[0,1,1,""],s_closeness_centrality:[0,1,1,""],s_eccentricity:[0,1,1,""],s_harmonic_centrality:[0,1,1,""],s_harmonic_closeness_centrality:[0,1,1,""]},"classes.entity":{Entity:[2,2,1,""],EntitySet:[2,2,1,""]},"classes.entity.Entity":{add:[2,3,1,""],add_element:[2,3,1,""],add_elements_from:[2,3,1,""],children:[2,4,1,""],clone:[2,3,1,""],complete_registry:[2,3,1,""],depth:[2,3,1,""],elements:[2,4,1,""],fullregistry:[2,3,1,""],incidence_dict:[2,4,1,""],intersection:[2,3,1,""],is_bipartite:[2,4,1,""],is_empty:[2,4,1,""],level:[2,3,1,""],levelset:[2,3,1,""],memberships:[2,4,1,""],merge_entities:[2,3,1,""],nested_incidence_dict:[2,3,1,""],properties:[2,4,1,""],registry:[2,4,1,""],remove:[2,3,1,""],remove_element:[2,3,1,""],remove_elements_from:[2,3,1,""],restrict_to:[2,3,1,""],size:[2,3,1,""],uid:[2,4,1,""],uidset:[2,4,1,""]},"classes.entity.EntitySet":{add:[2,3,1,""],clone:[2,3,1,""],collapse_identical_elements:[2,3,1,""],incidence_matrix:[2,3,1,""],restrict_to:[2,3,1,""]},"classes.hypergraph":{Hypergraph:[2,2,1,""]},"classes.hypergraph.Hypergraph":{add_edge:[2,3,1,""],add_edges_from:[2,3,1,""],add_node_to_edge:[2,3,1,""],add_nwhy:[2,3,1,""],adjacency_matrix:[2,3,1,""],auxiliary_matrix:[2,3,1,""],bipartite:[2,3,1,""],collapse_edges:[2,3,1,""],collapse_nodes:[2,3,1,""],collapse_nodes_and_edges:[2,3,1,""],component_subgraphs:[2,3,1,""],components:[2,3,1,""],connected_component_subgraphs:[2,3,1,""],connected_components:[2,3,1,""],convert_to_static:[2,3,1,""],dataframe:[2,3,1,""],degree:[2,3,1,""],diameter:[2,3,1,""],dim:[2,3,1,""],distance:[2,3,1,""],dual:[2,3,1,""],edge_adjacency_matrix:[2,3,1,""],edge_diameter:[2,3,1,""],edge_diameters:[2,3,1,""],edge_distance:[2,3,1,""],edge_neighbors:[2,3,1,""],edge_size_dist:[2,3,1,""],edges:[2,4,1,""],from_bipartite:[2,3,1,""],from_dataframe:[2,3,1,""],from_numpy_array:[2,3,1,""],get_id:[2,3,1,""],get_linegraph:[2,3,1,""],get_name:[2,3,1,""],incidence_dict:[2,4,1,""],incidence_matrix:[2,3,1,""],incidence_to_adjacency:[2,3,1,""],is_connected:[2,3,1,""],isstatic:[2,4,1,""],neighbors:[2,3,1,""],node_diameters:[2,3,1,""],nodes:[2,4,1,""],number_of_edges:[2,3,1,""],number_of_nodes:[2,3,1,""],order:[2,3,1,""],recover_from_state:[2,3,1,""],remove_edge:[2,3,1,""],remove_edges:[2,3,1,""],remove_node:[2,3,1,""],remove_nodes:[2,3,1,""],remove_singletons:[2,3,1,""],remove_static:[2,3,1,""],restrict_to_edges:[2,3,1,""],restrict_to_nodes:[2,3,1,""],s_component_subgraphs:[2,3,1,""],s_components:[2,3,1,""],s_connected_components:[2,3,1,""],s_degree:[2,3,1,""],save_state:[2,3,1,""],set_state:[2,3,1,""],shape:[2,4,1,""],singletons:[2,3,1,""],size:[2,3,1,""],toplexes:[2,3,1,""],translate:[2,3,1,""]},"classes.staticentity":{StaticEntity:[2,2,1,""],StaticEntitySet:[2,2,1,""]},"classes.staticentity.StaticEntity":{arr:[2,4,1,""],children:[2,4,1,""],data:[2,4,1,""],dataframe:[2,4,1,""],dimensions:[2,4,1,""],dimsize:[2,4,1,""],elements:[2,4,1,""],elements_by_level:[2,3,1,""],incidence_dict:[2,4,1,""],incidence_matrix:[2,3,1,""],index:[2,3,1,""],indices:[2,3,1,""],is_empty:[2,3,1,""],keyindex:[2,4,1,""],keys:[2,4,1,""],labels:[2,4,1,""],labs:[2,3,1,""],level:[2,3,1,""],properties:[2,5,1,""],restrict_to_indices:[2,3,1,""],restrict_to_levels:[2,3,1,""],size:[2,3,1,""],translate:[2,3,1,""],translate_arr:[2,3,1,""],turn_entity_data_into_dataframe:[2,3,1,""],uid:[2,4,1,""],uidset:[2,4,1,""],uidset_by_level:[2,3,1,""]},"classes.staticentity.StaticEntitySet":{collapse_identical_elements:[2,3,1,""],convert_to_entityset:[2,3,1,""],incidence_matrix:[2,3,1,""],restrict_to:[2,3,1,""]},"drawing.rubber_band":{draw:[5,1,1,""],draw_hyper_edge_labels:[5,1,1,""],draw_hyper_edges:[5,1,1,""],draw_hyper_labels:[5,1,1,""],draw_hyper_nodes:[5,1,1,""],get_default_radius:[5,1,1,""],layout_hyper_edges:[5,1,1,""],layout_node_link:[5,1,1,""]},"drawing.two_column":{draw:[5,1,1,""],draw_hyper_edges:[5,1,1,""],draw_hyper_labels:[5,1,1,""],layout_two_column:[5,1,1,""]},"drawing.util":{get_frozenset_label:[5,1,1,""],get_line_graph:[5,1,1,""],get_set_layering:[5,1,1,""],inflate:[5,1,1,""],inflate_kwargs:[5,1,1,""],transpose_inflated_kwargs:[5,1,1,""]},"reports.descriptive_stats":{centrality_stats:[16,1,1,""],comp_dist:[16,1,1,""],degree_dist:[16,1,1,""],dist_stats:[16,1,1,""],edge_size_dist:[16,1,1,""],info:[16,1,1,""],info_dict:[16,1,1,""],s_comp_dist:[16,1,1,""],s_edge_diameter_dist:[16,1,1,""],s_node_diameter_dist:[16,1,1,""],toplex_dist:[16,1,1,""]},algorithms:{homology_mod2:[0,0,0,"-"],s_centrality_measures:[0,0,0,"-"]},classes:{entity:[2,0,0,"-"],hypergraph:[2,0,0,"-"],staticentity:[2,0,0,"-"]},drawing:{rubber_band:[5,0,0,"-"],two_column:[5,0,0,"-"],util:[5,0,0,"-"]},reports:{descriptive_stats:[16,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","property","Python property"],"5":["py","attribute","Python attribute"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:property","5":"py:attribute"},terms:{"0":[0,2,5,7,9,12,14],"00231":[0,14],"020":[0,14],"021":14,"030":14,"04197":14,"1":[0,2,5,7,9,12,14,16],"10":[0,2,14],"1000":2,"1007":14,"1140":[0,14],"11782":14,"1186":14,"12901":14,"15":14,"16":[0,14],"17th":14,"1_1":14,"2":[0,2,5,7,12,13,14],"2003":14,"2016":0,"2018":11,"2019":14,"2020":[0,14],"22":14,"27":0,"287":14,"2d":0,"2z":0,"3":[0,2,5,10,12,13,14],"35":5,"4":[2,13],"48478":14,"5":[2,5,13],"6":[2,13],"7":[10,13],"755":10,"76rl01830":13,"9":[0,10,12,14],"978":14,"abstract":0,"boolean":[0,2,12],"case":[2,13],"class":[4,7,8,9],"default":[0,2,5,16],"do":[2,7,11,12,13],"export":12,"final":0,"float":[0,2,5],"function":[2,5],"import":2,"int":[0,2,5,14],"long":16,"new":[0,2,5,9,12],"null":10,"public":[8,9],"return":[0,2,5,7,12,16],"static":[2,13],"super":17,"switch":7,"true":[0,2,5,12,16],"while":17,A:[0,2,5,7,11,12,14],AND:11,AS:11,As:[2,8,9],BE:11,BUT:11,BY:11,By:[2,5],FOR:11,For:[0,2,5,7,8,9,10,12,13,17],IF:11,IN:11,IS:11,If:[0,2,5,7,10,12,16],In:[0,2,5,12,13],It:[2,5,12],NO:11,NOT:11,Not:2,OF:[11,13],ON:11,OR:11,One:2,SUCH:11,Such:11,THE:11,TO:11,The:[0,2,5,7,8,9,12,13,17],Then:5,These:[0,17],To:[0,2,8,9],Will:2,_0:2,_1:2,_2:[0,2],_:2,__dict__:2,_edg:2,_node:2,_version:12,ab:[2,14],about:[8,9],abov:[2,5,11,16],ac05:13,accept:[5,12],access:[7,10],accomplish:0,accord:7,account:13,accuraci:13,aco5:13,across:5,action:0,activ:[10,17],ad:[0,2,5,13],adam:14,adaptor:12,add:2,add_edg:2,add_edges_from:2,add_el:2,add_elements_from:2,add_node_to_edg:2,add_nodes_from:2,add_nwhi:2,add_to_column:0,add_to_row:0,addit:[0,2],addon:[12,13,17],adjac:[0,2,7,8,9],adjacency_matrix:2,adjust:5,admit:[8,9],advanc:17,advis:11,after:[2,12],against:2,agenc:13,aggreg:16,ah:14,aksoi:[0,13,14],al:[0,14],algebra:[8,9],algorithm:[4,5,8,9,12,13,14,17],align:5,all:[0,2,5,7,10,12,13,16,17],allow:[2,5,17],alpha:5,alreadi:[2,17],also:[2,7,8,9,12,16,17],alter:0,altern:17,ami:14,among:[8,9],amount:5,an:[0,2,5,7,9,13,16,17],anaconda3:10,anaconda:9,analysi:12,analyt:[13,14],andrew:13,angl:5,ani:[0,2,7,11,12,13,17],annot:5,anoth:[0,5,7],api:9,apparatu:13,appear:[2,17],appli:[0,2,5],applic:2,approach:5,appropri:5,ar1:0,ar2:0,ar:[0,2,5,7,8,9,10,11,12,17],arbitrari:[5,8,9],arendt:[13,14],arg:[0,2],arg_set:2,argument:[2,5],argumetn:5,aris:11,around:5,arr:[0,2],arrai:[0,2,12],articl:14,arxiv:14,asc:0,aspect:16,assign:[2,5],associ:[0,2,11,12],assum:[2,13],attribut:[2,7],author:13,automat:2,auxiliari:[2,7],auxiliary_matrix:2,avail:[0,2,17],averag:12,ax:5,axi:5,azsecur:14,b:[2,5,7,14],back:12,backend:2,background:17,band:5,baric:14,base:[0,2,5,7,12,13,17],basi:0,basic:[2,7,8,9,13,16],bat:10,battel:[11,13],bd:0,becaus:[8,9],becom:[0,2],been:[0,12],befor:2,behavior:0,behind:5,being:0,belong:[2,7,12],below:10,berg:0,best:0,betti:0,betti_numb:0,between:[0,2,5,7,12,17],big:14,binari:[0,11],bioinformat:14,biolog:14,biomedcentr:14,bipartit:[2,5,7,17],bk:0,bkmatrix:0,bmc:14,bmcbioinformat:14,book:13,bool:[0,2,5,16],both:[2,7,8,9,12,17],bound:0,boundari:[0,5],boundary_group:0,box:5,bramer:14,brenda:[13,14],brett:14,brian:13,briefest:0,browser:[10,13],bsd:13,build:[2,10],build_doc:10,built:17,bulk:17,busi:11,button:17,c:[0,2,5,9,10,12,13,14],c_:0,c_b:[0,12],c_k:0,ca:14,calcul:5,call:[5,7,12],callahan:14,can:[2,5,7,8,9,12,13,17],cannot:2,capabl:17,cardin:2,care:2,carlo:14,categori:2,caus:[2,11,17],caution:2,cdot:0,cell:[0,2,16],center:5,central:[1,4,12,13,16],centrality_stat:16,certain:2,chain:0,chain_complex:0,chang:[2,5,17],check:[2,8,9,12],child:2,children:[2,7],chmod:10,choos:2,chosen:[2,5],circl:[5,17],ck:0,classmethod:2,claus:13,click:17,cliff:[13,14],cliqu:[8,9],clone:[2,10],close:[0,12],cockrel:14,code:11,col:12,colab:[2,9],coldict:2,collaps:[2,5,12,17],collapse_edg:[2,12],collapse_identical_el:2,collapse_nod:[2,12],collapse_nodes_and_edg:[2,12],collect:[2,5],collumn:5,colon:2,color:[2,5,17],column:[0,2,5,7,12,13,16],column_index:2,com:[10,14],combin:12,come:10,command:[2,10,17],comment:[8,9,13],commerci:13,commun:[8,9,13],comp:16,comp_dist:16,compar:12,complet:[7,13,17],complete_registri:2,complex:[0,2,8,9,12,14],compon:[0,2,5,7,12,16],component_subgraph:2,comput:[0,2,5,13,14,16],concentr:5,conda:[10,12],condit:[2,7,11],conf:14,conflict:2,connect:[0,2,5,7,8,9,12,16],connected:0,connected_compon:2,connected_component_subgraph:2,consecut:2,consent:11,consequenti:11,consid:2,constitut:13,construct:[0,2,7,12,13],constructor:[2,5,12,13],contact:[8,9,13],contain:[0,2,5,7,12,16,17],content:[1,3,4,6,15],continu:10,contract:[11,13],contributor:[8,9,11,13],control:[2,17],conveni:[2,5],convert:[2,5],convert_to_entityset:2,convert_to_stat:2,convex:5,cooper:13,coord:2,coordin:5,copi:[0,2,11,12],copyright:11,core:2,correct:5,correspond:[0,2,7],coset:0,could:2,count:[2,5,16],counter:16,creat:[2,10,12,13,16],creation:2,criteria:12,critic:14,cross:5,csr:2,csr_matrix:[0,2],ctrl:17,current:12,current_st:2,curvi:5,custom:5,cybersecur:14,cycl:[0,2,5],cyclic:0,d:[0,2,12,14],damag:11,daniel:14,data:[0,2,5,8,9,11,12,13,14],data_subset:2,datafram:[2,13],de:[13,17],dedup:2,deeper:2,defaultdict:2,defin:[0,2],degre:[2,7,12,16,17],degree_dist:16,delet:2,demo:17,denorm:0,densiti:16,depart:13,depend:[2,12],deprec:2,depth:[0,2,7],deriv:2,descend:2,descript:[0,2],descriptive_stat:[4,15],design:13,desir:2,dest:12,detail:17,determin:[0,2,5],develop:[8,9,12,13],deviat:16,df:2,diagon:[0,2],diagram:[5,17],diamet:[2,7,12,16],diamond:14,dict:[0,2,5,16],dictionari:[0,2,5,7,12,16],differ:[2,12],digraph:5,dim:[0,2,12],dimens:[0,2,12],dimension:[0,2,8,9],dimensionsl:2,dimsiz:2,direct:[2,5,11,12,17],directli:[2,8,9,13,17],dirti:5,disabl:5,discard:2,disclaim:11,disclos:13,disconnect:5,discov:0,discuss:0,disjoint:[2,7],disonnecct:5,dist:16,dist_stat:16,distanc:[0,2,5,7,12],distant:5,distinct:2,distinguish:[2,7,8,9],distribut:[11,12,16],divid:0,dlfer:0,doc:10,document:[2,10,11],doe:[2,5,13],doi:[0,14],domain:[0,14],done:[2,12],dot:0,down:17,dr:5,drag:17,draw:[4,9],draw_hyper_edg:5,draw_hyper_edge_label:5,draw_hyper_label:5,draw_hyper_nod:5,drawn:5,drop:2,dual:[2,7],duplic:[0,2],dustin:[13,14],dynam:[2,7],e0:2,e1:2,e2:2,e3:2,e:[0,2,5,7,10,12,14,16,17],e_1:2,e_2:2,e_end:2,e_n:2,e_start:2,each:[0,2,5,7,12,16,17],easier:5,ecc:0,eccentr:[0,12],echelon:0,ed:14,edg:[0,2,5,7,8,9,12,13,16,17],edge_adjac:2,edge_adjacency_matrix:2,edge_column_nam:2,edge_diamet:2,edge_dist:2,edge_incid:12,edge_kwarg:5,edge_label:[2,5],edge_labels_kwarg:5,edge_nam:2,edge_neighbor:2,edge_set:2,edge_size_dist:[2,12,16],edge_uid:2,edges_kwarg:5,edges_nam:2,edgeset:2,edit:10,effect:2,eg:0,eisfeld:14,either:[2,7,12,16],element:[0,2,5,7,12],element_subset:2,elements_by_level:2,emili:[13,14],emploi:2,employe:13,empti:[2,7,12],en:2,encapsul:12,end:2,endors:13,energi:13,ensur:2,ent1:2,ent2:2,entir:17,entiti:[3,4,5,7,8,9,11,13],entityset:[2,7],entri:[0,2,7,12],env:[10,12],environ:[9,13],epj:[0,14],epjd:[0,14],eq:0,eq_class:2,equal:[0,2,7,12],equat:0,equival:[0,2,12],equivalence_class:2,error:[0,2,12],essenc:0,et:[0,14],euler:17,evalu:2,even:11,event:11,everi:[0,2,7,12,17],everyth:17,ex:[2,10],exactli:7,exampl:[0,2,5,10,13,17],exceed:2,except:7,execut:10,exemplari:11,exhibit:0,exist:[0,2,5,7],expand:[5,17],explicit:0,explor:[8,9],expos:2,express:[11,13],ext:0,extend:17,extens:10,f:14,facecolor:5,fail:2,fals:[0,2,5,12,16],fan:14,fast:2,faster:12,favor:13,featur:[0,9],feng:14,ferrario:0,figur:5,file:[2,10,11],filepath:2,fill:16,fillna:2,filter:12,find:[5,8,9],firoz:14,first:[2,5],firstlevel:2,fit:11,fix:2,flexibl:2,fly:12,follow:[2,5,10,11,13],forc:17,fork:10,form:[1,4,11],format:[2,12,16],forth:12,found:[2,8,9],four:13,fpath:2,frac:12,fraction:[5,12],frame:2,from:[0,2,5,7,10,12,14,16,17],from_bipartit:[2,7],from_datafram:2,from_numpy_arrai:2,frozen:2,frozenset:2,fruchterman_reingold_layout:5,full:2,fullregistri:2,further:5,g:[0,5,12,14,16],gene:14,gener:[0,2,5,7,8,9,10,16],get_default_radiu:5,get_frozenset_label:5,get_id:2,get_line_graph:5,get_linegraph:2,get_nam:2,get_set_lay:5,get_singleton:12,github:[10,17],give:[2,17],given:[0,2,5,7,12],glossari:9,go:16,goal:12,good:[0,11],googl:13,gotten:2,gov:[8,9,13],govern:13,grant:11,graph:[0,2,5,7,8,9,12,14,17],greater:[0,2],group:0,grow:[8,9,13],guarante:5,h:[0,2,5,16],h_k:0,ha:[2,7,12,13,17],halfmann:14,handl:5,harmon:[0,12],hashabl:2,have:[0,2,5,7,8,9,12,13,17],header:[2,13],heath:14,held:2,heller:14,help:17,helper:[2,5],henc:2,henri:14,here:[12,17],herebi:11,herein:[11,13],hereinaft:11,hicss:14,hidden:17,hide:17,high:[0,12,13,14],higher:0,highlight:13,hist:16,hit:17,hnx:[0,2,10,12,13,17],hnxwidget:17,hold:17,holder:11,home:9,homolog:[1,4,8,9,13],homology_basi:0,homology_mod2:[1,4],honor:2,howev:11,hpda:13,html:10,http:[0,10,14],hugh:14,hull:5,hunter:14,hyper:[2,5,7,17],hyperedg:[2,7,8,9,12],hypergraph:[0,3,4,5,7,8,9,12,13,14,16,17],hypergraph_homology_basi:0,hypergraphedg:2,hypernet:13,hypernetwork:[0,14],hypernetx:[2,11,13],hypernetxerror:[0,2],hypernetxwidget:17,i:[0,2,7,12,17],i_m:0,i_n:0,icc:14,id:[2,5,7,12],ideal:0,ident:[0,2,5,17],identifi:[0,2,14],idx:2,ignacio:14,ignor:[0,2],illustr:5,im:0,imag:0,image_basi:0,immut:2,implement:12,impli:[5,11,13],impos:7,improv:17,incid:[2,7,8,9,12,16],incidence_dict:2,incidence_matrix:2,incidence_to_adjac:2,incident:11,includ:[2,8,9,11],inclus:[0,2],inde:2,independ:[5,17],index:[0,2,7,9,10],indic:[0,2,12],indirect:11,induc:[2,7],inf:2,infin:2,infinit:7,inflat:5,inflate_kwarg:5,info:16,info_dict:16,inform:[2,13,16],infring:13,inner:0,inseper:2,insert:2,insid:2,inspect:13,instal:[2,9],instanc:[2,7],instanti:[2,7],instead:[2,5,12],institut:[11,13],instruct:10,integ:[0,2,5,7,12,16],intend:5,intens:2,inter:2,interact:[13,17],interest:[0,2],interfac:17,intern:2,interpret:[0,12],interpreted_basi:0,interrupt:11,intersect:[0,2,5,7],intuit:7,invers:0,invert:0,investig:13,invis:5,is_bipartit:2,is_connect:2,is_empti:2,is_s_connect:12,isn:2,isomorph:[2,7],isstat:2,item:[2,5,16],iter:[0,2,5,16],ith:0,iti:7,its:[0,2,5,7,12,13,17],itself:[2,7],j:[0,7,14],jacob:14,jason:14,javascript:[13,17],jefferson:14,jenkin:14,ji:13,joslyn:[0,13,14],jth:0,jupyt:[10,13],jurisdict:13,k:[0,2,7],kaminski:14,katrina:14,kawaoka:14,kbasi:0,kchain:0,kchainbasi:0,kdx:2,keep:[2,16,17],kei:[0,2,5,7,12],kelli:14,kernel:0,kevin:14,keyindex:2,keyword:[2,5],km1basi:0,known:2,kocher:14,krang:0,kritzstein:13,kth:0,kving:14,kwarg:[0,2,5],l:[0,12,14],lab:2,label:[0,2,5],label_alpha:5,laboratori:13,larg:2,larger:17,largest:2,larissa:14,last:2,lastlevel:2,latter:2,lawfulli:11,layer:5,layout:5,layout_hyper_edg:5,layout_kwarg:5,layout_node_link:5,layout_two_column:5,le:14,learn:[8,9],leas:7,least:[2,5,7],lectur:14,left:[0,5],legal:13,len:16,length:[0,2,5,7,8,9],lesmi:13,less:[0,2,12],let:2,level1:2,level2:2,level:[2,5,7],levelset:[2,7],liabil:[11,13],liabl:11,librari:[0,2,8,9,12,13],licens:9,like:[2,5],limit:[2,11],line:[0,2,5,12],linecollect:5,linegraph:[0,2,7],linewidth:5,link:[2,17],linux:[10,13],linv:0,lisa:14,list:[0,2,5,11,12,16],liu:[12,13],llinv:0,lm:0,lmr:0,local:12,locat:[5,10,17],logic:0,logical_dot:0,logical_matadd:0,logical_matmul:0,longer:2,longest:[0,2],look:0,loss:11,loui:14,lower:5,lumsdain:13,m:[0,2,14],mac:[10,17],made:2,mai:[2,7,8,9,10,11,13,17],main:17,make:[2,5,13],manag:13,mani:[2,12,16],manipul:2,manual:5,manufactur:13,map:[0,5],marcin:14,mark:13,marrero:[0,14],mat1:0,mat2:0,mat:0,match:2,materi:13,mathbb:0,mathemat:13,matmulreduc:0,matplotlib:[5,10],matric:[0,5],matrix:[0,2,7,12,16],max:[0,16],max_degre:12,max_depth:2,max_level:2,max_siz:[2,12],maxim:[2,7],maximum:[2,7],maxlevel:2,mcdermott:14,mean:[0,2,16],measur:[1,4,13],median:[5,16],member:2,membership:[2,5,7,17],memori:[11,12,13],menacheri:14,merchant:11,merg:[2,11],merge_ent:2,method:[0,2,7,8,9,13,16],metric:[0,8,9,13],michael:14,might:17,min:[0,16],min_degre:12,min_level:2,min_siz:12,minim:[0,5,10,17],minimum:[2,5],minlevel:2,minu:[0,2],miss:5,mitchel:14,mod2:[1,4,13],mod:0,model:[8,9,13,14],modestli:2,modif:11,modifi:11,modul:[1,3,4,6,9,15],modulo:0,more:[2,7,8,9,10,12],most:[2,5,8,9],much:12,multi:[2,8,9],multidimension:14,multipl:[0,2,7,12,17],multipli:0,multiwai:[8,9],must:[0,2,11,12],mxn:0,n:[0,2,5,7,10,12],nama:2,name:[2,10,11,12,13,14,17],nan:2,natali:14,nation:13,natur:[8,9],navig:2,ncell:16,ncol:16,ndarrai:[0,2],necessarili:13,need:[0,2,5,10],neglig:11,neighbor:[2,12],neither:[11,13],neq:12,nest:2,nested_incidence_dict:2,network:[2,8,9,14],networkx:[2,5],netwrokx:5,newfpath:2,newuid:2,node:[0,2,5,7,12,13,16,17],node_column_nam:2,node_diamet:2,node_incid:12,node_label:[2,5],node_labels_kwarg:5,node_nam:2,node_radiu:5,node_set:2,node_size_dist:12,nodes_kwarg:5,nodes_nam:2,nodeset:2,non:[0,7],none:[0,2,5,12,16],nonempti:[2,7],nonexist:2,nonzero:7,nor:13,normal:[1,4,12],northwest:13,note:[0,2,7,10,12,14],notebook:[10,13],noth:2,notic:[9,11],np:[0,2],nrow:16,num:16,number:[0,2,5,7,12,16],number_of_edg:[2,12],number_of_nod:[2,12],numer:2,numpi:[0,2,5,12],nwgraph:12,nwhy:[0,2,9,10,13],nwhypergraph:[2,9],nx2:5,nx:[2,5,7],nxm:0,o:14,obj:16,object:[2,7,12,13,16],obtain:[0,2,7,11],occupi:7,occur:2,off:2,offer:2,offset:5,onc:[10,13],one:[0,2,5,7,12],oneapi:12,onetbb:12,onli:[0,2,7,10,12],open:10,oper:13,opinion:13,opt:12,optim:[5,9,12,13,17],option:[0,2,9,16],order:[0,2,5,14],ordereddict:2,org:[0,14],organ:13,orient:5,origin:[0,2,12],ortiz:0,osx:10,other:[0,2,5,7,11,12],otherwis:[2,7,10,11,12,13],our:[0,8,9],out:[0,5,8,9,11],outlin:17,output:2,outsid:2,over:[0,5,7,12],overlap:[5,12],overrid:5,overview:9,own:[7,13],p:[0,2,14],pacif:13,packag:[1,3,6,9,15],page:9,pair:[0,2,5,7,12],pairwis:2,panda:[2,13],paper:5,parallel:[5,12],param:2,paramet:[0,2,5,16],part:[5,13],partial_k:0,particular:[2,8,9,11,13],partit:[2,7],pass:[0,2,5,12],path:[0,2,5,8,9,10,12],pathogen:14,pd:2,perfect:17,perform:[2,12,13,14,17],permiss:11,permit:11,person:11,peter:14,pickl:2,pin:17,pip:[9,17],place:2,placehold:2,placement:17,planar:5,pleas:2,plot:5,pnnl:[8,9,10,13],po:5,point:5,poli:5,polycollect:5,polygon:5,poset:2,posit:[0,2,5,7,12,16,17],possibl:[5,11,17],post:0,power:[8,9],powershel:10,pp:14,practic:2,praggasti:[13,14],pre:5,precis:7,prefil:2,preliminari:12,prepar:13,prepend:2,present:2,preserv:[2,17],press:14,princip:13,principl:13,print:[0,16],prior:2,privat:13,proc:14,process:[2,12,13],procur:11,product:[0,13],profit:11,program:13,project:13,prompt:10,prop:2,properli:7,properti:[2,7,12,17],provid:[0,2,5,8,9,11,12],ps1:10,publish:11,purpos:[0,11],purvin:[13,14],put:16,py:7,pybind11:12,pytest:10,python:[10,12],qing:14,quantiti:[8,9],question:[8,9,13],quick:5,quit:2,r0:5,r:[0,5],rac:0,radiu:5,rais:[0,2],ralph:14,rang:[0,5],rather:16,ratio:[0,16],rauga:13,re:17,reachabl:12,read:[5,13],real:2,reason:[2,5],receiv:2,reciproc:[0,12],recommend:[2,5,13],recov:2,recover_from_st:2,rectangular:[0,7],recurs:0,redistribut:11,reduc:[0,5],reduced_row_echelon_form_mod2:0,refer:[2,13],referenc:[0,2],reflect:[2,13],regist:2,registri:[2,7],rel:[0,17],relat:[2,8,9],relationship:[0,2,8,9,14],releas:[13,17],remov:[2,17],remove_edg:2,remove_el:2,remove_elements_from:2,remove_nod:2,remove_singleton:2,remove_stat:2,render:5,rep:2,repeatedli:0,replac:[0,2],report:[4,9],repositori:[8,9],repres:[0,2,5,7,8,9,13],represent:[0,5,12],reproduc:[5,11],request:2,requir:[0,2,12],research:[8,9,13],reserv:5,respect:[0,2],respons:[13,14],restrict:[2,7],restrict_to:2,restrict_to_edg:2,restrict_to_indic:2,restrict_to_level:2,restrict_to_nod:2,result:[5,17],retain:11,retriev:2,return_count:2,return_equal_class:12,return_equivalence_class:2,return_index:2,return_po:5,return_singleton:[0,2,16],revers:[0,2,17],rich:12,right:[0,5,13],rigor:5,ring:5,role:[2,7],root:2,row:[0,2,7,12,16],rowdict:2,rubber:5,rubber_band:[4,6],run:[10,12,13],s12859:14,s13688:[0,14],s:[1,2,4,5,7,12,13,14,16],s_betweenness_centr:[0,12],s_centrality_measur:[1,4],s_closeness_centr:[0,12],s_comp_dist:16,s_compon:2,s_component_subgraph:2,s_components_subgraph:2,s_connect:2,s_connected_compon:[2,12],s_degre:[2,12],s_diamet:12,s_distanc:12,s_eccentr:[0,12],s_edge_connect:2,s_edge_diameter_dist:16,s_harmonic_centr:0,s_harmonic_closeness_centr:[0,12],s_linegraph:12,s_neighbor:12,s_node_diameter_dist:16,s_path:12,same:[0,2,5,7,12],sampl:2,satifi:2,satisfi:[2,7],save:2,save_st:2,scalabl:12,sci:0,scienc:[0,14],scip:2,scipi:[0,2],score:12,script:10,search:9,second:2,see:[2,5,7,10,13,16],self:2,sell:11,sens:7,sensibl:5,sequenc:[2,7],serv:[8,9],servic:[11,13],set:[0,2,5,7,8,9,12,17],set_nam:2,set_stat:2,setsystem:2,sh:10,shabang:10,shall:11,shallow:2,shape:2,share:[2,7,12],sheahan:14,shift:17,shortest:[0,2,7,12],shortest_path_length:2,should:[0,2,5],show:17,shufang:14,side:[0,2],sigma:[0,12],signatur:2,significantli:12,sim:14,similar:[2,17],simpl:[0,2,7,16],simplic:[8,9],simplici:[0,8,9],sinan:[13,14],sinc:[2,7,8,9],singl:[0,2,7,16],singleton:[0,2,8,9,12],size:[0,2,5,7,12,16,17],slightli:17,slower:12,small:[0,2,5],smaller:5,smallest:2,smith:[1,4,14],smith_normal_form_mod2:0,snf:0,so:[2,5,11],softwar:[11,13],some:[7,8,9,10],sometim:[5,17],song:14,sort:2,sort_column:2,sort_row:2,sortabl:[0,2],sourc:[0,2,5,10,11,16],space:[5,12],spars:[0,2,12],special:11,specif:[2,7,13],specifi:[2,5,10,12,17],spectral:5,sped:13,sponsor:13,spring_layout:5,springer:14,squar:7,src:12,stack:5,standard:16,start:[0,2,5,16,17],stat:16,state:[2,13,17],state_dict:2,staticent:[3,4],staticentityset:2,statist:16,still:2,storag:2,store:[0,2,12],str:[0,2],stratton:14,strict:[8,9,11],string:[2,5,16],structur:[2,7,8,9,12],studi:[8,9,13],style:5,subgraph:[0,2],subhypergraph:7,subject:11,sublicens:11,submatrix:7,submit:2,submodul:[1,3,4,6,15],subset:[2,5,7],substitut:11,subtract:2,success:7,sum:[0,12],sum_:[0,12],summari:16,suppli:5,support:[2,13],sure:2,surround:5,swap:0,swap_column:0,swap_row:0,symp:14,system:[2,5,8,9,10,14],t:[0,2,12],tabl:17,take:[2,5],tan:14,target:2,tbb:10,tbbroot:12,techniqu:5,tell:[8,9],tensor:2,term:[0,2],test:10,text:5,textbook:5,thackrai:14,than:[0,2,7,11,16],thei:[2,5,7,8,9,17],them:[2,7,10,16,17],theori:11,therebi:[8,9],therefor:[2,12],thereof:13,thi:[0,2,5,7,8,9,10,11,12,13,16,17],think:2,those:[0,13],three:12,through:[0,5,12],tiffani:14,time:[0,17],timothi:14,todo:2,togeth:[0,5],toggl:17,toni:[12,13],tool:[8,9],toolbar:17,toplex:[0,2,7,12,16],toplex_dist:16,topolog:[0,8,9,14],tort:11,total:0,tour:13,track:[0,2,16],trade:13,trademark:13,tradit:17,transform:[0,2],translat:2,translate_arr:2,transpar:5,transpos:2,transpose_inflated_kwarg:5,travers:17,treat:2,triloop:13,tripodi:14,trivial:0,truthi:2,tupl:[0,2],turn_entity_data_into_datafram:2,tutori:[2,9,10],two:[0,2,5,7,12,17],two_column:[4,6],type:[0,2,5,16],typic:5,u:[0,5,12],uid:[0,2,7,16],uidset:[2,7],uidset_by_level:2,un:17,under:[12,13],undesir:2,undirect:12,uniform:0,uniqu:[2,7],unit:13,unless:2,unpack:2,unreach:12,unweight:[2,7,12],up:[2,13,16],updat:2,upgrad:12,upon:17,us:[0,2,5,7,8,9,11,13],use_nwhi:[0,2],use_rep:2,user:[2,8,9,10,12,13,17],usual:5,util:[4,6],v0:2,v1:2,v2:2,v:[0,2,5,12,14],v_1:2,v_2:2,v_end:2,v_n:2,v_start:2,valu:[0,2,5,7,12],variou:[12,16],ve:13,vector:0,verifi:0,version:[9,10,12],vertex:[0,5,8,9,12],vertic:[0,2,5,12],via:[0,14],view:13,vineet:14,viral:14,virtual:10,virtualenv:9,visibl:17,visual:[9,13,17],vn:2,w:2,wa:[2,12,13],wai:[2,5,8,9,11],walk:[0,2,7,8,9,14],walter:14,want:[0,17],warranti:[11,13],water:14,waw:14,we:[0,2,8,9,12,13],web:14,weight:[2,7,12],well:[5,17],westhoff:14,what:[8,9],whatsoev:11,when:[2,12],whera:17,where:[0,2,5,7,12],whether:[2,11,12],which:[0,2,5,7,16,17],whitespac:5,whole:10,whose:[5,7,12],widget:[9,13],width:[2,7,8,9],window:[10,17],wish:10,with_color:5,with_edge_count:5,with_edge_label:5,with_node_count:5,with_node_label:5,within:[2,5,17],without:[11,17],work:[0,2,5,10,13],would:[2,13],wrangl:2,wrap:5,written:11,wshop:14,www:[0,14],x:[2,5,12,16],xor:0,xu:12,xx:2,xy:5,xyz:0,y:[2,5,12],yet:2,yield:2,yoshihiro:14,you:[2,5,8,9,10,13,17],young:13,your:[2,10,13],yun:13,z:[0,2],z_2:0,zalewski:14,zero:2},titles:["algorithms package","algorithms","classes package","classes","HyperNetX Packages","drawing package","drawing","Glossary of HNX terms","HyperNetX (HNX)","HyperNetX (HNX)","Installing HyperNetX","License","NWHy","Overview","Publications","reports","reports package","Hypernetx-Widget"],titleterms:{"0":13,"1":13,"class":[2,3,12],"import":12,"new":13,"public":14,Then:12,To:[10,12],activ:12,algorithm:[0,1],an:[10,12],anaconda:[10,12],api:12,attribut:12,block:12,build:12,central:0,colab:13,content:[0,2,5,9,16],descript:[8,9,12],descriptive_stat:16,draw:[5,6],entiti:2,environ:[10,12],featur:[13,17],form:0,glossari:7,hnx:[7,8,9],homolog:0,homology_mod2:0,hypergraph:2,hypernetx:[4,8,9,10,17],indic:9,instal:[10,12,17],intel:12,layout:17,licens:[11,13],measur:0,method:12,mod2:0,modul:[0,2,5,12,16],normal:0,notic:13,nwhy:12,nwhypergraph:12,option:10,other:17,overview:[13,17],packag:[0,2,4,5,16],panel:17,pip:[10,12],quick:12,report:[15,16],rubber_band:5,s:0,s_centrality_measur:0,select:17,side:17,slinegraph:12,smith:0,staticent:2,submodul:[0,2,5,16],tabl:9,tbb:12,term:7,test:12,thread:12,tool:17,tutori:13,two_column:5,us:[10,12,17],util:5,version:13,virtualenv:10,widget:17}}) \ No newline at end of file +Search.setIndex({docnames:["algorithms/algorithms","algorithms/modules","classes/classes","classes/modules","core","drawing/drawing","drawing/modules","glossary","home","index","install","license","nwhy","overview/index","publications","reports/modules","reports/reports","widget"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["algorithms/algorithms.rst","algorithms/modules.rst","classes/classes.rst","classes/modules.rst","core.rst","drawing/drawing.rst","drawing/modules.rst","glossary.rst","home.rst","index.rst","install.rst","license.rst","nwhy.rst","overview/index.rst","publications.rst","reports/modules.rst","reports/reports.rst","widget.rst"],objects:{},objnames:{},objtypes:{},terms:{"0":[7,9,12,14],"00231":14,"020":14,"021":14,"030":14,"04197":14,"1":[7,9,12,14],"10":14,"1007":14,"1140":14,"11782":14,"1186":14,"12901":14,"15":14,"16":14,"17th":14,"1_1":14,"2":[7,12,13,14],"2003":14,"2018":11,"2019":14,"2020":14,"22":14,"287":14,"3":[10,12,13,14],"4":13,"48478":14,"5":13,"6":13,"7":[10,13],"755":10,"76rl01830":13,"9":[10,12,14],"978":14,"boolean":12,"case":13,"class":[4,7,8,9],"do":[7,11,12,13],"export":12,"int":14,"new":[9,12],"null":10,"public":[8,9],"return":[7,12],"static":13,"super":17,"switch":7,"true":12,"while":17,A:[7,11,12,14],AND:11,AS:11,As:[8,9],BE:11,BUT:11,BY:11,FOR:11,For:[7,8,9,10,12,13,17],IF:11,IN:11,IS:11,If:[7,10,12],In:[12,13],It:12,NO:11,NOT:11,OF:[11,13],ON:11,OR:11,SUCH:11,Such:11,THE:11,TO:11,The:[7,8,9,12,13,17],These:17,To:[8,9],_version:12,ab:14,about:[8,9],abov:11,ac05:13,accept:12,access:[7,10],accord:7,account:13,accuraci:13,aco5:13,activ:[10,17],ad:13,adam:14,adaptor:12,addon:[12,13,17],adjac:[7,8,9],admit:[8,9],advanc:17,advis:11,after:12,agenc:13,ah:14,aksoi:[13,14],al:14,algebra:[8,9],algorithm:[4,8,9,12,13,14,17],all:[7,10,12,13,17],allow:17,alreadi:17,also:[7,8,9,12,17],altern:17,ami:14,among:[8,9],an:[7,9,13,17],anaconda3:10,anaconda:9,analysi:12,analyt:[13,14],andrew:13,ani:[7,11,12,13,17],anoth:7,api:9,apparatu:13,appear:17,ar:[7,8,9,10,11,12,17],arbitrari:[8,9],arendt:[13,14],aris:11,arrai:12,articl:14,arxiv:14,associ:[11,12],assum:13,attribut:7,author:13,auxiliari:7,avail:17,averag:12,azsecur:14,b:[7,14],back:12,background:17,baric:14,base:[7,12,13,17],basic:[7,8,9,13],bat:10,battel:[11,13],becaus:[8,9],been:12,belong:[7,12],below:10,between:[7,12,17],big:14,binari:11,bioinformat:14,biolog:14,biomedcentr:14,bipartit:[7,17],bmc:14,bmcbioinformat:14,book:13,both:[7,8,9,12,17],bramer:14,brenda:[13,14],brett:14,brian:13,browser:[10,13],bsd:13,build:10,build_doc:10,built:17,bulk:17,busi:11,button:17,c:[9,10,12,13,14],c_b:12,ca:14,call:[7,12],callahan:14,can:[7,8,9,12,13,17],capabl:17,carlo:14,caus:[11,17],central:[12,13],chang:17,check:[8,9,12],children:7,chmod:10,circl:17,claus:13,click:17,cliff:[13,14],cliqu:[8,9],clone:10,close:12,cockrel:14,code:11,col:12,colab:9,collaps:[12,17],collapse_edg:12,collapse_nod:12,collapse_nodes_and_edg:12,color:17,column:[7,12,13],com:[10,14],combin:12,come:10,command:[10,17],comment:[8,9,13],commerci:13,commun:[8,9,13],compar:12,complet:[7,13,17],complex:[8,9,12,14],compon:[7,12],comput:[13,14],conda:[10,12],condit:[7,11],conf:14,connect:[7,8,9,12],consent:11,consequenti:11,constitut:13,construct:[7,12,13],constructor:[12,13],contact:[8,9,13],contain:[7,12,17],content:[1,3,4,6,15],continu:10,contract:[11,13],contributor:[8,9,11,13],control:17,cooper:13,copi:[11,12],copyright:11,correspond:7,creat:[10,12,13],criteria:12,critic:14,ctrl:17,current:12,cybersecur:14,d:[12,14],damag:11,daniel:14,data:[8,9,11,12,13,14],datafram:13,de:[13,17],degre:[7,12,17],demo:17,depart:13,depend:12,depth:7,descriptive_stat:[4,15],design:13,dest:12,detail:17,develop:[8,9,12,13],diagram:17,diamet:[7,12],diamond:14,dictionari:[7,12],differ:12,dim:12,dimens:12,dimension:[8,9],direct:[11,12,17],directli:[8,9,13,17],disclaim:11,disclos:13,disjoint:7,distanc:[7,12],distinguish:[7,8,9],distribut:[11,12],doc:10,document:[10,11],doe:13,doi:14,domain:14,done:12,down:17,drag:17,draw:[4,9],dual:7,dustin:[13,14],dynam:7,e:[7,10,12,14,17],each:[7,12,17],eccentr:12,ed:14,edg:[7,8,9,12,13,17],edge_incid:12,edge_size_dist:12,edit:10,eisfeld:14,either:[7,12],element:[7,12],emili:[13,14],employe:13,empti:[7,12],encapsul:12,endors:13,energi:13,entir:17,entiti:[3,4,7,8,9,11,13],entityset:7,entri:[7,12],env:[10,12],environ:[9,13],epj:14,epjd:14,equal:[7,12],equival:12,error:12,et:14,euler:17,even:11,event:11,everi:[7,12,17],everyth:17,ex:10,exactli:7,exampl:[10,13,17],except:7,execut:10,exemplari:11,exist:7,expand:17,explor:[8,9],express:[11,13],extend:17,extens:10,f:14,fals:12,fan:14,faster:12,favor:13,featur:9,feng:14,file:[10,11],filter:12,find:[8,9],firoz:14,fit:11,fly:12,follow:[10,11,13],forc:17,fork:10,form:11,format:12,forth:12,found:[8,9],four:13,frac:12,fraction:12,from:[7,10,12,14,17],from_bipartit:7,g:[12,14],gene:14,gener:[7,8,9,10],get_singleton:12,github:[10,17],give:17,given:[7,12],glossari:9,goal:12,good:11,googl:13,gov:[8,9,13],govern:13,grant:11,graph:[7,8,9,12,14,17],grow:[8,9,13],ha:[7,12,13,17],halfmann:14,harmon:12,have:[7,8,9,12,13,17],header:13,heath:14,heller:14,help:17,henri:14,here:[12,17],herebi:11,herein:[11,13],hereinaft:11,hicss:14,hidden:17,hide:17,high:[12,13,14],highlight:13,hit:17,hnx:[10,12,13,17],hnxwidget:17,hold:17,holder:11,home:9,homolog:[8,9,13],homology_mod2:[1,4],howev:11,hpda:13,html:10,http:[10,14],hugh:14,hunter:14,hyper:[7,17],hyperedg:[7,8,9,12],hypergraph:[3,4,7,8,9,12,13,14,17],hypernet:13,hypernetwork:14,hypernetx:[11,13],hypernetxwidget:17,i:[7,12,17],icc:14,id:[7,12],ident:17,identifi:14,ignacio:14,implement:12,impli:[11,13],impos:7,improv:17,incid:[7,8,9,12],incident:11,includ:[8,9,11],independ:17,index:[7,9,10],indic:12,indirect:11,induc:7,infinit:7,inform:13,infring:13,inspect:13,instal:9,instanc:7,instanti:7,instead:12,institut:[11,13],instruct:10,integ:[7,12],interact:[13,17],interfac:17,interpret:12,interrupt:11,intersect:7,intuit:7,investig:13,is_s_connect:12,isomorph:7,iti:7,its:[7,12,13,17],itself:7,j:[7,14],jacob:14,jason:14,javascript:[13,17],jefferson:14,jenkin:14,ji:13,joslyn:[13,14],jupyt:[10,13],jurisdict:13,k:7,kaminski:14,katrina:14,kawaoka:14,keep:17,kei:[7,12],kelli:14,kevin:14,kocher:14,kritzstein:13,kving:14,l:[12,14],laboratori:13,larger:17,larissa:14,lawfulli:11,le:14,learn:[8,9],leas:7,least:7,lectur:14,legal:13,length:[7,8,9],lesmi:13,less:12,level:7,levelset:7,liabil:[11,13],liabl:11,librari:[8,9,12,13],licens:9,limit:11,line:12,linegraph:7,link:17,linux:[10,13],lisa:14,list:[11,12],liu:[12,13],local:12,locat:[10,17],loss:11,loui:14,lumsdain:13,m:14,mac:[10,17],mai:[7,8,9,10,11,13,17],main:17,make:13,manag:13,mani:12,manufactur:13,marcin:14,mark:13,marrero:14,materi:13,mathemat:13,matplotlib:10,matrix:[7,12],max_degre:12,max_siz:12,maxim:7,maximum:7,mcdermott:14,measur:13,membership:[7,17],memori:[11,12,13],menacheri:14,merchant:11,merg:11,method:[7,8,9,13],metric:[8,9,13],michael:14,might:17,min_degre:12,min_siz:12,minim:[10,17],mitchel:14,mod2:13,model:[8,9,13,14],modif:11,modifi:11,modul:[1,3,4,6,9,15],more:[7,8,9,10,12],most:[8,9],much:12,multi:[8,9],multidimension:14,multipl:[7,12,17],multiwai:[8,9],must:[11,12],n:[7,10,12],name:[10,11,12,13,14,17],natali:14,nation:13,natur:[8,9],necessarili:13,need:10,neglig:11,neighbor:12,neither:[11,13],neq:12,network:[8,9,14],node:[7,12,13,17],node_incid:12,node_size_dist:12,non:7,none:12,nonempti:7,nonzero:7,nor:13,normal:12,northwest:13,note:[7,10,12,14],notebook:[10,13],notic:[9,11],number:[7,12],number_of_edg:12,number_of_nod:12,numpi:12,nwgraph:12,nwhy:[9,10,13],nwhypergraph:9,nx:7,o:14,object:[7,12,13],obtain:[7,11],occupi:7,onc:[10,13],one:[7,12],oneapi:12,onetbb:12,onli:[7,10,12],open:10,oper:13,opinion:13,opt:12,optim:[9,12,13,17],option:9,order:14,org:14,organ:13,origin:12,osx:10,other:[7,11,12],otherwis:[7,10,11,12,13],our:[8,9],out:[8,9,11],outlin:17,over:[7,12],overlap:12,overview:9,own:[7,13],p:14,pacif:13,packag:[1,3,6,9,15],page:9,pair:[7,12],panda:13,parallel:12,part:13,particular:[8,9,11,13],partit:7,pass:12,path:[8,9,10,12],pathogen:14,perfect:17,perform:[12,13,14,17],permiss:11,permit:11,person:11,peter:14,pin:17,pip:[9,17],placement:17,pnnl:[8,9,10,13],posit:[7,12,17],possibl:[11,17],power:[8,9],powershel:10,pp:14,praggasti:[13,14],precis:7,preliminari:12,prepar:13,preserv:17,press:14,princip:13,principl:13,privat:13,proc:14,process:[12,13],procur:11,product:13,profit:11,program:13,project:13,prompt:10,properli:7,properti:[7,12,17],provid:[8,9,11,12],ps1:10,publish:11,purpos:11,purvin:[13,14],py:7,pybind11:12,pytest:10,python:[10,12],qing:14,quantiti:[8,9],question:[8,9,13],ralph:14,rauga:13,re:17,reachabl:12,read:13,reciproc:12,recommend:13,rectangular:7,redistribut:11,refer:13,reflect:13,registri:7,rel:17,relat:[8,9],relationship:[8,9,14],releas:[13,17],remov:17,report:[4,9],repositori:[8,9],repres:[7,8,9,13],represent:12,reproduc:11,requir:12,research:[8,9,13],respons:[13,14],restrict:7,result:17,retain:11,return_equal_class:12,revers:17,rich:12,right:13,role:7,row:[7,12],rubber_band:[4,6],run:[10,12,13],s12859:14,s13688:14,s:[7,12,13,14],s_betweenness_centr:12,s_centrality_measur:[1,4],s_closeness_centr:12,s_connected_compon:12,s_degre:12,s_diamet:12,s_distanc:12,s_eccentr:12,s_harmonic_closeness_centr:12,s_linegraph:12,s_neighbor:12,s_path:12,same:[7,12],satisfi:7,scalabl:12,scienc:14,score:12,script:10,search:9,see:[7,10,13],sell:11,sens:7,sequenc:7,serv:[8,9],servic:[11,13],set:[7,8,9,12,17],sh:10,shabang:10,shall:11,share:[7,12],sheahan:14,shift:17,shortest:[7,12],show:17,shufang:14,sigma:12,significantli:12,sim:14,similar:17,simpl:7,simplic:[8,9],simplici:[8,9],sinan:[13,14],sinc:[7,8,9],singl:7,singleton:[8,9,12],size:[7,12,17],slightli:17,slower:12,smith:14,so:11,softwar:[11,13],some:[7,8,9,10],sometim:17,song:14,sourc:[10,11],space:12,spars:12,special:11,specif:[7,13],specifi:[10,12,17],sped:13,sponsor:13,springer:14,squar:7,src:12,start:17,state:[13,17],staticent:[3,4],store:12,stratton:14,strict:[8,9,11],structur:[7,8,9,12],studi:[8,9,13],subhypergraph:7,subject:11,sublicens:11,submatrix:7,submodul:[1,3,4,6,15],subset:7,substitut:11,success:7,sum:12,sum_:12,support:13,symp:14,system:[8,9,10,14],t:12,tabl:17,tan:14,tbb:10,tbbroot:12,tell:[8,9],test:10,thackrai:14,than:[7,11],thei:[7,8,9,17],them:[7,10,17],theori:11,therebi:[8,9],therefor:12,thereof:13,thi:[7,8,9,10,11,12,13,17],those:13,three:12,through:12,tiffani:14,time:17,timothi:14,toggl:17,toni:[12,13],tool:[8,9],toolbar:17,toplex:[7,12],topolog:[8,9,14],tort:11,tour:13,trade:13,trademark:13,tradit:17,travers:17,triloop:13,tripodi:14,tutori:[9,10],two:[7,12,17],two_column:[4,6],u:12,uid:7,uidset:7,un:17,under:[12,13],undirect:12,uniqu:7,unit:13,unreach:12,unweight:[7,12],up:13,upgrad:12,upon:17,us:[7,8,9,11,13],user:[8,9,10,12,13,17],util:[4,6],v:[12,14],valu:[7,12],variou:12,ve:13,version:[9,10,12],vertex:[8,9,12],vertic:12,via:14,view:13,vineet:14,viral:14,virtual:10,virtualenv:9,visibl:17,visual:[9,13,17],wa:[12,13],wai:[8,9,11],walk:[7,8,9,14],walter:14,want:17,warranti:[11,13],water:14,waw:14,we:[8,9,12,13],web:14,weight:[7,12],well:17,westhoff:14,what:[8,9],whatsoev:11,when:12,whera:17,where:[7,12],whether:[11,12],which:[7,17],whole:10,whose:[7,12],widget:[9,13],width:[7,8,9],window:[10,17],wish:10,within:17,without:[11,17],work:[10,13],would:13,written:11,wshop:14,www:14,x:12,xu:12,y:12,yoshihiro:14,you:[8,9,10,13,17],young:13,your:[10,13],yun:13,zalewski:14},titles:["algorithms package","algorithms","classes package","classes","HyperNetX Packages","drawing package","drawing","Glossary of HNX terms","HyperNetX (HNX)","HyperNetX (HNX)","Installing HyperNetX","License","NWHy","Overview","Publications","reports","reports package","Hypernetx-Widget"],titleterms:{"0":13,"1":13,"class":[2,3,12],"import":12,"new":13,"public":14,Then:12,To:[10,12],activ:12,algorithm:[0,1],an:[10,12],anaconda:[10,12],api:12,attribut:12,block:12,build:12,colab:13,content:[0,2,5,9,16],descript:[8,9,12],descriptive_stat:16,draw:[5,6],entiti:2,environ:[10,12],featur:[13,17],glossari:7,hnx:[7,8,9],homology_mod2:0,hypergraph:2,hypernetx:[4,8,9,10,17],indic:9,instal:[10,12,17],intel:12,layout:17,licens:[11,13],method:12,modul:[0,2,5,12,16],notic:13,nwhy:12,nwhypergraph:12,option:10,other:17,overview:[13,17],packag:[0,2,4,5,16],panel:17,pip:[10,12],quick:12,report:[15,16],rubber_band:5,s_centrality_measur:0,select:17,side:17,slinegraph:12,staticent:2,submodul:[0,2,5,16],tabl:9,tbb:12,term:7,test:12,thread:12,tool:17,tutori:13,two_column:5,us:[10,12,17],util:5,version:13,virtualenv:10,widget:17}}) \ No newline at end of file diff --git a/docs/build/widget.html b/docs/build/widget.html index 6a53528f..06447db2 100644 --- a/docs/build/widget.html +++ b/docs/build/widget.html @@ -7,7 +7,7 @@ - Hypernetx-Widget — HyperNetX 1.0.0 documentation + Hypernetx-Widget — HyperNetX 1.0.1 documentation diff --git a/docs/source/conf.py b/docs/source/conf.py index e2f7b209..76582fce 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,7 +19,7 @@ import os import shlex -__version__ = "1.0.0" +__version__ = "1.0.1" # If extensions (or modules to document with autodoc) are in another directory, diff --git a/setup.py b/setup.py index cec73023..87c61475 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup import sys -__version__ = "1.0" +__version__ = "1.0.1" if sys.version_info < (3, 7): sys.exit("HyperNetX requires Python 3.7 or later.")