|
| 1 | +# # Type stability |
| 2 | + |
| 3 | +using Graphs |
| 4 | +using MetaGraphs: MetaGraphs |
| 5 | +using MetaGraphsNext |
| 6 | +using Test #src |
| 7 | + |
| 8 | +# ## Constructor and access |
| 9 | + |
| 10 | +# In the previous examples, we used a `MetaGraph` constructor which receives type parameters as keyword arguments. This was done for ease of exposition, but it may impede type inference, and hence reduce performance. |
| 11 | + |
| 12 | +colors = MetaGraph( |
| 13 | + Graph(); # underlying graph structure |
| 14 | + label_type=Symbol, # color name |
| 15 | + vertex_data_type=NTuple{3,Int}, # RGB code |
| 16 | + edge_data_type=Symbol, # result of the addition between two colors |
| 17 | + graph_data="additive colors", # tag for the whole graph |
| 18 | +) |
| 19 | + |
| 20 | +@test_throws ErrorException (@inferred MetaGraph( #src |
| 21 | + Graph(); #src |
| 22 | + label_type=Symbol, #src |
| 23 | + vertex_data_type=NTuple{3,Int}, #src |
| 24 | + edge_data_type=Symbol, #src |
| 25 | + graph_data="additive colors", #src |
| 26 | +)) #src |
| 27 | + |
| 28 | +# While casual users probably won't care, if your goal is performance, you might need to proceed differently. |
| 29 | + |
| 30 | +# Option 1: wrap the constructor in a helper function to trigger constant propagation. |
| 31 | + |
| 32 | +function colors_constructor() |
| 33 | + return MetaGraph( |
| 34 | + Graph(); |
| 35 | + label_type=Symbol, |
| 36 | + vertex_data_type=NTuple{3,Int}, |
| 37 | + edge_data_type=Symbol, |
| 38 | + graph_data="additive colors", |
| 39 | + ) |
| 40 | +end |
| 41 | + |
| 42 | +colors_constructor() |
| 43 | + |
| 44 | +@test @inferred colors_constructor() == colors #src |
| 45 | + |
| 46 | +# Option 2: switch to another constructor that uses positional arguments (be careful with the order!) |
| 47 | + |
| 48 | +MetaGraph(Graph(), Symbol, NTuple{3,Int}, Symbol, "additive colors") |
| 49 | + |
| 50 | +@test (@inferred MetaGraph( #src |
| 51 | + Graph(), #src |
| 52 | + Symbol, #src |
| 53 | + NTuple{3,Int}, #src |
| 54 | + Symbol, #src |
| 55 | + "additive colors", #src |
| 56 | +) == colors) #src |
| 57 | + |
| 58 | +# Option 3: use the constructor for a non-empty graph instead. |
| 59 | + |
| 60 | +vertices_description = [:red => (255, 0, 0), :green => (0, 255, 0), :blue => (0, 0, 255)] |
| 61 | +edges_description = [ |
| 62 | + (:red, :green) => :yellow, (:red, :blue) => :magenta, (:green, :blue) => :cyan |
| 63 | +] |
| 64 | +MetaGraph(cycle_graph(3), vertices_description, edges_description, "additive colors") |
| 65 | + |
| 66 | +@test (@inferred zero( |
| 67 | + MetaGraph( #src |
| 68 | + cycle_graph(3), #src |
| 69 | + vertices_description, #src |
| 70 | + edges_description, #src |
| 71 | + "additive colors", #src |
| 72 | + ) |
| 73 | +) == colors) #src |
| 74 | + |
| 75 | +# Once Julia can infer the full type of the `MetaGraph`, accessing vertex and edge metadata also becomes type-stable. |
| 76 | + |
| 77 | +# ## Comparison with MetaGraphs.jl |
| 78 | + |
| 79 | +# In the older package [MetaGraphs.jl](https://github.com/JuliaGraphs/MetaGraphs.jl) that we used as inspiration, data types are not specified in the graph structure. Their choice allows more flexibility and an arbitrary number of attributes which the user does not need to anticipate at construction. |
| 80 | + |
| 81 | +colors_unstable = MetaGraphs.MetaGraph(cycle_graph(3)) |
| 82 | + |
| 83 | +# Here is how one would add data and labels to `colors_unstable`. |
| 84 | + |
| 85 | +MetaGraphs.set_indexing_prop!(colors_unstable, :label) |
| 86 | + |
| 87 | +MetaGraphs.set_prop!(colors_unstable, :graph_tag, "additive colors") |
| 88 | + |
| 89 | +MetaGraphs.set_props!(colors_unstable, 1, Dict(:label => :red, :rgb_code => (255, 0, 0))) |
| 90 | +MetaGraphs.set_props!(colors_unstable, 2, Dict(:label => :green, :rgb_code => (0, 255, 0))) |
| 91 | +MetaGraphs.set_props!(colors_unstable, 3, Dict(:label => :blue, :rgb_code => (0, 0, 255))) |
| 92 | + |
| 93 | +MetaGraphs.set_prop!(colors_unstable, 1, 2, :addition_result, :yellow) |
| 94 | +MetaGraphs.set_prop!(colors_unstable, 1, 3, :addition_result, :magenta) |
| 95 | +MetaGraphs.set_prop!(colors_unstable, 2, 3, :addition_result, :cyan); |
| 96 | + |
| 97 | +# One can retrieve the vertex index (which we called code) using any indexing property. |
| 98 | + |
| 99 | +colors_unstable[:green, :label] |
| 100 | + |
| 101 | +# Then we can access vertex properties... |
| 102 | + |
| 103 | +MetaGraphs.get_prop(colors_unstable, 2, :rgb_code) |
| 104 | + |
| 105 | +@test_throws ErrorException (@inferred MetaGraphs.get_prop( #src |
| 106 | + colors_unstable, #src |
| 107 | + 2, #src |
| 108 | + :rgb_code, #src |
| 109 | +)) #src |
| 110 | + |
| 111 | +#- |
| 112 | + |
| 113 | +MetaGraphs.props(colors_unstable, 2) |
| 114 | + |
| 115 | +# ... and edge properties. |
| 116 | + |
| 117 | +MetaGraphs.get_prop(colors_unstable, 2, 3, :addition_result) |
| 118 | + |
| 119 | +@test_throws ErrorException (@inferred MetaGraphs.get_prop( #src |
| 120 | + colors_unstable, #src |
| 121 | + 2, #src |
| 122 | + 3, #src |
| 123 | + :addition_result, #src |
| 124 | +)) #src |
| 125 | + |
| 126 | +#- |
| 127 | + |
| 128 | +MetaGraphs.props(colors_unstable, 2, 3) |
| 129 | + |
| 130 | +# The fact that the outputs of these calls to `props` are of type `Dict{Symbol, Any}` is at the root of the problem. It means that if we use their values in any subsequent algorithms, we introduce type instability in our code (due to `Any`). MetaGraphsNext.jl overcomes this obstacle thanks to a more precise storage method. |
0 commit comments