Skip to content

Commit

Permalink
Added weights option to to_line_graph function. (#427)
Browse files Browse the repository at this point in the history
* Added weights option to to_line_graph function.

* Added tests for weighted line graph implementation.

* Added s=2 test.
  • Loading branch information
tlarock authored Aug 2, 2023
1 parent ea1aaf6 commit 0b19dc1
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 5 deletions.
80 changes: 80 additions & 0 deletions tests/convert/test_line_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 34 additions & 5 deletions xgi/convert/line_graph.py
Original file line number Diff line number Diff line change
@@ -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
----------
Expand All @@ -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
-------
Expand All @@ -32,14 +44,31 @@ 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()}

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

0 comments on commit 0b19dc1

Please sign in to comment.