From b2643ee1899003ac6d6b44a3ec58027e10d0e5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sat, 5 Jul 2025 18:03:21 +0200 Subject: [PATCH 1/7] Add directed methods --- docs/src/interfaces.md | 27 ++++++++++++----- src/Interfaces/Network.jl | 63 +++++++++++++++++++++++++++++++++++++++ src/Networks.jl | 3 ++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/docs/src/interfaces.md b/docs/src/interfaces.md index 66a4582..043215e 100644 --- a/docs/src/interfaces.md +++ b/docs/src/interfaces.md @@ -13,14 +13,25 @@ Using [`DelegatorTraits.jl`](https://github.com/bsc-quantic/DelegatorTraits.jl) The `Network` interface abstracts a network or graph as a bipartite graph whose sets are the vertices and the edges. A type implementing the `Network` interface must implement the following methods: -| Required method | Description | -| :----------------------- | :------------------------------------------ | -| `all_vertices(g)` | Returns the list of vertices | -| `all_edges(g)` | Returns the list of edges | -| `edge_incidents(g, e)` | Returns the vertices connected by edge `e` | -| `vertex_incidents(g, v)` | Returns the edges conected to vertex `v` | -| `vertex_neighbors(g, v)` | Returns the vertices neighboring vertex `v` | -| `edge_neighbors(g, e)` | Returns the edges neighboring edge `e` | +| Required method | Description | +| :------------------------ | :----------------------------------------------- | +| `all_vertices(g)` | Returns the list of vertices | +| `all_edges(g)` | Returns the list of edges | +| `edge_incidents(g, e)` | Returns the vertices connected by edge `e` | +| `vertex_incidents(g, v)` | Returns the edges conected to vertex `v` | +| `Directedness(::Type{G})` | Returns the directedness trait of graph type `G` | + +### Directed methods + +| Required method | Description | +| :----------------- | :-------------------------------------------- | +| `edges_in(g, v)` | Returns the edges incoming to vertex `v` | +| `edges_out(g, v)` | Returns the vertices outgoing from vertex `v` | +| `vertex_src(g, e)` | Returns the source vertex of edge `e` | +| `vertex_dst(g, e)` | Returns the destination vertex of edge `e` | + +!!! todo + What about hypergraphs? In such case, the source and destination of an edge can be multiple and thus, we should have a `vertices_src` and `vertices_dst` functions. ### Optional methods diff --git a/src/Interfaces/Network.jl b/src/Interfaces/Network.jl index 2d0ecb1..d18a79b 100644 --- a/src/Interfaces/Network.jl +++ b/src/Interfaces/Network.jl @@ -33,6 +33,20 @@ EdgePersistence(graph) = EdgePersistence(graph, DelegatorTrait(Network(), graph) EdgePersistence(graph, ::DelegateToField) = EdgePersistence(delegator(Network(), graph)) EdgePersistence(graph, ::DontDelegate) = PruneEdges() +""" + Directedness + +Trait for the directedness of a [`Network`](@ref). It defines whether the network is `Directed` or `Undirected`. +""" +abstract type Directedness end +struct Directed <: Directedness end +struct Undirected <: Directedness end +# struct Hybrid <: Directedness end + +Directedness(graph) = Directedness(graph, DelegatorTrait(Network(), graph)) +Directedness(graph, ::DelegateToField) = Directedness(delegator(Network(), graph)) +Directedness(graph, ::DontDelegate) = throw(MethodError(Directedness, (graph,))) + # query methods function vertices end function edges end @@ -133,6 +147,35 @@ function edges_set_hyper end function vertex_at end function edge_at end +# directed methods +""" + edges_in(graph, v) + +Returns the edges incoming to vertex `v` in `graph`. +""" +function edges_in end + +""" + edges_out(graph, v) + +Returns the edges outgoing from vertex `v` in `graph`. +""" +function edges_out end + +""" + vertex_src(graph, g) + +Returns the source vertex of edge `e` in `graph`. +""" +function vertex_src end + +""" + vertex_dst(graph, e) + +Returns the destination vertex of edge `e` in `graph`. +""" +function vertex_dst end + # mutating methods """ addvertex!(graph, v) @@ -363,6 +406,26 @@ function edges_set_hyper(graph, ::DontDelegate) return stranded_edges end +## `edges_in` +edges_in(graph, v) = edges_in(graph, v, DelegatorTrait(Network(), graph)) +edges_in(graph, v, ::DelegateToField) = edges_in(delegator(Network(), graph), v) +edges_in(graph, v, ::DontDelegate) = throw(MethodError(edges_in, (graph, v))) + +## `edges_out` +edges_out(graph, v) = edges_out(graph, v, DelegatorTrait(Network(), graph)) +edges_out(graph, v, ::DelegateToField) = edges_out(delegator(Network(), graph), v) +edges_out(graph, v, ::DontDelegate) = throw(MethodError(edges_out, (graph, v))) + +## `vertex_src` +vertex_src(graph, e) = vertex_src(graph, e, DelegatorTrait(Network(), graph)) +vertex_src(graph, e, ::DelegateToField) = vertex_src(delegator(Network(), graph), e) +vertex_src(graph, e, ::DontDelegate) = throw(MethodError(vertex_src, (graph, e))) + +## `vertex_dst` +vertex_dst(graph, e) = vertex_dst(graph, e, DelegatorTrait(Network(), graph)) +vertex_dst(graph, e, ::DelegateToField) = vertex_dst(delegator(Network(), graph), e) +vertex_dst(graph, e, ::DontDelegate) = throw(MethodError(vertex_dst, (graph, e))) + ## `addvertex!` # TODO check if vertex already exists # hasvertex(graph, e.vertex) && throw(ArgumentError("Vertex $(e.vertex) already exists in network")) diff --git a/src/Networks.jl b/src/Networks.jl index 3619d88..698a52c 100644 --- a/src/Networks.jl +++ b/src/Networks.jl @@ -13,6 +13,9 @@ export edges, all_edges, edge_at, vertex_incidents, edge_type, hasedge, nedges, export edges_set_strand, edges_set_open, edges_set_hyper export neighbors, vertex_neighbors, edge_neighbors +export Directedness, Directed, Undirected +export edges_in, edges_out, vertex_src, vertex_dst + include("Interfaces/Taggable.jl") export tags, tag, hastag, tag_at, replace_tag! export vertex_tags, has_vertex_tag, tag_at_vertex, tag_vertex!, untag_vertex!, replace_vertex_tag! From 683f29ec028ab17316a1862c7b4ead58446f6346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Tue, 15 Jul 2025 17:15:27 +0200 Subject: [PATCH 2/7] Rename functions --- README.md | 8 +-- docs/src/index.md | 8 +-- docs/src/interfaces.md | 16 +++--- src/Components/IncidentNetwork.jl | 12 ++--- src/Components/SimpleNetwork.jl | 4 +- src/Interfaces/Network.jl | 88 ++++++++++++++++--------------- src/Networks.jl | 6 +-- test/unit/network.jl | 12 ++--- 8 files changed, 79 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 1e27a76..3634dc9 100644 --- a/README.md +++ b/README.md @@ -70,20 +70,20 @@ julia> Networks.link!(g, :b, 1); julia> Networks.link!(g, :c, 1); ``` -In order to query the vertices connected by an edge, use `edge_incidents`: +In order to query the vertices connected by an edge, use `incident_vertices`: ```julia -julia> edge_incidents(g, 1) +julia> incident_vertices(g, 1) Set{Symbol} with 3 elements: :a :b :c ``` -... and to query the edges connected to a vertex, use `vertex_incidents`: +... and to query the edges connected to a vertex, use `incident_edges`: ```julia -julia> vertex_incidents(g, :a) +julia> incident_edges(g, :a) Set{Int64} with 1 element: 1 ``` diff --git a/docs/src/index.md b/docs/src/index.md index f1319ae..c3a3e5f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -53,14 +53,14 @@ Networks.link!(g, :b, 1) Networks.link!(g, :c, 1) ``` -In order to query the vertices connected by an edge, use `edge_incidents`: +In order to query the vertices connected by an edge, use `incident_vertices`: ```@repl example -edge_incidents(g, 1) +incident_vertices(g, 1) ``` -... and to query the edges connected to a vertex, use `edge_incidents`: +... and to query the edges connected to a vertex, use `incident_vertices`: ```@repl example -vertex_incidents(g, :a) +incident_edges(g, :a) ``` diff --git a/docs/src/interfaces.md b/docs/src/interfaces.md index 043215e..e2e97b5 100644 --- a/docs/src/interfaces.md +++ b/docs/src/interfaces.md @@ -17,18 +17,18 @@ A type implementing the `Network` interface must implement the following methods | :------------------------ | :----------------------------------------------- | | `all_vertices(g)` | Returns the list of vertices | | `all_edges(g)` | Returns the list of edges | -| `edge_incidents(g, e)` | Returns the vertices connected by edge `e` | -| `vertex_incidents(g, v)` | Returns the edges conected to vertex `v` | +| `incident_vertices(g, e)` | Returns the vertices connected by edge `e` | +| `incident_edges(g, v)` | Returns the edges conected to vertex `v` | | `Directedness(::Type{G})` | Returns the directedness trait of graph type `G` | ### Directed methods -| Required method | Description | -| :----------------- | :-------------------------------------------- | -| `edges_in(g, v)` | Returns the edges incoming to vertex `v` | -| `edges_out(g, v)` | Returns the vertices outgoing from vertex `v` | -| `vertex_src(g, e)` | Returns the source vertex of edge `e` | -| `vertex_dst(g, e)` | Returns the destination vertex of edge `e` | +| Required method | Description | +| :------------------------- | :-------------------------------------------- | +| `incoming_edges(g, v)` | Returns the edges incoming to vertex `v` | +| `outgoing_edges(g, v)` | Returns the vertices outgoing from vertex `v` | +| `source_vertex(g, e)` | Returns the source vertex of edge `e` | +| `destination_vertex(g, e)` | Returns the destination vertex of edge `e` | !!! todo What about hypergraphs? In such case, the source and destination of an edge can be multiple and thus, we should have a `vertices_src` and `vertices_dst` functions. diff --git a/src/Components/IncidentNetwork.jl b/src/Components/IncidentNetwork.jl index 386ae6f..ddfc0f3 100644 --- a/src/Components/IncidentNetwork.jl +++ b/src/Components/IncidentNetwork.jl @@ -32,8 +32,8 @@ all_vertices(graph::IncidentNetwork) = keys(graph.vertexmap) all_edges(graph::IncidentNetwork) = keys(graph.edgemap) # TODO should we copy the sets to avoid accidental mutation? -edge_incidents(graph::IncidentNetwork, e) = graph.edgemap[e] -vertex_incidents(graph::IncidentNetwork, v) = graph.vertexmap[v] +incident_vertices(graph::IncidentNetwork, e) = graph.edgemap[e] +incident_edges(graph::IncidentNetwork, v) = graph.vertexmap[v] function vertex_neighbors(graph::IncidentNetwork, v) neighbors = Set{typeof(v)}() @@ -106,11 +106,11 @@ end # TODO parameterize `EdgePersistence` to allow for different edge persistence strategies function rmvertex!(graph::IncidentNetwork, vertex) - # isempty(vertex_incidents(graph, vertex)) || throw(ArgumentError("Vertex $vertex is incident to edges. Unlink edges first.")) + # isempty(incident_edges(graph, vertex)) || throw(ArgumentError("Vertex $vertex is incident to edges. Unlink edges first.")) hasvertex(graph, vertex) || throw(ArgumentError("Vertex $vertex does not exist in the graph")) # unlink vertex-edge pairs - for edge in vertex_incidents(graph, vertex) + for edge in incident_edges(graph, vertex) unlink!(graph, vertex, edge) end @@ -127,11 +127,11 @@ function addedge!(graph::IncidentNetwork{V,E}, edge) where {V,E} end function rmedge!(graph::IncidentNetwork, edge) - # isempty(edge_incidents(graph, edge)) || throw(ArgumentError("Edge $edge is incident to vertices. Unlink vertices first.")) + # isempty(incident_vertices(graph, edge)) || throw(ArgumentError("Edge $edge is incident to vertices. Unlink vertices first.")) hasedge(graph, edge) || throw(ArgumentError("Edge $edge does not exist in the graph")) # unlink edge-vertex pairs - for vertex in edge_incidents(graph, edge) + for vertex in incident_vertices(graph, edge) unlink!(graph, vertex, edge) end diff --git a/src/Components/SimpleNetwork.jl b/src/Components/SimpleNetwork.jl index 1a0d290..3c1edd6 100644 --- a/src/Components/SimpleNetwork.jl +++ b/src/Components/SimpleNetwork.jl @@ -35,8 +35,8 @@ edges(g::SimpleNetwork) = SimpleEdgeIter(g) all_vertices(g::SimpleNetwork) = 1:length(g.fadjlist) all_edges(g::SimpleNetwork) = SimpleEdgeIter(g) -edge_incidents(::SimpleNetwork, e::SimpleEdge) = [e.v1, e.v2] -vertex_incidents(g::SimpleNetwork, v) = map(dst -> SimpleEdge(v, dst), g.fadjlist[v]) +incident_vertices(g::SimpleNetwork, e::SimpleEdge) = [e.v1, e.v2] +incident_edges(g::SimpleNetwork, v) = g.fadjlist[v] vertex_neighbors(g::SimpleNetwork, v) = g.fadjlist[v] function edge_neighbors(g::SimpleNetwork, e::SimpleEdge) diff --git a/src/Interfaces/Network.jl b/src/Interfaces/Network.jl index d18a79b..c86ac6d 100644 --- a/src/Interfaces/Network.jl +++ b/src/Interfaces/Network.jl @@ -70,18 +70,20 @@ Returns the edges in the `graph`. function all_edges end """ - edge_incidents(graph, e) + incident_vertices(graph, e) Returns the vertices connected by edge `e` in `graph`. """ -function edge_incidents end +function incident_vertices end +@deprecate edge_incidents(args...; kwargs...) incident_vertices(args...; kwargs...) true """ - vertex_incidents(graph, v) + incident_edges(graph, v) Returns the edges connected to vertex `v` in `graph`. """ -function vertex_incidents end +function incident_edges end +@deprecate vertex_incidents(args...; kwargs...) incident_edges(args...; kwargs...) true """ vertex_neighbors(graph, v) @@ -149,32 +151,34 @@ function edge_at end # directed methods """ - edges_in(graph, v) + incoming_edges(graph, v) Returns the edges incoming to vertex `v` in `graph`. """ -function edges_in end +function incoming_edges end """ - edges_out(graph, v) + outgoing_edges(graph, v) Returns the edges outgoing from vertex `v` in `graph`. """ -function edges_out end +function outgoing_edges end """ - vertex_src(graph, g) + source_vertex(graph, g) Returns the source vertex of edge `e` in `graph`. """ -function vertex_src end +function source_vertex end """ - vertex_dst(graph, e) + destination_vertex(graph, e) Returns the destination vertex of edge `e` in `graph`. """ -function vertex_dst end +function destination_vertex end + +# TODO `neighbor_vertices`, `neighbor_edges`, `predecessor_vertices`, `successor_vertices` # mutating methods """ @@ -258,15 +262,15 @@ all_edges(graph) = all_edges(graph, DelegatorTrait(Network(), graph)) all_edges(graph, ::DelegateToField) = all_edges(delegator(Network(), graph)) all_edges(graph, ::DontDelegate) = throw(MethodError(all_edges, (graph,))) -## `edge_incidents` -edge_incidents(graph, e) = edge_incidents(graph, e, DelegatorTrait(Network(), graph)) -edge_incidents(graph, e, ::DelegateToField) = edge_incidents(delegator(Network(), graph), e) -edge_incidents(graph, e, ::DontDelegate) = throw(MethodError(edge_incidents, (graph, e))) +## `incident_vertices` +incident_vertices(graph, e) = incident_vertices(graph, e, DelegatorTrait(Network(), graph)) +incident_vertices(graph, e, ::DelegateToField) = incident_vertices(delegator(Network(), graph), e) +incident_vertices(graph, e, ::DontDelegate) = throw(MethodError(incident_vertices, (graph, e))) -## `vertex_incidents` -vertex_incidents(graph, v) = vertex_incidents(graph, v, DelegatorTrait(Network(), graph)) -vertex_incidents(graph, v, ::DelegateToField) = vertex_incidents(delegator(Network(), graph), v) -vertex_incidents(graph, v, ::DontDelegate) = throw(MethodError(vertex_incidents, (graph, v))) +## `incident_edges` +incident_edges(graph, v) = incident_edges(graph, v, DelegatorTrait(Network(), graph)) +incident_edges(graph, v, ::DelegateToField) = incident_edges(delegator(Network(), graph), v) +incident_edges(graph, v, ::DontDelegate) = throw(MethodError(incident_edges, (graph, v))) ## `vertex_neighbors` vertex_neighbors(graph, v) = vertex_neighbors(graph, v, DelegatorTrait(Network(), graph)) @@ -369,7 +373,7 @@ function edges_set_strand(graph, ::DontDelegate) fallback(edges_set_strand) stranded_edges = Set{edge_type(graph)}() for edge in edges(graph) - vertex_set = edge_incidents(graph, edge) + vertex_set = incident_vertices(graph, edge) if length(vertex_set) == 0 push!(stranded_edges, edge) end @@ -384,7 +388,7 @@ function edges_set_open(graph, ::DontDelegate) fallback(edges_set_open) stranded_edges = Set{edge_type(graph)}() for edge in edges(graph) - vertex_set = edge_incidents(graph, edge) + vertex_set = incident_vertices(graph, edge) if length(vertex_set) == 1 push!(stranded_edges, edge) end @@ -398,7 +402,7 @@ edges_set_hyper(graph, ::DelegateToField) = edges_set_hyper(delegator(Network(), function edges_set_hyper(graph, ::DontDelegate) stranded_edges = Set{edge_type(graph)}() for edge in edges(graph) - vertex_set = edge_incidents(graph, edge) + vertex_set = incident_vertices(graph, edge) if length(vertex_set) > 2 push!(stranded_edges, edge) end @@ -406,25 +410,25 @@ function edges_set_hyper(graph, ::DontDelegate) return stranded_edges end -## `edges_in` -edges_in(graph, v) = edges_in(graph, v, DelegatorTrait(Network(), graph)) -edges_in(graph, v, ::DelegateToField) = edges_in(delegator(Network(), graph), v) -edges_in(graph, v, ::DontDelegate) = throw(MethodError(edges_in, (graph, v))) +## `incoming_edges` +incoming_edges(graph, v) = incoming_edges(graph, v, DelegatorTrait(Network(), graph)) +incoming_edges(graph, v, ::DelegateToField) = incoming_edges(delegator(Network(), graph), v) +incoming_edges(graph, v, ::DontDelegate) = throw(MethodError(incoming_edges, (graph, v))) -## `edges_out` -edges_out(graph, v) = edges_out(graph, v, DelegatorTrait(Network(), graph)) -edges_out(graph, v, ::DelegateToField) = edges_out(delegator(Network(), graph), v) -edges_out(graph, v, ::DontDelegate) = throw(MethodError(edges_out, (graph, v))) +## `outgoing_edges` +outgoing_edges(graph, v) = outgoing_edges(graph, v, DelegatorTrait(Network(), graph)) +outgoing_edges(graph, v, ::DelegateToField) = outgoing_edges(delegator(Network(), graph), v) +outgoing_edges(graph, v, ::DontDelegate) = throw(MethodError(outgoing_edges, (graph, v))) -## `vertex_src` -vertex_src(graph, e) = vertex_src(graph, e, DelegatorTrait(Network(), graph)) -vertex_src(graph, e, ::DelegateToField) = vertex_src(delegator(Network(), graph), e) -vertex_src(graph, e, ::DontDelegate) = throw(MethodError(vertex_src, (graph, e))) +## `source_vertex` +source_vertex(graph, e) = source_vertex(graph, e, DelegatorTrait(Network(), graph)) +source_vertex(graph, e, ::DelegateToField) = source_vertex(delegator(Network(), graph), e) +source_vertex(graph, e, ::DontDelegate) = throw(MethodError(source_vertex, (graph, e))) -## `vertex_dst` -vertex_dst(graph, e) = vertex_dst(graph, e, DelegatorTrait(Network(), graph)) -vertex_dst(graph, e, ::DelegateToField) = vertex_dst(delegator(Network(), graph), e) -vertex_dst(graph, e, ::DontDelegate) = throw(MethodError(vertex_dst, (graph, e))) +## `destination_vertex` +destination_vertex(graph, e) = destination_vertex(graph, e, DelegatorTrait(Network(), graph)) +destination_vertex(graph, e, ::DelegateToField) = destination_vertex(delegator(Network(), graph), e) +destination_vertex(graph, e, ::DontDelegate) = throw(MethodError(destination_vertex, (graph, e))) ## `addvertex!` # TODO check if vertex already exists @@ -465,7 +469,7 @@ rmvertex!(graph, v, ::DontDelegate) = throw(MethodError(rmvertex!, (graph, v))) # checkeffect(graph, RemoveVertexEffect(v)) # # trait is to remove edges on vertex removal -# for edge in vertex_incidents(graph, v) +# for edge in incident_edges(graph, v) # rmedge!(graph, edge) # end @@ -479,8 +483,8 @@ rmvertex!(graph, v, ::DontDelegate) = throw(MethodError(rmvertex!, (graph, v))) # # trait is to remove edges on vertex removal if that leaves them stranded # # (i.e. no open indices left) -# for edge in vertex_incidents(graph, v) -# if length(edge_incidents(graph, edge)) == 1 +# for edge in incident_edges(graph, v) +# if length(incident_vertices(graph, edge)) == 1 # rmedge!(graph, edge) # end # end diff --git a/src/Networks.jl b/src/Networks.jl index 698a52c..06f6d7d 100644 --- a/src/Networks.jl +++ b/src/Networks.jl @@ -8,13 +8,13 @@ include("Utils.jl") include("Interfaces/Network.jl") export Network -export vertices, all_vertices, vertex_at, edge_incidents, vertex_type, hasvertex, nvertices, addvertex!, rmvertex! -export edges, all_edges, edge_at, vertex_incidents, edge_type, hasedge, nedges, addedge!, rmedge! +export vertices, all_vertices, vertex_at, incident_vertices, vertex_type, hasvertex, nvertices, addvertex!, rmvertex! +export edges, all_edges, edge_at, incident_edges, edge_type, hasedge, nedges, addedge!, rmedge! export edges_set_strand, edges_set_open, edges_set_hyper export neighbors, vertex_neighbors, edge_neighbors export Directedness, Directed, Undirected -export edges_in, edges_out, vertex_src, vertex_dst +export incoming_edges, outgoing_edges, source_vertex, destination_vertex include("Interfaces/Taggable.jl") export tags, tag, hastag, tag_at, replace_tag! diff --git a/test/unit/network.jl b/test/unit/network.jl index 2569c8e..2853bfe 100644 --- a/test/unit/network.jl +++ b/test/unit/network.jl @@ -71,8 +71,8 @@ Networks.vertices(g::MockNetwork) = vertices(g.g) Networks.edges(g::MockNetwork) = edges(g.g) Networks.all_vertices(g::MockNetwork) = vertices(g.g) Networks.all_edges(g::MockNetwork) = edges(g.g) -Networks.edge_incidents(g::MockNetwork, edge) = edge_incidents(g.g, edge) -Networks.vertex_incidents(g::MockNetwork, vertex) = vertex_incidents(g.g, vertex) +Networks.incident_vertices(g::MockNetwork, edge) = incident_vertices(g.g, edge) +Networks.incident_edges(g::MockNetwork, vertex) = incident_edges(g.g, vertex) Networks.vertex_type(g::MockNetwork) = vertex_type(g.g) Networks.edge_type(g::MockNetwork) = edge_type(g.g) @@ -103,26 +103,26 @@ end end end -@testset "edge_incidents" begin +@testset "incident_vertices" begin @testset "$(typeof(network))" for network in [ IncidentNetwork(fixture.vertex_map, fixture.edge_map), WrapNetwork(fixture.vertex_map, fixture.edge_map), MockNetwork(fixture.vertex_map, fixture.edge_map), ] for (edge, vertex_set) in fixture.edge_map - @test issetequal(edge_incidents(network, edge), vertex_set) + @test issetequal(incident_vertices(network, edge), vertex_set) end end end -@testset "vertex_incidents" begin +@testset "incident_edges" begin @testset "$(typeof(network))" for network in [ IncidentNetwork(fixture.vertex_map, fixture.edge_map), WrapNetwork(fixture.vertex_map, fixture.edge_map), MockNetwork(fixture.vertex_map, fixture.edge_map), ] for (vertex, edge_set) in fixture.vertex_map - @test issetequal(vertex_incidents(network, vertex), edge_set) + @test issetequal(incident_edges(network, vertex), edge_set) end end end From abed4d03909e3eed6225965d48a0c503bf5c1466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Tue, 15 Jul 2025 19:13:06 +0200 Subject: [PATCH 3/7] add `neighbor_vertices`, `neighbor_edges` functions to `Network` interface --- src/Interfaces/Network.jl | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Interfaces/Network.jl b/src/Interfaces/Network.jl index c86ac6d..a2ff569 100644 --- a/src/Interfaces/Network.jl +++ b/src/Interfaces/Network.jl @@ -178,7 +178,21 @@ Returns the destination vertex of edge `e` in `graph`. """ function destination_vertex end -# TODO `neighbor_vertices`, `neighbor_edges`, `predecessor_vertices`, `successor_vertices` +""" + neighbor_vertices(graph, v) + +Returns the vertices that share and edge with vertex `v`. +""" +function neighbor_vertices end + +""" + neighbor_edges(graph, e) + +Returns the edges that share a vertex with edge `e`. +""" +function neighbor_edges end + +# TODO `predecessor_vertices`, `successor_vertices` # mutating methods """ @@ -430,6 +444,16 @@ destination_vertex(graph, e) = destination_vertex(graph, e, DelegatorTrait(Netwo destination_vertex(graph, e, ::DelegateToField) = destination_vertex(delegator(Network(), graph), e) destination_vertex(graph, e, ::DontDelegate) = throw(MethodError(destination_vertex, (graph, e))) +### `neighbor_vertices` +neighbor_vertices(graph, v) = neighbor_vertices(graph, v, DelegatorTrait(Network(), graph)) +neighbor_vertices(graph, v, ::DelegateToField) = neighbor_vertices(delegator(Network(), graph), v) +neighbor_vertices(graph, v, ::DontDelegate) = throw(MethodError(neighbor_vertices, (graph, v))) + +### `neighbor_edges` +neighbor_edges(graph, e) = neighbor_edges(graph, e, DelegatorTrait(Network(), graph)) +neighbor_edges(graph, e, ::DelegateToField) = neighbor_edges(delegator(Network(), graph), e) +neighbor_edges(graph, e, ::DontDelegate) = throw(MethodError(neighbor_edges, (graph, e))) + ## `addvertex!` # TODO check if vertex already exists # hasvertex(graph, e.vertex) && throw(ArgumentError("Vertex $(e.vertex) already exists in network")) From 121eec566a09937c61b894338e96d7763f59cdcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Wed, 16 Jul 2025 05:44:10 +0200 Subject: [PATCH 4/7] add `predecessor_vertices`, `successor_vertices` --- docs/src/interfaces.md | 21 ++++++++++++--------- src/Interfaces/Network.jl | 24 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/src/interfaces.md b/docs/src/interfaces.md index e2e97b5..516add1 100644 --- a/docs/src/interfaces.md +++ b/docs/src/interfaces.md @@ -23,15 +23,18 @@ A type implementing the `Network` interface must implement the following methods ### Directed methods -| Required method | Description | -| :------------------------- | :-------------------------------------------- | -| `incoming_edges(g, v)` | Returns the edges incoming to vertex `v` | -| `outgoing_edges(g, v)` | Returns the vertices outgoing from vertex `v` | -| `source_vertex(g, e)` | Returns the source vertex of edge `e` | -| `destination_vertex(g, e)` | Returns the destination vertex of edge `e` | - -!!! todo - What about hypergraphs? In such case, the source and destination of an edge can be multiple and thus, we should have a `vertices_src` and `vertices_dst` functions. +!!! warning + + Directedness on hypergraphs is not well-defined. It is such an edge case, that we have decide to don't support it explicitly for the time being. + +| Required method | Description | +| :--------------------------- | :------------------------------------------------ | +| `incoming_edges(g, v)` | Returns the edges incoming to vertex `v` | +| `outgoing_edges(g, v)` | Returns the vertices outgoing from vertex `v` | +| `source_vertex(g, e)` | Returns the source vertex of edge `e` | +| `destination_vertex(g, e)` | Returns the destination vertex of edge `e` | +| `predecessor_vertices(g, v)` | Returns the vertices that are predecessors of `v` | +| `successor_vertices(g, v)` | Returns the vertices that are successors of `v` | ### Optional methods diff --git a/src/Interfaces/Network.jl b/src/Interfaces/Network.jl index a2ff569..37cd931 100644 --- a/src/Interfaces/Network.jl +++ b/src/Interfaces/Network.jl @@ -192,7 +192,19 @@ Returns the edges that share a vertex with edge `e`. """ function neighbor_edges end -# TODO `predecessor_vertices`, `successor_vertices` +""" + predecessor_vertices(graph, v) + +Returns the vertices that are predecessors of vertex `v` in `graph`. +""" +function predecessor_vertices end + +""" + successor_vertices(graph, v) + +Returns the vertices that are successors of vertex `v` in `graph`. +""" +function successor_vertices end # mutating methods """ @@ -454,6 +466,16 @@ neighbor_edges(graph, e) = neighbor_edges(graph, e, DelegatorTrait(Network(), gr neighbor_edges(graph, e, ::DelegateToField) = neighbor_edges(delegator(Network(), graph), e) neighbor_edges(graph, e, ::DontDelegate) = throw(MethodError(neighbor_edges, (graph, e))) +### `predecessor_vertices` +predecessor_vertices(graph, v) = predecessor_vertices(graph, v, DelegatorTrait(Network(), graph)) +predecessor_vertices(graph, v, ::DelegateToField) = predecessor_vertices(delegator(Network(), graph), v) +predecessor_vertices(graph, v, ::DontDelegate) = throw(MethodError(predecessor_vertices, (graph, v))) + +### `successor_vertices` +successor_vertices(graph, v) = successor_vertices(graph, v, DelegatorTrait(Network(), graph)) +successor_vertices(graph, v, ::DelegateToField) = successor_vertices(delegator(Network(), graph), v) +successor_vertices(graph, v, ::DontDelegate) = throw(MethodError(successor_vertices, (graph, v))) + ## `addvertex!` # TODO check if vertex already exists # hasvertex(graph, e.vertex) && throw(ArgumentError("Vertex $(e.vertex) already exists in network")) From d85a4d6d61e9d323cc24c3e4aa8c202d54140657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Wed, 16 Jul 2025 06:10:21 +0200 Subject: [PATCH 5/7] Rename `*_neighbors` methods --- docs/src/interfaces.md | 2 ++ src/Algorithms/cycles.jl | 2 +- src/Components/IncidentNetwork.jl | 4 +-- src/Components/SimpleNetwork.jl | 8 ++--- src/Interfaces/Network.jl | 56 ++++++++++++------------------- src/Networks.jl | 2 +- test/unit/network.jl | 16 ++++----- 7 files changed, 40 insertions(+), 50 deletions(-) diff --git a/docs/src/interfaces.md b/docs/src/interfaces.md index 516add1..45b9dcd 100644 --- a/docs/src/interfaces.md +++ b/docs/src/interfaces.md @@ -19,6 +19,8 @@ A type implementing the `Network` interface must implement the following methods | `all_edges(g)` | Returns the list of edges | | `incident_vertices(g, e)` | Returns the vertices connected by edge `e` | | `incident_edges(g, v)` | Returns the edges conected to vertex `v` | +| `neighbor_vertices(g, v)` | Returns the vertices neighboring vertex `v` | +| `neighbor_edges(g, e)` | Returns the edges neighboring edge `e` | | `Directedness(::Type{G})` | Returns the directedness trait of graph type `G` | ### Directed methods diff --git a/src/Algorithms/cycles.jl b/src/Algorithms/cycles.jl index 447fbb0..3413435 100644 --- a/src/Algorithms/cycles.jl +++ b/src/Algorithms/cycles.jl @@ -17,7 +17,7 @@ function cycle_basis(g, root=nothing) while !isempty(stack) z = pop!(stack) zused = used[z] - for nbr in vertex_neighbors(g, z) + for nbr in neighbor_vertices(g, z) if !in(nbr, keys_used) pred[nbr] = z push!(keys_pred, nbr) diff --git a/src/Components/IncidentNetwork.jl b/src/Components/IncidentNetwork.jl index ddfc0f3..56407ae 100644 --- a/src/Components/IncidentNetwork.jl +++ b/src/Components/IncidentNetwork.jl @@ -35,7 +35,7 @@ all_edges(graph::IncidentNetwork) = keys(graph.edgemap) incident_vertices(graph::IncidentNetwork, e) = graph.edgemap[e] incident_edges(graph::IncidentNetwork, v) = graph.vertexmap[v] -function vertex_neighbors(graph::IncidentNetwork, v) +function neighbor_vertices(graph::IncidentNetwork, v) neighbors = Set{typeof(v)}() for edge in vertex_incidents(graph, v) for neighbor in edge_incidents(graph, edge) @@ -47,7 +47,7 @@ function vertex_neighbors(graph::IncidentNetwork, v) return neighbors end -function edge_neighbors(graph::IncidentNetwork, e) +function neighbor_edges(graph::IncidentNetwork, e) neighbors = Set{typeof(e)}() for vertex in edge_incidents(graph, e) for neighbor in vertex_incidents(graph, vertex) diff --git a/src/Components/SimpleNetwork.jl b/src/Components/SimpleNetwork.jl index 3c1edd6..4b77d4f 100644 --- a/src/Components/SimpleNetwork.jl +++ b/src/Components/SimpleNetwork.jl @@ -38,10 +38,10 @@ all_edges(g::SimpleNetwork) = SimpleEdgeIter(g) incident_vertices(g::SimpleNetwork, e::SimpleEdge) = [e.v1, e.v2] incident_edges(g::SimpleNetwork, v) = g.fadjlist[v] -vertex_neighbors(g::SimpleNetwork, v) = g.fadjlist[v] -function edge_neighbors(g::SimpleNetwork, e::SimpleEdge) - neigh_v1 = vertex_neighbors(g, e.v1) - neigh_v2 = vertex_neighbors(g, e.v2) +neighbor_vertices(g::SimpleNetwork, v) = g.fadjlist[v] +function neighbor_edges(g::SimpleNetwork, e::SimpleEdge) + neigh_v1 = neighbor_vertices(g, e.v1) + neigh_v2 = neighbor_vertices(g, e.v2) neighbors = Set{edge_type(g)}() for v in neigh_v1 diff --git a/src/Interfaces/Network.jl b/src/Interfaces/Network.jl index 37cd931..f631e74 100644 --- a/src/Interfaces/Network.jl +++ b/src/Interfaces/Network.jl @@ -86,18 +86,20 @@ function incident_edges end @deprecate vertex_incidents(args...; kwargs...) incident_edges(args...; kwargs...) true """ - vertex_neighbors(graph, v) + neighbor_vertices(graph, v) -Returns the vertices neighboring vertex `v` in the `graph`. +Returns the vertices neighboring vertex `v` in the `graph`; i.e. those that share an edge with vertex `v`. """ -function vertex_neighbors end +function neighbor_vertices end +@deprecate vertex_neighbors(args...; kwargs...) neighbor_vertices(args...; kwargs...) true """ - edge_neighbors(graph, e) + neighbor_edges(graph, e) -Returns the edges neighboring edge `e` in the `graph`. +Returns the edges neighboring edge `e` in the `graph`; i.e. those that share a vertex with edge `e`. """ -function edge_neighbors end +function neighbor_edges end +@deprecate edge_neighbors(args...; kwargs...) neighbor_edges(args...; kwargs...) true # query methods with default implementation """ @@ -178,20 +180,6 @@ Returns the destination vertex of edge `e` in `graph`. """ function destination_vertex end -""" - neighbor_vertices(graph, v) - -Returns the vertices that share and edge with vertex `v`. -""" -function neighbor_vertices end - -""" - neighbor_edges(graph, e) - -Returns the edges that share a vertex with edge `e`. -""" -function neighbor_edges end - """ predecessor_vertices(graph, v) @@ -273,10 +261,10 @@ end ## `neighbors` neighbors(graph; kwargs...) = neighbors(sort_nt(kwargs), graph, v) -neighbors(graph, v::AbstractVertex) = vertex_neighbors(graph, v) -neighbors(kwargs::NamedTuple{(:vertex,)}, graph) = vertex_neighbors(graph, kwargs.v) -neighbors(graph, e::AbstractEdge) = edge_neighbors(graph, e) -neighbors(kwargs::NamedTuple{(:edge,)}, graph) = edge_neighbors(graph, kwargs.e) +neighbors(graph, v::AbstractVertex) = neighbor_vertices(graph, v) +neighbors(kwargs::NamedTuple{(:vertex,)}, graph) = neighbor_vertices(graph, kwargs.v) +neighbors(graph, e::AbstractEdge) = neighbor_edges(graph, e) +neighbors(kwargs::NamedTuple{(:edge,)}, graph) = neighbor_edges(graph, kwargs.e) ## `all_vertices` all_vertices(graph) = all_vertices(graph, DelegatorTrait(Network(), graph)) @@ -298,11 +286,11 @@ incident_edges(graph, v) = incident_edges(graph, v, DelegatorTrait(Network(), gr incident_edges(graph, v, ::DelegateToField) = incident_edges(delegator(Network(), graph), v) incident_edges(graph, v, ::DontDelegate) = throw(MethodError(incident_edges, (graph, v))) -## `vertex_neighbors` -vertex_neighbors(graph, v) = vertex_neighbors(graph, v, DelegatorTrait(Network(), graph)) -vertex_neighbors(graph, v, ::DelegateToField) = vertex_neighbors(delegator(Network(), graph), v) -function vertex_neighbors(graph, v, ::DontDelegate) - fallback(vertex_neighbors) +## `neighbor_vertices` +neighbor_vertices(graph, v) = neighbor_vertices(graph, v, DelegatorTrait(Network(), graph)) +neighbor_vertices(graph, v, ::DelegateToField) = neighbor_vertices(delegator(Network(), graph), v) +function neighbor_vertices(graph, v, ::DontDelegate) + fallback(neighbor_vertices) incident_edges = vertex_incidents(graph, v) neighbors = Set{vertex_type(graph)}() for edge in incident_edges @@ -316,11 +304,11 @@ function vertex_neighbors(graph, v, ::DontDelegate) return neighbors end -## `edge_neighbors` -edge_neighbors(graph, e) = edge_neighbors(graph, e, DelegatorTrait(Network(), graph)) -edge_neighbors(graph, e, ::DelegateToField) = edge_neighbors(delegator(Network(), graph), e) -function edge_neighbors(graph, e, ::DontDelegate) - fallback(edge_neighbors) +## `neighbor_edges` +neighbor_edges(graph, e) = neighbor_edges(graph, e, DelegatorTrait(Network(), graph)) +neighbor_edges(graph, e, ::DelegateToField) = neighbor_edges(delegator(Network(), graph), e) +function neighbor_edges(graph, e, ::DontDelegate) + fallback(neighbor_edges) incident_vertices = edge_incidents(graph, e) neighbors = Set{edge_type(graph)}() for vertex in incident_vertices diff --git a/src/Networks.jl b/src/Networks.jl index 06f6d7d..c9b1c57 100644 --- a/src/Networks.jl +++ b/src/Networks.jl @@ -11,7 +11,7 @@ export Network export vertices, all_vertices, vertex_at, incident_vertices, vertex_type, hasvertex, nvertices, addvertex!, rmvertex! export edges, all_edges, edge_at, incident_edges, edge_type, hasedge, nedges, addedge!, rmedge! export edges_set_strand, edges_set_open, edges_set_hyper -export neighbors, vertex_neighbors, edge_neighbors +export neighbors, neighbor_vertices, neighbor_edges export Directedness, Directed, Undirected export incoming_edges, outgoing_edges, source_vertex, destination_vertex diff --git a/test/unit/network.jl b/test/unit/network.jl index 2853bfe..1f47232 100644 --- a/test/unit/network.jl +++ b/test/unit/network.jl @@ -127,7 +127,7 @@ end end end -@testset "vertex_neighbors" begin +@testset "neighbor_vertices" begin g = IncidentNetwork{Symbol,Int}() addvertex!(g, :a) addvertex!(g, :b) @@ -136,12 +136,12 @@ end Networks.link!(g, :a, 1) Networks.link!(g, :b, 1) - @test issetequal(vertex_neighbors(g, :a), Set([:b])) - @test issetequal(vertex_neighbors(g, :b), Set([:a])) - @test isempty(vertex_neighbors(g, :c)) + @test issetequal(neighbor_vertices(g, :a), Set([:b])) + @test issetequal(neighbor_vertices(g, :b), Set([:a])) + @test isempty(neighbor_vertices(g, :c)) end -@testset "edge_neighbors" begin +@testset "neighbor_edges" begin g = IncidentNetwork{Symbol,Int}() addvertex!(g, :a) addvertex!(g, :b) @@ -159,9 +159,9 @@ end addedge!(g, 3) Networks.link!(g, :d, 3) - @test issetequal(edge_neighbors(g, 1), Set([2])) - @test issetequal(edge_neighbors(g, 2), Set([1])) - @test isempty(edge_neighbors(g, 3)) + @test issetequal(neighbor_edges(g, 1), Set([2])) + @test issetequal(neighbor_edges(g, 2), Set([1])) + @test isempty(neighbor_edges(g, 3)) end @testset "vertex_type" begin From b207b491ef46c8bd9d95dba62b13b6b7fa50a779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Wed, 16 Jul 2025 06:26:31 +0200 Subject: [PATCH 6/7] format docs --- docs/src/interfaces.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/src/interfaces.md b/docs/src/interfaces.md index 45b9dcd..1965682 100644 --- a/docs/src/interfaces.md +++ b/docs/src/interfaces.md @@ -10,7 +10,8 @@ Using [`DelegatorTraits.jl`](https://github.com/bsc-quantic/DelegatorTraits.jl) ## Network -The `Network` interface abstracts a network or graph as a bipartite graph whose sets are the vertices and the edges. +The `Network` interface abstracts a network or graph. Unlike other graph interfaces, `Network` considers edges as first-class objects and not just as +relations of vertices. This allows for a more relaxed abstraction where self-loops, open-edges, multi-edges and hyper-edges are allowed. A type implementing the `Network` interface must implement the following methods: | Required method | Description | @@ -23,21 +24,6 @@ A type implementing the `Network` interface must implement the following methods | `neighbor_edges(g, e)` | Returns the edges neighboring edge `e` | | `Directedness(::Type{G})` | Returns the directedness trait of graph type `G` | -### Directed methods - -!!! warning - - Directedness on hypergraphs is not well-defined. It is such an edge case, that we have decide to don't support it explicitly for the time being. - -| Required method | Description | -| :--------------------------- | :------------------------------------------------ | -| `incoming_edges(g, v)` | Returns the edges incoming to vertex `v` | -| `outgoing_edges(g, v)` | Returns the vertices outgoing from vertex `v` | -| `source_vertex(g, e)` | Returns the source vertex of edge `e` | -| `destination_vertex(g, e)` | Returns the destination vertex of edge `e` | -| `predecessor_vertices(g, v)` | Returns the vertices that are predecessors of `v` | -| `successor_vertices(g, v)` | Returns the vertices that are successors of `v` | - ### Optional methods The following methods have a default implementation or their implementation is optional. @@ -53,6 +39,23 @@ The following methods have a default implementation or their implementation is o | `vertex_at(g, tag)` | If your type has some other way to refer to a vertex | _(undefined)_ | Returns the vertex related to `tag` | | `edge_at(g, tag)` | If your type has some other way to refer to an edge | _(undefined)_ | Returns the edge related to `tag` | +### Directed methods + +If your `Network` implementation represents a directed graph, the following methods are required: + +!!! warning + + Directedness on hypergraphs is not well-defined. It is such an edge case, that we have decide to don't support it explicitly for the time being. + +| Required method | Description | +| :--------------------------- | :------------------------------------------------ | +| `incoming_edges(g, v)` | Returns the edges incoming to vertex `v` | +| `outgoing_edges(g, v)` | Returns the vertices outgoing from vertex `v` | +| `source_vertex(g, e)` | Returns the source vertex of edge `e` | +| `destination_vertex(g, e)` | Returns the destination vertex of edge `e` | +| `predecessor_vertices(g, v)` | Returns the vertices that are predecessors of `v` | +| `successor_vertices(g, v)` | Returns the vertices that are successors of `v` | + ### Mutating methods | Method | Brief description | From c677382f1f501724a80bba2b9d4c1c194d5a36d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Wed, 16 Jul 2025 06:58:27 +0200 Subject: [PATCH 7/7] fix names --- src/Components/IncidentNetwork.jl | 8 ++++---- src/Interfaces/Network.jl | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Components/IncidentNetwork.jl b/src/Components/IncidentNetwork.jl index 56407ae..dd74068 100644 --- a/src/Components/IncidentNetwork.jl +++ b/src/Components/IncidentNetwork.jl @@ -37,8 +37,8 @@ incident_edges(graph::IncidentNetwork, v) = graph.vertexmap[v] function neighbor_vertices(graph::IncidentNetwork, v) neighbors = Set{typeof(v)}() - for edge in vertex_incidents(graph, v) - for neighbor in edge_incidents(graph, edge) + for edge in incident_edges(graph, v) + for neighbor in incident_vertices(graph, edge) if neighbor != v push!(neighbors, neighbor) end @@ -49,8 +49,8 @@ end function neighbor_edges(graph::IncidentNetwork, e) neighbors = Set{typeof(e)}() - for vertex in edge_incidents(graph, e) - for neighbor in vertex_incidents(graph, vertex) + for vertex in incident_vertices(graph, e) + for neighbor in incident_edges(graph, vertex) if neighbor != e push!(neighbors, neighbor) end diff --git a/src/Interfaces/Network.jl b/src/Interfaces/Network.jl index f631e74..d584061 100644 --- a/src/Interfaces/Network.jl +++ b/src/Interfaces/Network.jl @@ -291,10 +291,10 @@ neighbor_vertices(graph, v) = neighbor_vertices(graph, v, DelegatorTrait(Network neighbor_vertices(graph, v, ::DelegateToField) = neighbor_vertices(delegator(Network(), graph), v) function neighbor_vertices(graph, v, ::DontDelegate) fallback(neighbor_vertices) - incident_edges = vertex_incidents(graph, v) + incident_edges = incident_edges(graph, v) neighbors = Set{vertex_type(graph)}() for edge in incident_edges - edge_vertices = edge_incidents(graph, edge) + edge_vertices = incident_vertices(graph, edge) for neighbor in edge_vertices if neighbor != v push!(neighbors, neighbor) @@ -309,10 +309,10 @@ neighbor_edges(graph, e) = neighbor_edges(graph, e, DelegatorTrait(Network(), gr neighbor_edges(graph, e, ::DelegateToField) = neighbor_edges(delegator(Network(), graph), e) function neighbor_edges(graph, e, ::DontDelegate) fallback(neighbor_edges) - incident_vertices = edge_incidents(graph, e) + incident_vertices = incident_vertices(graph, e) neighbors = Set{edge_type(graph)}() for vertex in incident_vertices - vertex_edges = vertex_incidents(graph, vertex) + vertex_edges = incident_edges(graph, vertex) for neighbor in vertex_edges if neighbor != e push!(neighbors, neighbor)