Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c620364
feat: Add support for HIF format
AleksanderWWW Mar 19, 2025
75208fd
add function to parse input for hg dimensions
AleksanderWWW Mar 19, 2025
7694c4a
finalize PoC for hg loading
AleksanderWWW Mar 20, 2025
ef22632
add function for saving hg in the hiv format
AleksanderWWW Mar 20, 2025
94b02b0
add test
AleksanderWWW Mar 20, 2025
b491ee1
move to a separate file
AleksanderWWW Mar 20, 2025
cf48d52
even out the diff
AleksanderWWW Mar 20, 2025
fa7f526
code review 1
AleksanderWWW Mar 20, 2025
e71787c
code review 2
AleksanderWWW Mar 20, 2025
f069829
restructure + better metadata handling in load
AleksanderWWW Mar 23, 2025
8fa099d
improve saving
AleksanderWWW Mar 24, 2025
94ce937
improve loading
AleksanderWWW Mar 31, 2025
c4c8be2
update
AleksanderWWW Apr 6, 2025
c29e9ad
Merge branch 'master' into aw/hif-import-export
AleksanderWWW Apr 6, 2025
ae33a1e
update
AleksanderWWW Apr 6, 2025
f716a3f
update
AleksanderWWW Apr 6, 2025
d7908dd
update
AleksanderWWW Apr 6, 2025
5b470ff
start adding tests
AleksanderWWW Apr 9, 2025
c13fe0e
start refactor
AleksanderWWW May 11, 2025
d3bb227
update
AleksanderWWW May 19, 2025
d1547d0
fix for no incidences and edges as string
AleksanderWWW May 31, 2025
60dadb0
add saving and tests
AleksanderWWW Jun 2, 2025
64f51b3
fix last corner case
AleksanderWWW Jun 3, 2025
64eb4bd
update
AleksanderWWW Jun 5, 2025
f7e7b9b
Merge remote-tracking branch 'origin/master' into hif-format-support
pszufe Dec 13, 2025
2b5f344
HIF standard support, replace JSON3 with JSON.jl
pszufe Dec 28, 2025
6a59851
upd Project.toml
pszufe Dec 28, 2025
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
/docs/site/
/.vscode

.ipynb_checkpoints
.ipynb_checkpoints/*
10 changes: 6 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
name = "SimpleHypergraphs"
uuid = "aa4a32ff-dd5d-5357-90e3-e7a9512f0501"
version = "0.3.3"
authors = ["Przemysław Szufel <pszufe@sgh.waw.pl>", "Bogumił Kamiński <bkamins@sgh.waw.pl>", "Carmine Spagnuolo <spagnuolocarmine@gmail.com>", "Alessia Antelmi <aless.antelmi@gmail.com>", "Evan Walter Clark Spotte-Smith <espottesmith@gmail.com>"]
version = "0.3.2"

[deps]
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee"
Expand All @@ -19,9 +20,10 @@ StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"

[compat]
Conda = "^1.5.0"
DataStructures = "^0.18.11"
DataFrames = "^1.7.0"
DataStructures = "^0.18.11, ^0.19.1"
Graphs = "^1.4.1"
JSON3 = "^1.0.1"
JSON = "^1.0.0"
PyCall = "^1.91.2"
PyPlot = "^2.8.2"
SimpleTraits = "^0.9.4"
Expand Down
3 changes: 1 addition & 2 deletions REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ LightGraphs 1.2.0
StatsBase 0.32
DataStructures 0.17.7
Conda 1.3.0
JSON 0.21.0
JSON3 0.1.13
JSON 1.0.0
PyCall 1.91.2
PyPlot 2.8.2
StructTypes 1.0.1
8 changes: 6 additions & 2 deletions src/SimpleHypergraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
using Graphs
using StatsBase
using DataStructures
using DataFrames
using PyCall
using Conda
using PyPlot
using JSON3
using JSON
using Random
using LinearAlgebra
using SparseArrays
Expand All @@ -27,7 +28,7 @@
export dual
export random_model, random_kuniform_model, random_dregular_model, random_preferential_model

export Abstract_HG_format, HGF_Format, JSON_Format
export Abstract_HG_format, HGF_Format, JSON_Format, HIF_Format
export hg_load, hg_save

export modularity
Expand Down Expand Up @@ -65,7 +66,7 @@
catch e; end
has_plotting[] = has_networkx && has_hypernetx
if !has_plotting[]
@warn "The plotting functionality of HyperNetX will not work!\n"*

Check warning on line 69 in src/SimpleHypergraphs.jl

View workflow job for this annotation

GitHub Actions / Documentation

The plotting functionality of HyperNetX will not work! Conda Python networkx not found. Conda Python HyperNetX not found. To test your installation try running `using PyCall;pyimport("networkx");pyimport("hypernetx")`
(has_networkx ? "" : "Conda Python networkx not found.\n")*
(has_hypernetx ? "" : "Conda Python HyperNetX not found.\n")*
"To test your installation try running `using PyCall;pyimport(\"networkx\");pyimport(\"hypernetx\")`"
Expand All @@ -81,6 +82,9 @@
include("hypergraph.jl")
include("io.jl")

# support for HIF standard
include("io_hif.jl")

include("models/bipartite.jl")
include("models/twosection.jl")
include("models/random-models.jl")
Expand Down
141 changes: 93 additions & 48 deletions src/io.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
# TODO: maybe more fancy file format and correctness checking should be done

abstract type Abstract_HG_format end
"""
HGF_Format
Simple text serialization format.
This format only stores the incidence structure of the hypergraph and ignores any metadata.
"""
struct HGF_Format <: Abstract_HG_format end

"""
JSON_Format

Implementation of the `JSON` format for hypergraph input/output.
Note that more advanced features are supported in `HIF_Format`.
"""
struct JSON_Format <: Abstract_HG_format end

"""
HIF_Format

Implementation of the `HIF` (Hypergraph Interchange Format) format for hypergraph input/output.
See https://github.com/pszufe/HIF-standard for more details about the format.
See also the paper https://doi.org/10.1017/nws.2025.10018
"""
struct HIF_Format <: Abstract_HG_format end
# note that the HIF format support is implemented in a separate file io_hif.jl

"""
elemtypes(v::AbstractVector)

Returns the union of the element types of the elements of vector `v`.
"""
elemtypes(v::AbstractVector) = foldl((T, x) -> Union{T, typeof(x)}, v; init=Union{})

"""``
hg_save(io::IO, h::H, format::HGF_Format) where {H <: AbstractSimpleHypergraph}
"""
hg_save(io::IO, h::H, format::HGF_Format; pretty::Bool=false) where {H <: AbstractSimpleHypergraph}

Saves an undirected hypergraph `h` to an output stream `io` in `hgf` format.
This format only stores the incidence structure of the hypergraph and ignores any metadata.

TODO: what to do about metadata?
TODO: pretty option currently ignored

"""
function hg_save(io::IO, h::H, format::HGF_Format) where {H <: AbstractSimpleHypergraph}
function hg_save(io::IO, h::H, ::HGF_Format; pretty::Bool=false) where {H <: AbstractSimpleHypergraph}
println(io, length(h.v2he), " ", length(h.he2v))
for he in h.he2v
skeys = sort(collect(keys(he)))
Expand All @@ -23,16 +51,13 @@ end


"""
hg_save(io::IO, h::Hypergraph, format::JSON_Format)
hg_save(io::IO, h::Hypergraph, ::JSON_Format; pretty::Bool=false)

Saves an undirected hypergraph `h` to an output stream `io` in `json` format.

If `h` has `Composite Types` either for vertex metadata or hyperedges metadata,
the user has to explicit tell the JSON3 package about it, for instance using:

`JSON3.StructType(::Type{MyType}) = JSON3.Struct()`.
...TODO: complete this part

See the (JSON3.jl documentation)[https://github.com/quinnj/JSON3.jl] for more details.

The `json` in output contains the following information (keys):

Expand All @@ -44,36 +69,42 @@ The `json` in output contains the following information (keys):
* `he_meta` : hyperedges metadata

"""
function hg_save(io::IO, h::Hypergraph, format::JSON_Format)
function hg_save(io::IO, h::Hypergraph, ::JSON_Format; pretty::Bool=false)
json_hg = Dict{Symbol, Any}()

json_hg[:n] = nhv(h)
json_hg[:k] = nhe(h)

json_hg[:m] = JSON3.write(Matrix(h))
json_hg[:v2he] = JSON3.write(h.v2he)
#vec was addded when upgrading to JSON.jl from JSON3.jl
#to ensure proper serialization - JSON3 serialized to vec
json_hg[:m] = vec(Matrix(h))

json_hg[:v_meta] = JSON3.write(h.v_meta)
json_hg[:he_meta] = JSON3.write(h.he_meta)
json_hg[:v2he] = h.v2he

JSON3.write(io, json_hg)
json_hg[:v_meta] = h.v_meta
json_hg[:he_meta] = h.he_meta
JSON.json(io, json_hg; pretty)
end


"""
hg_save(
fname::AbstractString, h::AbstractHypergraph;
format::Abstract_HG_format=HGF_Format()
format::Abstract_HG_format=HGF_Format(), pretty::Bool=false
)

Saves a hypergraph `h` to a file `fname` in the specified `format`.
The default saving format is `hgf`.

"""
hg_save(
function hg_save(
fname::AbstractString, h::AbstractHypergraph;
format::Abstract_HG_format=HGF_Format()
) = open(io -> hg_save(io, h, format), fname, "w")
format::Abstract_HG_format = HGF_Format(), pretty::Bool=false
)
open(io -> hg_save(io, h, format; pretty=pretty), fname, "w")
end





"""
Expand All @@ -97,10 +128,12 @@ Skips a single initial comment.
"""
function hg_load(
io::IO,
format::HGF_Format;
::HGF_Format;
HType::Type{H} = Hypergraph,
T::Type{U} = Bool,
D::Type{<:AbstractDict{Int, U}} = Dict{Int, T},
V = Nothing,
E = Nothing
) where {U <: Real, H <: AbstractSimpleHypergraph}
line = readline(io)

Expand All @@ -124,7 +157,7 @@ function hg_load(
l = split(line)
length(l) == 2 || throw(ArgumentError("expected two integers"))
n, k = parse.(Int, l)
h = HType{T, D}(n, k)
h = HType{T, V, E, D}(n, k)

for i in 1:k
lastv = 0
Expand Down Expand Up @@ -167,34 +200,37 @@ Loads a hypergraph from a stream `io` from `json` format.
* `V` : type of values stored in the vertices of the hypergraph
* `E` : type of values stored in the edges of the hypergraph



"""
function hg_load(
io::IO,
format::JSON_Format;
::JSON_Format;
HType::Type{H} = Hypergraph,
T::Type{U} = Bool,
D::Type{<:AbstractDict{Int, U}} = Dict{Int, T},
V = Nothing,
E = Nothing
) where {H <: AbstractSimpleHypergraph, U <: Real}
json_hg = JSON3.read(readline(io))

m = reshape(JSON3.read(json_hg.m, Array{Union{T, Nothing}}), json_hg.n, json_hg.k)

if V != Nothing && E != Nothing && hasvertexmeta(HType) && hashyperedgemeta(HType)
v_meta = JSON3.read(json_hg.v_meta, Array{Union{V, Nothing}})
he_meta = JSON3.read(json_hg.he_meta, Array{Union{E, Nothing}})
h = HType{T, V, E, D}(m; v_meta=v_meta, he_meta=he_meta)
elseif V != Nothing && hasvertexmeta(HType)
v_meta = JSON3.read(json_hg.v_meta, Array{Union{V, Nothing}})
h = HType{T, V, D}(m; v_meta=v_meta)
elseif E != Nothing && hashyperedgemeta(HType)
he_meta = JSON3.read(json_hg.he_meta, Array{Union{E, Nothing}})
h = HType{T, E, D}(m; he_meta=he_meta)
json_hg = JSON.parse(read(io, String))
m = reshape(Vector{Union{T, Nothing}}(json_hg.m), json_hg.n, json_hg.k)

V2 = (V == :auto) ? ("v_meta" ∈ keys(json_hg) && length(json_hg.v_meta) > 0 ? elemtypes(json_hg.v_meta) : Nothing) : V
E2 = (E == :auto) ? ("he_meta" ∈ keys(json_hg) && length(json_hg.he_meta) > 0 ? elemtypes(json_hg.he_meta) : Nothing) : E

if V2 != Nothing && E2 != Nothing && hasvertexmeta(HType) && hashyperedgemeta(HType)
v_meta = Vector{Union{V2, Nothing}}(json_hg.v_meta)
he_meta = Vector{Union{E2, Nothing}}(json_hg.he_meta)
h = HType{T, V2, E2, D}(m; v_meta, he_meta)
elseif V2 != Nothing && hasvertexmeta(HType)
v_meta = Vector{Union{V2, Nothing}}(json_hg.v_meta)
h = HType{T, V2, D}(m; v_meta=v_meta)
elseif E2 != Nothing && hashyperedgemeta(HType)
he_meta = Vector{Union{E2, Nothing}}(json_hg.he_meta)
h = HType{T, E2, D}(m; he_meta=he_meta)
else
h = HType{T, D}(m)
h = HType{T, V2, E2, D}(m)
end

h
end

Expand All @@ -217,28 +253,37 @@ The default saving format is `hgf`.

* `HType`: type of hypergraph to store data in
* `T` : type of weight values stored in the hypergraph's adjacency matrix
* `D` : dictionary for storing values the default is `Dict{Int, T}`
* `V` : type of values stored in the vertices of the hypergraph
* `E` : type of values stored in the edges of the hypergraph

* `D` : dictionary for storing values the default is `Dict{Int, T}`
* `show_warning` : whether to show warnings during loading
* `sort_by_id` : whether to sort vertices and hyperedges by their original ids (only supported for HIF_Format)
* `add_original_id_to_meta` : if a `Symbol` is provided, the original ids are added to the vertex and hyperedge metadata under that key (only supported for HIF_Format)
"""
function hg_load(
fname::AbstractString;
format::Abstract_HG_format = HGF_Format(),
HType::Type{H} = Hypergraph,
T::Type{U} = Bool,
V = :auto,
E = :auto,
D::Type{<:AbstractDict{Int, U}} = Dict{Int, T},
V = Nothing,
E = Nothing
show_warning::Bool=true,
sort_by_id::Bool=false,
add_original_id_to_meta::Union{Symbol, Nothing}=nothing
) where {U <: Real, H <: AbstractSimpleHypergraph}

if format == HGF_Format()
@assert format isa HGF_Format || !sort_by_id "sort_by_id only supported for HIF_Format"
if format isa HGF_Format
if HType == Hypergraph
open(io -> hg_load(io, format; HType=HType, T=T, D=D), fname, "r")
open(io -> hg_load(io, format; HType, T, D), fname, "r")
else
error("HGF loading only implemented for Hypergraph")
end
else
open(io -> hg_load(io, format; HType=HType, T=T, D=D, V=V, E=E), fname, "r")
if format isa HIF_Format
open(io -> hg_load(io, format; HType, T, D, V, E, show_warning, sort_by_id, add_original_id_to_meta), fname, "r")
else
open(io -> hg_load(io, format; HType, T, D, V, E), fname, "r")
end
end
end
Loading
Loading