From 5f997f66a998707923e4a1907e14ba8d9f68044d Mon Sep 17 00:00:00 2001 From: Cecile Ane Date: Fri, 20 Oct 2023 22:10:48 -0500 Subject: [PATCH 1/6] fix #69 and #70 --- src/directedness.jl | 4 +++- src/graphs.jl | 4 ++++ test/misc.jl | 12 ++++++++++-- test/tutorial/1_basics.jl | 2 +- test/tutorial/2_graphs.jl | 3 ++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/directedness.jl b/src/directedness.jl index c39568e..f4a65f3 100644 --- a/src/directedness.jl +++ b/src/directedness.jl @@ -12,6 +12,8 @@ end arrange(graph, label_1, label_2) Sort two vertex labels in a default order (useful to uniquely express undirected edges). +For undirected graphs, the default order is based on the labels themselves +to be robust to vertex re-coding, so the labels need to support `<`. """ function arrange end @@ -24,7 +26,7 @@ end @traitfn function arrange( ::MG, label_1, label_2, code_1, code_2 ) where {MG <: MetaGraph; !IsDirected{MG}} - if code_1 < code_2 + if label_1 < label_2 (label_1, label_2) else (label_2, label_1) diff --git a/src/graphs.jl b/src/graphs.jl index 2ca9b28..287fe4a 100644 --- a/src/graphs.jl +++ b/src/graphs.jl @@ -174,6 +174,7 @@ Add an edge `(label_1, label_2)` to MetaGraph `meta_graph` with metadata `data`. If the `EdgeData` type of `meta_graph` is `Nothing`, `data` can be omitted. Return `true` if the edge has been added, `false` otherwise. +If `(label_1, label_2)` already existed, its data is *not* updated to `data`. """ function Graphs.add_edge!(meta_graph::MetaGraph, label_1, label_2, data) if !haskey(meta_graph, label_1) || !haskey(meta_graph, label_2) @@ -181,6 +182,9 @@ function Graphs.add_edge!(meta_graph::MetaGraph, label_1, label_2, data) end code_1, code_2 = code_for(meta_graph, label_1), code_for(meta_graph, label_2) label_tup = arrange(meta_graph, label_1, label_2, code_1, code_2) + if has_edge(meta_graph.graph, code_1, code_2) + return false + end meta_graph.edge_data[label_tup] = data ne_prev = ne(meta_graph.graph) add_edge!(meta_graph.graph, code_1, code_2) diff --git a/test/misc.jl b/test/misc.jl index 66f8b2a..dc90194 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -8,6 +8,9 @@ function test_labels_codes(mg::MetaGraph) for (label_1, label_2) in edge_labels(mg) @test has_edge(mg, code_for(mg, label_1), code_for(mg, label_2)) end + for e in edge_labels(mg) # arrange() consistent with mg.edge_data + @test_logs mg[e...] # no log, no error + end for label_1 in labels(mg) for label_2 in outneighbor_labels(mg, label_1) @test has_edge(mg, code_for(mg, label_1), code_for(mg, label_2)) @@ -24,17 +27,22 @@ end :red => (255, 0, 0), :green => (0, 255, 0), :blue => (0, 0, 255) ] edges_description = [ - (:red, :green) => :yellow, (:red, :blue) => :magenta, (:green, :blue) => :cyan + (:green, :red) => :yellow, (:blue, :red) => :magenta, (:blue, :green) => :cyan ] colors = MetaGraph(graph, vertices_description, edges_description, "additive colors") test_labels_codes(colors) + # attempt to add an existing edge: non-standard order, different data + @test !add_edge!(colors, :green, :blue, :teal) + @test length(colors.edge_data) == ne(colors) + @test colors[:blue, :green] == :cyan + # Delete vertex in a copy and test again colors_copy = copy(colors) rem_vertex!(colors_copy, 1) - test_labels_codes(colors) + test_labels_codes(colors_copy) end @testset verbose = true "Short-form add_vertex!/add_edge!" begin diff --git a/test/tutorial/1_basics.jl b/test/tutorial/1_basics.jl index 34581db..a19f60e 100644 --- a/test/tutorial/1_basics.jl +++ b/test/tutorial/1_basics.jl @@ -16,7 +16,7 @@ colors = MetaGraph( graph_data="additive colors", # tag for the whole graph ) -# The `label_type` argument defines how vertices will be referred to, it can be anything you want (although integer types are generally discouraged, to avoid confusion with the vertex codes used by Graphs.jl). The `vertex_data_type` and `edge_data_type` type determine what kind of data will be associated with each vertex and edge. Finally, `graph_data` can contain an arbitrary object associated with the graph as a whole. +# The `label_type` argument defines how vertices will be referred to. It can be anything you want, provided that pairs of labels can be compared with `<`. Integer types are generally discouraged, to avoid confusion with the vertex codes used by Graphs.jl. The `vertex_data_type` and `edge_data_type` type determine what kind of data will be associated with each vertex and edge. Finally, `graph_data` can contain an arbitrary object associated with the graph as a whole. # If you don't care about labels at all, using the integer vertex codes as labels may be reasonable. Just keep in mind that labels do not change with vertex deletion, whereas vertex codes get decreased, so the coherence will be broken. diff --git a/test/tutorial/2_graphs.jl b/test/tutorial/2_graphs.jl index 7f74711..612fcf4 100644 --- a/test/tutorial/2_graphs.jl +++ b/test/tutorial/2_graphs.jl @@ -35,7 +35,8 @@ cities[:Paris, :Berlin] = 878; is_directed(cities) @test @inferred !is_directed(cities) #src @test !istrait(IsDirected{typeof(cities)}) #src -@test MetaGraphsNext.arrange(cities, :London, :Paris) == (:Paris, :London) #src +@test MetaGraphsNext.arrange(cities, :London, :Paris) == (:London, :Paris) #src +@test MetaGraphsNext.arrange(cities, :Paris, :London) == (:London, :Paris) #src #- eltype(cities) @test @inferred eltype(cities) == Int #src From 99bb60adfb6cafd7ada69b3e00ed960761eeeb83 Mon Sep 17 00:00:00 2001 From: Cecile Ane Date: Sun, 22 Oct 2023 09:01:46 -0500 Subject: [PATCH 2/6] v0.7.0 because breaking change --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8fe55f8..1ef0f7a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "MetaGraphsNext" uuid = "fa8bd995-216d-47f1-8a91-f3b68fbeb377" -version = "0.6.0" +version = "0.7.0" [deps] Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" From 39029861e9162ea5dba8c68a1b4d83528fb8555c Mon Sep 17 00:00:00 2001 From: Cecile Ane Date: Sun, 22 Oct 2023 09:39:28 -0500 Subject: [PATCH 3/6] arrange: no more codes as arguments --- src/dict_utils.jl | 2 +- src/directedness.jl | 20 ++------------------ src/graphs.jl | 16 +++++----------- src/weights.jl | 2 +- test/misc.jl | 1 + 5 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/dict_utils.jl b/src/dict_utils.jl index d4fd2ee..1bea515 100644 --- a/src/dict_utils.jl +++ b/src/dict_utils.jl @@ -114,7 +114,7 @@ function _copy_props!(old_meta_graph::MetaGraph, new_meta_graph::MetaGraph, code code_1, code_2 = Tuple(new_edge) label_1 = vertex_labels[code_1] label_2 = vertex_labels[code_2] - new_meta_graph.edge_data[arrange(new_meta_graph, label_1, label_2, code_1, code_2)] = old_meta_graph.edge_data[arrange( + new_meta_graph.edge_data[arrange(new_meta_graph, label_1, label_2)] = old_meta_graph.edge_data[arrange( old_meta_graph, label_1, label_2 )] end diff --git a/src/directedness.jl b/src/directedness.jl index f4a65f3..8a79ee5 100644 --- a/src/directedness.jl +++ b/src/directedness.jl @@ -17,30 +17,14 @@ to be robust to vertex re-coding, so the labels need to support `<`. """ function arrange end -@traitfn function arrange( - ::MG, label_1, label_2, _drop... -) where {MG <: MetaGraph; IsDirected{MG}} +@traitfn function arrange(::MG, label_1, label_2) where {MG <: MetaGraph; IsDirected{MG}} return label_1, label_2 end -@traitfn function arrange( - ::MG, label_1, label_2, code_1, code_2 -) where {MG <: MetaGraph; !IsDirected{MG}} +@traitfn function arrange(::MG, label_1, label_2) where {MG <: MetaGraph; !IsDirected{MG}} if label_1 < label_2 (label_1, label_2) else (label_2, label_1) end end - -@traitfn function arrange( - meta_graph::MG, label_1, label_2 -) where {MG <: MetaGraph; !IsDirected{MG}} - return arrange( - meta_graph, - label_1, - label_2, - code_for(meta_graph, label_1), - code_for(meta_graph, label_2), - ) -end diff --git a/src/graphs.jl b/src/graphs.jl index 287fe4a..cf88908 100644 --- a/src/graphs.jl +++ b/src/graphs.jl @@ -181,7 +181,7 @@ function Graphs.add_edge!(meta_graph::MetaGraph, label_1, label_2, data) return false end code_1, code_2 = code_for(meta_graph, label_1), code_for(meta_graph, label_2) - label_tup = arrange(meta_graph, label_1, label_2, code_1, code_2) + label_tup = arrange(meta_graph, label_1, label_2) if has_edge(meta_graph.graph, code_1, code_2) return false end @@ -210,16 +210,10 @@ function _rem_vertex!(meta_graph::MetaGraph, label, code) edge_data = meta_graph.edge_data last_vertex_code = nv(meta_graph) for out_neighbor in outneighbors(meta_graph, code) - delete!( - edge_data, - arrange(meta_graph, label, vertex_labels[out_neighbor], code, out_neighbor), - ) + delete!(edge_data, arrange(meta_graph, label, vertex_labels[out_neighbor])) end for in_neighbor in inneighbors(meta_graph, code) - delete!( - edge_data, - arrange(meta_graph, vertex_labels[in_neighbor], label, in_neighbor, code), - ) + delete!(edge_data, arrange(meta_graph, vertex_labels[in_neighbor], label)) end removed = rem_vertex!(meta_graph.graph, code) if removed @@ -248,9 +242,9 @@ end function Graphs.rem_edge!(meta_graph::MetaGraph, code_1::Integer, code_2::Integer) removed = rem_edge!(meta_graph.graph, code_1, code_2) - if removed + if removed # assume that vertex codes were not modified by edge removal label_1, label_2 = label_for(meta_graph, code_1), label_for(meta_graph, code_2) - delete!(meta_graph.edge_data, arrange(meta_graph, label_1, label_2, code_1, code_2)) + delete!(meta_graph.edge_data, arrange(meta_graph, label_1, label_2)) end return removed end diff --git a/src/weights.jl b/src/weights.jl index 7eeb6d3..94bf5f6 100644 --- a/src/weights.jl +++ b/src/weights.jl @@ -65,7 +65,7 @@ function Base.getindex(meta_weights::MetaWeights, code_1::Integer, code_2::Integ labels = meta_graph.vertex_labels weight_function = get_weight_function(meta_graph) arranged_label_1, arranged_label_2 = arrange( - meta_graph, labels[code_1], labels[code_2], code_1, code_2 + meta_graph, labels[code_1], labels[code_2] ) return weight_function(meta_graph[arranged_label_1, arranged_label_2]) else diff --git a/test/misc.jl b/test/misc.jl index dc90194..ebe812f 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -53,6 +53,7 @@ end @test add_vertex!(mg, :A) @test add_vertex!(mg, :B) @test add_edge!(mg, :A, :B) + @test !add_edge!(mg, :A, :C) # long-form mg2 = MetaGraph( From 7225e924b758d21ce977c278f05a0283d11dfb8d Mon Sep 17 00:00:00 2001 From: Cecile Ane Date: Sun, 22 Oct 2023 09:56:03 -0500 Subject: [PATCH 4/6] more testing --- test/misc.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/misc.jl b/test/misc.jl index ebe812f..1697e8d 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -8,9 +8,11 @@ function test_labels_codes(mg::MetaGraph) for (label_1, label_2) in edge_labels(mg) @test has_edge(mg, code_for(mg, label_1), code_for(mg, label_2)) end - for e in edge_labels(mg) # arrange() consistent with mg.edge_data + # below: arrange(edges) ⊆ keys of mg.edge_data. then = because same length + for e in edge_labels(mg) @test_logs mg[e...] # no log, no error end + @test length(keys(mg.edge_data)) == ne(mg) for label_1 in labels(mg) for label_2 in outneighbor_labels(mg, label_1) @test has_edge(mg, code_for(mg, label_1), code_for(mg, label_2)) @@ -43,6 +45,8 @@ end colors_copy = copy(colors) rem_vertex!(colors_copy, 1) test_labels_codes(colors_copy) + @test ne(colors_copy) == 1 + @test colors_copy[:blue, :green] == :cyan end @testset verbose = true "Short-form add_vertex!/add_edge!" begin From b98373a90def47ce664af3710a6c90ef60685b6e Mon Sep 17 00:00:00 2001 From: Cecile Ane Date: Tue, 24 Oct 2023 20:57:23 -0500 Subject: [PATCH 5/6] add_edge! modifies edge data if edge already present --- src/graphs.jl | 4 ++-- test/misc.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/graphs.jl b/src/graphs.jl index cf88908..6d8fac5 100644 --- a/src/graphs.jl +++ b/src/graphs.jl @@ -174,7 +174,7 @@ Add an edge `(label_1, label_2)` to MetaGraph `meta_graph` with metadata `data`. If the `EdgeData` type of `meta_graph` is `Nothing`, `data` can be omitted. Return `true` if the edge has been added, `false` otherwise. -If `(label_1, label_2)` already existed, its data is *not* updated to `data`. +If `(label_1, label_2)` already existed, its data is updated to `data`. """ function Graphs.add_edge!(meta_graph::MetaGraph, label_1, label_2, data) if !haskey(meta_graph, label_1) || !haskey(meta_graph, label_2) @@ -182,10 +182,10 @@ function Graphs.add_edge!(meta_graph::MetaGraph, label_1, label_2, data) end code_1, code_2 = code_for(meta_graph, label_1), code_for(meta_graph, label_2) label_tup = arrange(meta_graph, label_1, label_2) + meta_graph.edge_data[label_tup] = data if has_edge(meta_graph.graph, code_1, code_2) return false end - meta_graph.edge_data[label_tup] = data ne_prev = ne(meta_graph.graph) add_edge!(meta_graph.graph, code_1, code_2) if ne(meta_graph.graph) == ne_prev # undo diff --git a/test/misc.jl b/test/misc.jl index 1697e8d..eb17a8b 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -38,7 +38,7 @@ end # attempt to add an existing edge: non-standard order, different data @test !add_edge!(colors, :green, :blue, :teal) @test length(colors.edge_data) == ne(colors) - @test colors[:blue, :green] == :cyan + @test colors[:blue, :green] == :teal # Delete vertex in a copy and test again @@ -46,7 +46,7 @@ end rem_vertex!(colors_copy, 1) test_labels_codes(colors_copy) @test ne(colors_copy) == 1 - @test colors_copy[:blue, :green] == :cyan + @test colors_copy[:blue, :green] == :teal end @testset verbose = true "Short-form add_vertex!/add_edge!" begin From 197d134cf1a1a7d331a94d30c44d67d2a539aaf0 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:02:28 +0200 Subject: [PATCH 6/6] Update src/graphs.jl --- src/graphs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphs.jl b/src/graphs.jl index 6d8fac5..a4c098b 100644 --- a/src/graphs.jl +++ b/src/graphs.jl @@ -174,7 +174,7 @@ Add an edge `(label_1, label_2)` to MetaGraph `meta_graph` with metadata `data`. If the `EdgeData` type of `meta_graph` is `Nothing`, `data` can be omitted. Return `true` if the edge has been added, `false` otherwise. -If `(label_1, label_2)` already existed, its data is updated to `data`. +If `(label_1, label_2)` already existed, its data is updated to `data` and `false` is returned nonetheless. """ function Graphs.add_edge!(meta_graph::MetaGraph, label_1, label_2, data) if !haskey(meta_graph, label_1) || !haskey(meta_graph, label_2)