Source code for xgi.convert.line_graph

"""Method for converting to a line graph."""

from collections import defaultdict
from itertools import combinations

from ..exception import XGIError

__all__ = ["to_line_graph"]


[docs]def to_line_graph(H, s=1, weights=None): """The s-line graph of the hypergraph. The s-line graph of the hypergraph `H` is the graph whose nodes correspond to each hyperedge in `H`, linked together if they share at least s vertices. Optional edge weights correspond to the size of the intersection between the hyperedges, optionally normalized by the size of the smaller hyperedge. Parameters ---------- H : Hypergraph The hypergraph of interest s : int The intersection size to consider edges as connected, by default 1. weights : str or None Specify whether to return a weighted line graph. If None, returns an unweighted line graph. If 'absolute', includes edge weights corresponding to the size of intersection between hyperedges. If 'normalized', includes edge weights normalized by the size of the smaller hyperedge. Returns ------- LG : networkx.Graph The line graph associated to the Hypergraph References ---------- "Hypernetwork science via high-order hypergraph walks", by Sinan G. Aksoy, Cliff Joslyn, Carlos Ortiz Marrero, Brenda Praggastis & Emilie Purvine. https://doi.org/10.1140/epjds/s13688-020-00231-0 """ if weights not in [None, "absolute", "normalized"]: raise XGIError( f"{weights} not a valid weights option. Choices are " "None, 'absolute', and 'normalized'." ) import networkx as nx LG = nx.Graph() LG.add_nodes_from([(k, {"original_hyperedge": v}) for k, v in H._edge.items()]) edge_order = {edge: idx for idx, edge in enumerate(H._edge)} # Preserve the current behavior for s <= 0, which includes disjoint pairs. if s <= 0: for e1, e2 in combinations(H._edge, 2): intersection_size = len(H._edge[e1].intersection(H._edge[e2])) if not weights: LG.add_edge(e1, e2) else: weight = intersection_size if weights == "normalized": weight /= min(len(H._edge[e1]), len(H._edge[e2])) LG.add_edge( e1, e2, weight=weight, ) return LG overlap_sizes = defaultdict(int) for memberships in H._node.values(): if len(memberships) < 2: continue for e1, e2 in combinations(memberships, 2): if edge_order[e1] > edge_order[e2]: e1, e2 = e2, e1 overlap_sizes[(e1, e2)] += 1 if weights == "normalized": edge_sizes = {e: len(members) for e, members in H._edge.items()} for e1, e2 in sorted( overlap_sizes, key=lambda pair: (edge_order[pair[0]], edge_order[pair[1]]) ): intersection_size = overlap_sizes[(e1, e2)] if intersection_size < s: continue if not weights: # Add unweighted edge LG.add_edge(e1, e2) else: # Compute the (normalized) weight weight = intersection_size if weights == "normalized": weight /= min(edge_sizes[e1], edge_sizes[e2]) # Add edge with weight LG.add_edge( e1, e2, weight=weight, ) return LG