Skip to content

Commit 54d40e6

Browse files
committed
Improve constructors and reflect them in tests
1 parent 7bdb117 commit 54d40e6

File tree

6 files changed

+169
-98
lines changed

6 files changed

+169
-98
lines changed

src/dict_utils.jl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,3 @@ function _copy_props!(old_meta_graph::MetaGraph, new_meta_graph::MetaGraph, code
120120
end
121121
return nothing
122122
end
123-
124-
# TODO - It would be nice to be able to apply a function to properties.
125-
# Not sure how this might work, but if the property is a vector,
126-
# a generic way to append to it would be a good thing.

src/metagraph.jl

Lines changed: 96 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ Vertex labels have type `Label`, while vertex (resp. edge, resp. graph) metadata
1616
It is recommended not to set `Label` to an integer type, so as to avoid confusion between vertex labels and vertex codes (which have type `Code<:Integer`).
1717
1818
# Fields
19-
- `graph::Graph`: underlying, data-less graph with vertex indices of type `Code`
19+
- `graph::Graph`: underlying, data-less graph with vertex codes of type `Code`
2020
- `vertex_labels::Dict{Code,Label}`: dictionary mapping vertex codes to vertex labels
21-
- `vertex_properties::Dict{Label,Tuple{Code,VertexData}}`: dictionary mapping vertex labels to vertex codes & data
22-
- `edge_data::Dict{Tuple{Label,Label},EdgeData}`: dictionary mapping edge labels such as `(label_u, label_v)` to edge data
23-
- `graph_data::GraphData`: global data for the graph object
24-
- `weight_function::WeightFunction`: function computing edge weight from edge data, its output must have the same type as `default_weight`
21+
- `vertex_properties::Dict{Label,Tuple{Code,VertexData}}`: dictionary mapping vertex labels to vertex codes & metadata
22+
- `edge_data::Dict{Tuple{Label,Label},EdgeData}`: dictionary mapping edge labels such as `(label_u, label_v)` to edge metadata
23+
- `graph_data::GraphData`: metadata for the graph object as a whole
24+
- `weight_function::WeightFunction`: function computing edge weight from edge metadata, its output must have the same type as `default_weight`
2525
- `default_weight::Weight`: default weight used when an edge doesn't exist
2626
"""
2727
struct MetaGraph{
@@ -46,48 +46,32 @@ end
4646
"""
4747
MetaGraph(
4848
graph,
49-
vertices_description,
50-
edges_description,
49+
label_type,
50+
vertex_data_type=Nothing,
51+
edge_data_type=Nothing,
5152
graph_data=nothing,
5253
weight_function=edge_data -> 1.0,
53-
default_weight=1.0,
54+
default_weight=1.0
5455
)
5556
56-
Construct a non-empty `MetaGraph` based on lists of vertices and edges with their labels and data.
57-
58-
These lists must be constructed as follows:
59-
- `vertices_description` is a vector of pairs `label => data` (the code of a vertex will correspond to its rank in the list)
60-
- `edges_description` is a vector of pairs `(label1, label2) => data`
61-
62-
Furthermore, they must be coherent with the `graph` argument, i.e. describe the same set of vertices and edges.
57+
Construct an empty `MetaGraph` based on an empty `graph`, initializing storage with the given metadata types *as positional arguments*.
6358
"""
6459
function MetaGraph(
6560
graph::AbstractGraph{Code},
66-
vertices_description::Vector{Pair{Label,VertexData}},
67-
edges_description::Vector{Pair{Tuple{Label,Label},EdgeData}},
61+
label_type::Type{Label},
62+
vertex_data_type::Type{VertexData}=Nothing,
63+
edge_data_type::Type{EdgeData}=Nothing,
6864
graph_data=nothing,
6965
weight_function=edge_data -> 1.0,
7066
default_weight=1.0,
7167
) where {Code,Label,VertexData,EdgeData}
72-
# Construct vertex data
73-
@assert length(vertices_description) == nv(graph)
68+
@assert nv(graph) == 0
69+
if Label <: Integer
70+
@warn "Constructing a MetaGraph with integer labels is not advised."
71+
end
7472
vertex_labels = Dict{Code,Label}()
7573
vertex_properties = Dict{Label,Tuple{Code,VertexData}}()
76-
for (code, (label, data)) in enumerate(vertices_description)
77-
vertex_labels[code] = label
78-
vertex_properties[label] = (code, data)
79-
end
80-
# Construct edge data
81-
@assert length(edges_description) == ne(graph)
82-
for ((label_1, label_2), _) in edges_description
83-
code_1 = vertex_properties[label_1][1]
84-
code_2 = vertex_properties[label_2][1]
85-
@assert has_edge(graph, code_1, code_2)
86-
end
8774
edge_data = Dict{Tuple{Label,Label},EdgeData}()
88-
for ((label_1, label_2), data) in edges_description
89-
edge_data[label_1, label_2] = data
90-
end
9175
return MetaGraph(
9276
graph,
9377
vertex_labels,
@@ -102,35 +86,84 @@ end
10286
"""
10387
MetaGraph(
10488
graph;
105-
Label=Symbol,
106-
VertexData=Nothing,
107-
EdgeData=Nothing,
89+
label_type,
90+
vertex_data_type=Nothing,
91+
edge_data_type=Nothing,
10892
graph_data=nothing,
10993
weight_function=edge_data -> 1.0,
11094
default_weight=1.0
11195
)
11296
113-
Construct an empty `MetaGraph` with the given metadata types and weights.
97+
Construct an empty `MetaGraph` based on an empty `graph`, initializing storage with the given metadata types *as keyword arguments*.
11498
115-
!!! danger "Warning"
116-
This constructor is not type-stable, it is only there for convenience.
99+
!!! warning "Warning"
100+
This constructor uses keyword arguments for convenience, which means it is type-unstable.
117101
"""
118102
function MetaGraph(
119-
graph::AbstractGraph{Code};
120-
Label=Symbol,
121-
VertexData=Nothing,
122-
EdgeData=Nothing,
103+
graph;
104+
label_type,
105+
vertex_data_type=Nothing,
106+
edge_data_type=Nothing,
123107
graph_data=nothing,
124108
weight_function=edge_data -> 1.0,
125109
default_weight=1.0,
126-
) where {Code}
127-
@assert nv(graph) == 0
128-
if Label <: Integer
129-
@warn "Constructing a MetaGraph with integer labels is not advised."
130-
end
110+
)
111+
return MetaGraph(
112+
graph,
113+
label_type,
114+
vertex_data_type,
115+
edge_data_type,
116+
graph_data,
117+
weight_function,
118+
default_weight,
119+
)
120+
end
121+
122+
"""
123+
MetaGraph(
124+
graph,
125+
vertices_description,
126+
edges_description,
127+
graph_data=nothing,
128+
weight_function=edge_data -> 1.0,
129+
default_weight=1.0,
130+
)
131+
132+
Construct a non-empty `MetaGraph` based on a non-empty `graph` with specified vertex and edge data.
133+
134+
The data must be given as follows:
135+
- `vertices_description` is a vector of pairs `label => data` (the code of a vertex will correspond to its rank in the list)
136+
- `edges_description` is a vector of pairs `(label1, label2) => data`
137+
138+
Furthermore, these arguments must be coherent with the `graph` argument, i.e. describe the same set of vertices and edges.
139+
"""
140+
function MetaGraph(
141+
graph::AbstractGraph{Code},
142+
vertices_description::Vector{Pair{Label,VertexData}},
143+
edges_description::Vector{Pair{Tuple{Label,Label},EdgeData}},
144+
graph_data=nothing,
145+
weight_function=edge_data -> 1.0,
146+
default_weight=1.0,
147+
) where {Code,Label,VertexData,EdgeData}
148+
# Construct vertex data
149+
@assert length(vertices_description) == nv(graph)
131150
vertex_labels = Dict{Code,Label}()
132151
vertex_properties = Dict{Label,Tuple{Code,VertexData}}()
152+
for (code, (label, data)) in enumerate(vertices_description)
153+
vertex_labels[code] = label
154+
vertex_properties[label] = (code, data)
155+
end
156+
# Construct edge data
157+
@assert length(edges_description) == ne(graph)
158+
for ((label_1, label_2), _) in edges_description
159+
code_1 = vertex_properties[label_1][1]
160+
code_2 = vertex_properties[label_2][1]
161+
@assert has_edge(graph, code_1, code_2)
162+
end
133163
edge_data = Dict{Tuple{Label,Label},EdgeData}()
164+
for ((label_1, label_2), data) in edges_description
165+
edge_data[label_1, label_2] = data
166+
end
134167
return MetaGraph(
135168
graph,
136169
vertex_labels,
@@ -151,3 +184,18 @@ function Base.show(
151184
)
152185
return nothing
153186
end
187+
188+
function Base.:(==)(meta_graph_1::MetaGraph, meta_graph_2::MetaGraph)
189+
return (
190+
(meta_graph_1.graph == meta_graph_2.graph) &&
191+
(meta_graph_1.vertex_labels == meta_graph_2.vertex_labels) &&
192+
(meta_graph_1.vertex_properties == meta_graph_2.vertex_properties) &&
193+
(meta_graph_1.edge_data == meta_graph_2.edge_data) &&
194+
(meta_graph_1.graph_data == meta_graph_2.graph_data) &&
195+
all(
196+
meta_graph_1.weight_function(ed) == meta_graph_2.weight_function(ed) for
197+
ed in meta_graph_1.edge_data
198+
) &&
199+
(meta_graph_1.default_weight == meta_graph_2.default_weight)
200+
)
201+
end

test/labels_codes_directedness.jl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# undirected MetaGraph
22
colors = MetaGraph(
3-
Graph(); VertexData=String, EdgeData=Symbol, graph_data="graph_of_colors"
3+
Graph();
4+
label_type=Symbol,
5+
vertex_data_type=String,
6+
edge_data_type=Symbol,
7+
graph_data="graph_of_colors",
48
)
59
@test istrait(IsDirected{typeof(colors)}) == is_directed(colors) == false
610
labels = [:red, :yellow, :blue]
@@ -22,7 +26,11 @@ end
2226

2327
# directed MetaGraph
2428
dcolors = MetaGraph(
25-
SimpleDiGraph(); VertexData=String, EdgeData=Symbol, graph_data="graph_of_colors"
29+
SimpleDiGraph();
30+
label_type=Symbol,
31+
vertex_data_type=String,
32+
edge_data_type=Symbol,
33+
graph_data="graph_of_colors",
2634
)
2735
@test istrait(IsDirected{typeof(dcolors)}) == is_directed(dcolors) == true
2836
labels = [:red, :yellow, :blue]

test/tutorial/1_basics.jl

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,31 @@ using Test #src
66

77
# ## Creating a `MetaGraph`
88

9-
# We provide a default constructor which looks as follows:
9+
# We provide a convenience constructor for creating empty graphs, which looks as follows:
1010

1111
colors = MetaGraph(
1212
Graph(); # underlying graph structure
13-
Label=Symbol, # color name
14-
VertexData=NTuple{3,Int}, # RGB code
15-
EdgeData=String, # result of the addition between two colors
13+
label_type=Symbol, # color name
14+
vertex_data_type=NTuple{3,Int}, # RGB code
15+
edge_data_type=String, # result of the addition between two colors
1616
graph_data="additive colors", # tag for the whole graph
1717
)
1818

19-
# The `Label` type defines how vertices will be referred to, it can be anything but an integer type (to avoid confusion with codes, see below). The `VertexData` and `EdgeData` 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.
19+
# The `label_type` argument defines how vertices will be referred to, it can be anything but an integer type (to avoid confusion with codes, see below). 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.
20+
21+
# However, since this constructor receives types as keyword arguments, it is type-unstable. Casual users may not care, but if your goal is performance, you should use the following syntax instead, which relies on positional arguments.
22+
23+
colors_stable = MetaGraph(Graph(), Symbol, NTuple{3,Int}, String, "additive colors")
24+
25+
@test @inferred MetaGraph( #src
26+
Graph(), #src
27+
Symbol, #src
28+
NTuple{3,Int}, #src
29+
String, #src
30+
"additive colors", #src
31+
) == colors_stable #src
32+
33+
# Be careful with the order!
2034

2135
# ## Modifying the graph
2236

@@ -47,13 +61,13 @@ colors[:green, :blue] = "cyan";
4761
# To check the presence of a vertex or edge, use `haskey`:
4862

4963
haskey(colors, :red)
50-
@test haskey(colors, :red) #src
64+
@test @inferred haskey(colors, :red) #src
5165
#-
5266
haskey(colors, :black)
5367
@test !haskey(colors, :black) #src
5468
#-
5569
haskey(colors, :red, :green) && haskey(colors, :green, :red)
56-
@test haskey(colors, :red, :green) && haskey(colors, :green, :red) #src
70+
@test (@inferred haskey(colors, :red, :green)) && haskey(colors, :green, :red) #src
5771
#-
5872
!haskey(colors, :red, :black)
5973
@test !haskey(colors, :red, :black) #src
@@ -63,28 +77,28 @@ haskey(colors, :red, :green) && haskey(colors, :green, :red)
6377
# All kinds of metadata can be accessed with `getindex`:
6478

6579
colors[]
66-
@test colors[] == "additive colors" #src
80+
@test @inferred colors[] == "additive colors" #src
6781
#-
6882
colors[:blue]
69-
@test colors[:blue] == (0, 0, 255) #src
83+
@test @inferred colors[:blue] == (0, 0, 255) #src
7084
#-
7185
colors[:green, :blue]
72-
@test colors[:green, :blue] == "cyan" #src
86+
@test @inferred colors[:green, :blue] == "cyan" #src
7387

7488
# ## Using vertex codes
7589

7690
# In the absence of removal, vertex codes correspond to order of insertion in the underlying graph. They are the ones used by most algorithms in the Graphs.jl ecosystem.
7791

7892
code_for(colors, :red)
79-
@test code_for(colors, :red) == 1 #src
93+
@test @inferred code_for(colors, :red) == 1 #src
8094
#-
8195
code_for(colors, :blue)
8296
@test code_for(colors, :blue) == 3 #src
8397

8498
# You can retrieve the associated labels as follows:
8599

86100
label_for(colors, 1)
87-
@test label_for(colors, 1) == :red #src
101+
@test @inferred label_for(colors, 1) == :red #src
88102
#-
89103
label_for(colors, 3)
90104
@test label_for(colors, 3) == :blue #src
@@ -93,32 +107,35 @@ label_for(colors, 3)
93107

94108
# The most simple way to add edge weights is to speficy a default weight for all of them.
95109

96-
weighted_default = MetaGraph(Graph(); default_weight=2);
110+
weighted_default = MetaGraph(Graph(); label_type=Symbol, default_weight=2);
97111
#-
98112
default_weight(weighted_default)
99-
@test default_weight(weighted_default) == 2 #src
113+
@test @inferred default_weight(weighted_default) == 2 #src
100114
#-
101115
weighttype(weighted_default)
102-
@test weighttype(weighted_default) == Int #src
116+
@test @inferred weighttype(weighted_default) == Int #src
103117

104118
# You can use the `weight_function` keyword to specify a function which will transform edge metadata into a weight. This weight must always be the same type as the `default_weight`.
105119

106-
weighted = MetaGraph(Graph(); EdgeData=Float64, weight_function=ed -> ed^2);
120+
weighted = MetaGraph(
121+
Graph(); label_type=Symbol, edge_data_type=Float64, weight_function=ed -> ed^2
122+
);
107123

108124
weighted[:alice] = nothing;
109125
weighted[:bob] = nothing;
110126
weighted[:alice, :bob] = 2.0;
111127
#-
112128
weight_matrix = Graphs.weights(weighted)
129+
@test @inferred Graphs.weights(weighted) == weight_matrix
113130
#-
114131
size(weight_matrix)
115132
@test size(weight_matrix) == (2, 2) #src
116133
#-
117134
weight_matrix[1, 2]
118-
@test weight_matrix[1, 2] 4.0 #src
135+
@test @inferred weight_matrix[1, 2] 4.0 #src
119136
#-
120137
wf = get_weight_function(weighted)
121138
wf(3)
122-
@test wf(3) == 9 #src
139+
@test @inferred wf(3) == 9 #src
123140

124141
# You can then use all functions from Graphs.jl that require weighted graphs (see the rest of the tutorial).

0 commit comments

Comments
 (0)