diff --git a/tests/convert/test_line_graph.py b/tests/convert/test_line_graph.py index eb1096612..68223b688 100644 --- a/tests/convert/test_line_graph.py +++ b/tests/convert/test_line_graph.py @@ -34,3 +34,83 @@ def test_to_line_graph(edgelist1, hypergraph1): assert L.number_of_nodes() == 0 assert L.number_of_edges() == 0 + + +def test_abs_weighted_line_graph(edgelist1, hypergraph1, hypergraph2): + H = xgi.Hypergraph(edgelist1) + L = xgi.to_line_graph(H) + + L = xgi.to_line_graph(H, weights="absolute") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {0, 1, 2, 3} + assert [set(e) for e in L.edges] == [{2, 3}] + assert L.edges[(2,3)]['weight'] == 1 + + L = xgi.to_line_graph(hypergraph1, weights="absolute") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {"e1", "e2", "e3"} + assert [set(e) for e in L.edges] == [{"e1", "e2"}, {"e2", "e3"}] + assert L.edges[("e1","e2")]['weight'] == 2 + assert L.edges[("e2","e3")]['weight'] == 1 + assert sum([dat["weight"] for _,_,dat in L.edges(data=True)]) == 3 + + + L = xgi.to_line_graph(hypergraph1, s=2, weights="absolute") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {"e1", "e2", "e3"} + assert [set(e) for e in L.edges] == [{"e1", "e2"}] + assert L.edges[("e1","e2")]['weight'] == 2 + assert sum([dat["weight"] for _,_,dat in L.edges(data=True)]) == 2 + + L = xgi.to_line_graph(hypergraph2, weights="absolute") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {"e1", "e2", "e3"} + assert [set(e) for e in L.edges] == [{"e1", "e2"}, {"e1", "e3"}, {"e2", "e3"}] + assert L.edges[("e1","e2")]['weight'] == 1 + assert L.edges[("e2","e3")]['weight'] == 2 + assert L.edges[("e1","e3")]['weight'] == 2 + assert sum([dat["weight"] for _,_,dat in L.edges(data=True)]) == 5 + + L = xgi.to_line_graph(hypergraph2, s=2, weights="absolute") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {"e1", "e2", "e3"} + assert [set(e) for e in L.edges] == [{"e1", "e3"}, {"e2", "e3"}] + assert L.edges[("e2","e3")]['weight'] == 2 + assert L.edges[("e1","e3")]['weight'] == 2 + assert sum([dat["weight"] for _,_,dat in L.edges(data=True)]) == 4 + + +def test_normed_weighted_line_graph(edgelist1, hypergraph1, edgelist2, hypergraph2): + H = xgi.Hypergraph(edgelist1) + L = xgi.to_line_graph(H) + + L = xgi.to_line_graph(H, weights="normalized") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {0, 1, 2, 3} + assert [set(e) for e in L.edges] == [{2, 3}] + assert L.edges[(2,3)]['weight'] == 0.5 + + L = xgi.to_line_graph(hypergraph1, weights="normalized") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {"e1", "e2", "e3"} + assert [set(e) for e in L.edges] == [{"e1", "e2"}, {"e2", "e3"}] + assert L.edges[("e1","e2")]['weight'] == 1.0 + assert L.edges[("e2","e3")]['weight'] == 1.0 + assert sum([dat["weight"] for _,_,dat in L.edges(data=True)]) == 2.0 + + L = xgi.to_line_graph(hypergraph2, weights="normalized") + assert isinstance(L, Graph) + assert all(["weight" in dat for u,v,dat in L.edges(data=True)]) + assert set(L.nodes) == {"e1", "e2", "e3"} + assert [set(e) for e in L.edges] == [{"e1", "e2"}, {"e1", "e3"}, {"e2", "e3"}] + assert L.edges[("e1","e2")]['weight'] == 0.5 + assert L.edges[("e2","e3")]['weight'] == 1.0 + assert L.edges[("e1","e3")]['weight'] == 1.0 + assert sum([dat["weight"] for _,_,dat in L.edges(data=True)]) == 2.5 diff --git a/xgi/convert/line_graph.py b/xgi/convert/line_graph.py index cf492bb7b..5834a3eb1 100644 --- a/xgi/convert/line_graph.py +++ b/xgi/convert/line_graph.py @@ -1,16 +1,22 @@ from itertools import combinations +from ..exception import XGIError + import networkx as nx __all__ = ["to_line_graph"] -def to_line_graph(H, s=1): +def to_line_graph(H, s=1, weights=None): """The s-line graph of the hypergraph. - The line graph of the hypergraph `H` is the graph whose + 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 one vertex. + 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 ---------- @@ -19,6 +25,12 @@ def to_line_graph(H, s=1): 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 ------- @@ -32,6 +44,9 @@ def to_line_graph(H, s=1): 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'.") LG = nx.Graph() edge_label_dict = {tuple(edge): index for index, edge in H._edge.items()} @@ -39,7 +54,21 @@ def to_line_graph(H, s=1): LG.add_nodes_from(H.edges) for edge1, edge2 in combinations(H.edges.members(), 2): - if len(edge1.intersection(edge2)) >= s: - LG.add_edge(edge_label_dict[tuple(edge1)], edge_label_dict[tuple(edge2)]) + # Check that the intersection size is larger than s + intersection_size = len(edge1.intersection(edge2)) + if intersection_size >= s: + if not weights: + # Add unweighted edge + LG.add_edge(edge_label_dict[tuple(edge1)], + edge_label_dict[tuple(edge2)]) + else: + # Compute the (normalized) weight + weight = intersection_size + if weights == "normalized": + weight /= min([len(edge1), len(edge2)]) + # Add edge with weight + LG.add_edge(edge_label_dict[tuple(edge1)], + edge_label_dict[tuple(edge2)], + weight=weight) return LG