Skip to content

Commit

Permalink
HIF functionality (new) (#572)
Browse files Browse the repository at this point in the history
* add hif module

* add bipartite edgelist functionality

* add dictionary functionality

* fix bipartite edge logic

* Add unit tests for add

* added empty method for dihypergraph

* simplify hif module

* add tests

* fix test error

* added unit tests

* update hif

* fix error

* updates

* add unit tests

* update unit tests

* Update hif.py

* added documentation

* added unit tests

* add new refs

* add functions to docs

* rename

* fix module name
  • Loading branch information
nwlandry authored Aug 24, 2024
1 parent 7646f9d commit 296056c
Show file tree
Hide file tree
Showing 13 changed files with 601 additions and 12 deletions.
2 changes: 2 additions & 0 deletions docs/source/api/core/xgi.core.dihypergraph.DiHypergraph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
~DiHypergraph.add_node
~DiHypergraph.add_edge
~DiHypergraph.add_nodes_from
~DiHypergraph.add_node_to_edge
~DiHypergraph.add_edges_from
~DiHypergraph.remove_node
~DiHypergraph.remove_edge
~DiHypergraph.remove_nodes_from
~DiHypergraph.remove_node_from_edge
~DiHypergraph.remove_edges_from
~DiHypergraph.clear
~DiHypergraph.copy
Expand Down
1 change: 1 addition & 0 deletions docs/source/api/readwrite.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ readwrite package
~xgi.readwrite.bigg_data
~xgi.readwrite.bipartite
~xgi.readwrite.edgelist
~xgi.readwrite.hif
~xgi.readwrite.incidence
~xgi.readwrite.json
~xgi.readwrite.xgi_data
11 changes: 11 additions & 0 deletions docs/source/api/readwrite/xgi.readwrite.hif.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
xgi.readwrite.hif
=================

.. currentmodule:: xgi.readwrite.hif

.. automodule:: xgi.readwrite.hif

.. rubric:: Functions

.. autofunction:: read_hif
.. autofunction:: write_hif
8 changes: 8 additions & 0 deletions docs/source/using-xgi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ Preprints
2024
----

Jordan Barrett, Paweł Prałat, Aaron Smith, François Théberge, "Counting simplicial pairs in hypergraphs", arXiv:2408.11806 (2024).

:bdg-link-primary-line:`Paper <https://arxiv.org/abs/2408.11806>`

Gonzalo Contreras-Aso, Regino Criado, and Miguel Romance, "Beyond directed hypergraphs: heterogeneous hypergraphs and spectral centralities", arXiv:2403.11825 (2024).

:bdg-link-primary-line:`Paper <https://arxiv.org/abs/2403.11825>`
Expand All @@ -83,6 +87,10 @@ Maxime Lucas, Luca Gallo, Arsham Ghavasieh, Federico Battiston, and Manlio De Do
:bdg-link-primary-line:`Paper <https://arxiv.org/abs/2404.08547>`
:bdg-link-primary-line:`Code <https://github.com/maximelucas/hypergraph_reducibility>`

Corbit R. Sampson, Juan G. Restrepo, "Competing Social Contagions with Opinion Dependent Infectivity", arXiv:2408.10373 (2024).

:bdg-link-primary-line:`Paper <https://arxiv.org/abs/2408.10373>`

2023
----

Expand Down
63 changes: 63 additions & 0 deletions tests/convert/test_bipartite_edges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import pytest

import xgi
from xgi.exception import XGIError


def test_to_bipartite_edges(edgelist2, diedgelist1):
H = xgi.Hypergraph(edgelist2)
el = xgi.to_bipartite_edgelist(H)
edgelist = [(1, 0), (2, 0), (3, 1), (4, 1), (4, 2), (5, 2), (6, 2)]
assert sorted(el) == edgelist

H = xgi.DiHypergraph(diedgelist1)
el = xgi.to_bipartite_edgelist(H)
edgelist = [
(1, 0, "in"),
(2, 0, "in"),
(3, 0, "in"),
(4, 0, "out"),
(5, 1, "in"),
(6, 1, "in"),
(6, 1, "out"),
(7, 1, "out"),
(8, 1, "out"),
]
assert sorted(el) == edgelist


def test_from_bipartite_edges(edgelist2, diedgelist1):
# undirected
edgelist = [(1, 0), (2, 0), (3, 1), (4, 1), (4, 2), (5, 2), (6, 2)]
H1 = xgi.from_bipartite_edgelist(edgelist)

H2 = xgi.Hypergraph(edgelist2)
assert H1.edges.members(dtype=dict) == H2.edges.members(dtype=dict)

# directed
edgelist = [
(1, 0, "in"),
(2, 0, "in"),
(3, 0, "in"),
(4, 0, "out"),
(5, 1, "in"),
(6, 1, "in"),
(6, 1, "out"),
(7, 1, "out"),
(8, 1, "out"),
]

H1 = xgi.from_bipartite_edgelist(edgelist)

H2 = xgi.DiHypergraph(diedgelist1)
assert H1.edges.dimembers(dtype=dict) == H2.edges.dimembers(dtype=dict)

# test error
edgelist = [
(1, 0, "in", "test"),
(2, 0, "in"),
(3, 0, "in"),
(4, 0, "out"),
]
with pytest.raises(XGIError):
H = xgi.from_bipartite_edgelist(edgelist)
259 changes: 259 additions & 0 deletions tests/readwrite/test_hif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import json
import tempfile

import pytest

import xgi


def test_to_hif(
edgelist1,
hyperwithdupsandattrs,
simplicialcomplex1,
diedgedict1,
dihyperwithattrs,
):
H = xgi.Hypergraph(edgelist1)
_, filename = tempfile.mkstemp()
xgi.write_hif(H, filename)
with open(filename) as file:
jsondata = json.loads(file.read())

assert "nodes" not in jsondata
assert "edges" not in jsondata
assert "incidences" in jsondata
assert "network-type" in jsondata
assert jsondata["network-type"] == "undirected"

incidences = [
{"edge": 0, "node": 1},
{"edge": 0, "node": 2},
{"edge": 0, "node": 3},
{"edge": 1, "node": 4},
{"edge": 2, "node": 5},
{"edge": 2, "node": 6},
{"edge": 3, "node": 6},
{"edge": 3, "node": 7},
{"edge": 3, "node": 8},
]
assert (
sorted(jsondata["incidences"], key=lambda x: (x["edge"], x["node"]))
== incidences
)

# hypergraph with attributes
_, filename = tempfile.mkstemp()
hyperwithdupsandattrs["name"] = "test"
xgi.write_hif(hyperwithdupsandattrs, filename)

with open(filename) as file:
jsondata = json.loads(file.read())

assert "nodes" in jsondata
assert "edges" in jsondata
assert "incidences" in jsondata
assert "network-type" in jsondata
assert jsondata["network-type"] == "undirected"
assert "metadata" in jsondata
assert jsondata["metadata"] == {"name": "test"}

nodes = [
{"node": 1, "attr": {"color": "red", "name": "horse"}},
{"node": 2, "attr": {"color": "blue", "name": "pony"}},
{"node": 3, "attr": {"color": "yellow", "name": "zebra"}},
{"node": 4, "attr": {"color": "red", "name": "orangutan", "age": 20}},
{"node": 5, "attr": {"color": "blue", "name": "fish", "age": 2}},
]

edges = [
{"edge": 0, "attr": {"color": "blue"}},
{"edge": 1, "attr": {"color": "red", "weight": 2}},
{"edge": 2, "attr": {"color": "yellow"}},
{"edge": 3, "attr": {"color": "purple"}},
{"edge": 4, "attr": {"color": "purple", "name": "test"}},
]

incidences = [
{"edge": 0, "node": 1},
{"edge": 0, "node": 2},
{"edge": 1, "node": 1},
{"edge": 1, "node": 2},
{"edge": 2, "node": 1},
{"edge": 2, "node": 2},
{"edge": 3, "node": 3},
{"edge": 3, "node": 4},
{"edge": 3, "node": 5},
{"edge": 4, "node": 3},
{"edge": 4, "node": 4},
{"edge": 4, "node": 5},
]

assert sorted(jsondata["nodes"], key=lambda x: x["node"]) == nodes
assert jsondata["edges"] == edges
assert (
sorted(jsondata["incidences"], key=lambda x: (x["edge"], x["node"]))
== incidences
)

# Simplicial complexes
_, filename = tempfile.mkstemp()
xgi.write_hif(simplicialcomplex1, filename)

with open(filename) as file:
jsondata = json.loads(file.read())

assert "nodes" not in jsondata
assert "edges" not in jsondata
assert "incidences" in jsondata
assert "network-type" in jsondata
assert jsondata["network-type"] == "asc"

incidences = [
{"edge": "e1", "node": 0},
{"edge": "e1", "node": "b"},
{"edge": "e2", "node": 0},
{"edge": "e2", "node": "c"},
{"edge": "e3", "node": 0},
{"edge": "e3", "node": "b"},
{"edge": "e3", "node": "c"},
{"edge": "e4", "node": "b"},
{"edge": "e4", "node": "c"},
]

def _mixed(ele):
return (0, int(ele)) if isinstance(ele, int) else (1, ele)

sorted_incidences = sorted(
jsondata["incidences"], key=lambda x: (_mixed(x["edge"]), _mixed(x["node"]))
)
assert sorted_incidences == incidences

# dihypergraphs without attributes
H = xgi.DiHypergraph(diedgedict1)

_, filename = tempfile.mkstemp()
xgi.write_hif(H, filename)

with open(filename) as file:
jsondata = json.loads(file.read())

assert "nodes" not in jsondata
assert "edges" not in jsondata
assert "incidences" in jsondata

# dihypergraphs with attributes
_, filename = tempfile.mkstemp()
xgi.write_hif(dihyperwithattrs, filename)

with open(filename) as file:
jsondata = json.loads(file.read())

assert "nodes" in jsondata
assert "edges" in jsondata
assert "incidences" in jsondata


def test_from_hif(
hyperwithdupsandattrs,
simplicialcomplex1,
dihyperwithattrs,
):
_, filename1 = tempfile.mkstemp()
xgi.write_hif(hyperwithdupsandattrs, filename1)

# test basic import
H = xgi.read_hif(filename1)

assert isinstance(H, xgi.Hypergraph)
assert (H.num_nodes, H.num_edges) == (5, 5)
assert set(H.nodes) == {1, 2, 3, 4, 5}
assert H.nodes[1] == {"color": "red", "name": "horse"}
assert H.nodes[2] == {"color": "blue", "name": "pony"}
assert H.nodes[3] == {"color": "yellow", "name": "zebra"}
assert H.nodes[4] == {"color": "red", "name": "orangutan", "age": 20}
assert H.nodes[5] == {"color": "blue", "name": "fish", "age": 2}

assert set(H.edges) == {0, 1, 2, 3, 4}
assert H.edges[0] == {"color": "blue"}
assert H.edges[1] == {"color": "red", "weight": 2}
assert H.edges[2] == {"color": "yellow"}
assert H.edges[3] == {"color": "purple"}
assert H.edges[4] == {"color": "purple", "name": "test"}

edgedict = {0: {1, 2}, 1: {1, 2}, 2: {1, 2}, 3: {3, 4, 5}, 4: {3, 4, 5}}
assert H.edges.members(dtype=dict) == edgedict

# cast nodes and edges
H = xgi.read_hif(filename1, nodetype=str, edgetype=float)
assert set(H.nodes) == {"1", "2", "3", "4", "5"}
assert set(H.edges) == {0.0, 1.0, 2.0, 3.0, 4.0}

assert H.nodes["1"] == {"color": "red", "name": "horse"}
assert H.edges[0.0] == {"color": "blue"}

edgedict = {
0: {"1", "2"},
1: {"1", "2"},
2: {"1", "2"},
3: {"3", "4", "5"},
4: {"3", "4", "5"},
}
assert H.edges.members(dtype=dict) == edgedict

_, filename2 = tempfile.mkstemp()
xgi.write_hif(simplicialcomplex1, filename2)

S = xgi.read_hif(filename2)
assert isinstance(S, xgi.SimplicialComplex)
assert (S.num_nodes, S.num_edges) == (3, 4)

assert set(S.nodes) == {0, "b", "c"}
assert set(S.edges) == {"e1", "e2", "e3", "e4"}

# dihypergraphs
_, filename3 = tempfile.mkstemp()
xgi.write_hif(dihyperwithattrs, filename3)

DH = xgi.read_hif(filename3)
assert (DH.num_nodes, DH.num_edges) == (6, 3)
assert isinstance(DH, xgi.DiHypergraph)
assert set(DH.nodes) == {0, 1, 2, 3, 4, 5}
assert set(DH.edges) == {0, 1, 2}

edgedict = {0: ({0, 1}, {2}), 1: ({1, 2}, {4}), 2: ({2, 3, 4}, {4, 5})}
assert DH.edges.dimembers(dtype=dict) == edgedict

# test error checking
with pytest.raises(TypeError):
S = xgi.read_hif(filename2, edgetype=int)

# metadata
_, filename4 = tempfile.mkstemp()
hyperwithdupsandattrs["name"] = "test"
xgi.write_hif(hyperwithdupsandattrs, filename4)
H = xgi.read_hif(filename4)

assert H["name"] == "test"

# test isolates and empty edges
H = xgi.Hypergraph()
H.add_nodes_from(range(5))
H.add_edges_from([[1, 2, 3], []])

_, filename5 = tempfile.mkstemp()
xgi.write_hif(H, filename5)

H = xgi.read_hif(filename5)
assert H.edges.size.aslist() == [3, 0]
assert set(H.nodes.isolates()) == {0, 4}

H = xgi.DiHypergraph()
H.add_nodes_from(range(5))
H.add_edges_from([([1, 2, 3], [2, 4]), [[], []]])

_, filename6 = tempfile.mkstemp()
xgi.write_hif(H, filename6)

H = xgi.read_hif(filename6)
# assert H.edges.size.aslist() == [5, 0]
# assert set(H.nodes.isolates()) == {0}
Loading

0 comments on commit 296056c

Please sign in to comment.