Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HIF functionality (new) #572

Merged
merged 22 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading