Usage
Following a walkthrough of the package and the features. An AttributeGraph
is quite straightforwardingly defined by 4 fields:
- the underlying wrapped graph
- the vertex attribute data structure
- the edge attribute data structure
- the graph attribute data structure
That is because we tried to manufacture the least speculative way to define a such. As this might not be able to do some easy out-of-the-box operations we also proposed a more Opinionated approach
Opinionated approach
The default operation of OAttributeGraphs
is an opinionated one. By calling OAttributeGraph()
, you are already within the realm of our preferences.
julia> using AttributeGraphs, Graphs
julia> OAttributeGraph() |> typeof
AttributeGraph{Int64, SimpleGraph{Int64}, Vector{Missing}, Dict{Tuple{Int64, Int64, Int64}, Missing}, Missing}
This approach treats graph data as following:
- The vertex attributes data structure is a vector.
The index of the vector corresponds to the index of the vertex. This way we want to be closer to the Graphs.jl implementation.
- The edge attributes data structure is a
Dict{Tuple{Int, Int, Int}}
.
Non surpisingly, each edge attribute is indexed by the source and destination node. The third integer is the multiplicity for MultiGraphs and if non given, 1
is assumed.
- The graph attributes data structure is non existent. The user type is directly what it is.
However some functionality is provided from {add,rem,has,get}graphattr[!] for AbstractDict
. For trivial situation you should directly use graph_attr
.
Following we define a AttributeGraph
with vertex and edge attributes of String
and graph attributes of Dict{Symbol, String}
julia> oag = OAttributeGraph(;vertex_type=String, edge_type=String, graph_type=Dict{Symbol, String})
{0, 0} undirected attribute Int64 graph
You will not be surpised to see that probably all of the obvious operations are supported.
For example starting with the graph attributes
julia> addgraphattr!(oag, :ena, "ena")
"ena"
julia> addgraphattr!(oag, :dio, "dio")
"dio"
julia> graph_attr(oag)
Dict{Symbol, String} with 2 entries:
:ena => "ena"
:dio => "dio"
Now let's add some vertices and edges. Note we use the addvertex!
, addedge!
and friends instead of add_vertex!
, add_edge!
and friends to be 100% consistent. The second ones will also work, but you sooner or later you will reach a corrupted state.
julia> foreach(_ -> addvertex!(oag), 1:10) # add 10 nodes
julia> foreach(x -> addedge!(oag,x[1],x[2]), [(1,2), (1,3), (3,4), (1,5), (7,8)]) # add some edges
addvertex!
immeadiately adds missing
to the vertex attribute:
julia> vertex_attr(oag)
10-element Vector{Union{Missing, String}}:
missing
missing
missing
missing
missing
missing
missing
missing
missing
missing
julia> addvertexattr!(oag, 4, "customattr4")
"customattr4"
julia> vertex_attr(oag)
10-element Vector{Union{Missing, String}}:
missing
missing
missing
"customattr4"
missing
missing
missing
missing
missing
missing
Similar for edge attributes:
julia> addedgeattr!(oag, 1, 2, "data_for_1-2")
"data_for_1-2"
julia> addedgeattr!(oag, 3, 4, "data_for_3-4")
"data_for_3-4"
julia> edge_attr(oag)
Dict{Tuple{Int64, Int64, Int64}, String} with 2 entries:
(1, 2, 1) => "data_for_1-2"
(3, 4, 1) => "data_for_3-4"
Deleting a node will automatically update the indices and attributes
julia> remvertex!(oag, 2)
true
julia> edge_attr(oag)
Dict{Tuple{Int64, Int64, Int64}, String} with 1 entry:
(2, 3, 1) => "data_for_3-4"
General approach
Basically, AttributeGraph
is a parametric type that can be anything. So you can customize it to do whatever you want. You might say that the lack of features is the basic feature here.
The basic use case for this is that you might often have some data data
accompanying your graph graph
. You don't want to always pass around a tuple of (graph, data)
. Instead, you can simply use this package to load data
in a AttributeGraph
and pass it around more compactly.
An outrageous example follows
julia> using DataStructures, Random, Test
julia> Random.seed!(0); # for reproducibility
julia> gag = AttributeGraph(SimpleGraph(), (v) -> v+randn(), DefaultDict{Tuple{Int, Int, String},Float64}(10.0), missing)
{0, 0} undirected attribute Int64 graph
Here the vertex attributes are given by a normally distributed random function with mean the vertex index. The edge attributes are a default dictionary indexed by a tuple {Int, Int, String}
The graph attributes are non-existant so we pass in missing
.
The following function will return our initializations
julia> vertex_attr(gag); # returns a function
julia> edge_attr(gag)
DataStructures.DefaultDict{Tuple{Int64, Int64, String}, Float64, Float64}()
julia> graph_attr(gag)
missing
We can continue on constructing our graph
julia> add_vertices!(gag, 10)
10
julia> foreach(v -> add_edge!(gag, v, v+1), 1:nv(gag))
julia> gag
{10, 9} undirected attribute Int64 graph
You can query right away the vertex stochastic attributes
julia> [vertex_attr(gag)(v) for v in vertices(gag)]
10-element Vector{Float64}:
1.9429705334461191
2.1339227576531843
4.5250689085124804
4.123901231205597
3.794227715740064
6.311817175360248
6.76535873503874
6.912647706289468
9.462310680431376
9.919406913366794
I hear you saying: "But I can even do vertex_attr(gag)(1235)
" and it will still return a value even though the vertex doesn't exist. True. You can move on to customize your approach if you want that to fail. Eg:
julia> myvertexattr(ag::AttributeGraph, x) = has_vertex(ag, x) ? vertex_attr(gag)(x) : error("no attribute")
myvertexattr (generic function with 1 method)
julia> myvertexattr(gag, 4)
3.187569312095576
julia> @test_throws Exception myvertexattr(gag, 123) # now it throws an error
Test Passed
Thrown: ErrorException
Similarly you will need to manually fill up the attributes for the graph edge, e.g.:
julia> let e = first(edges(gag))
edge_attr(gag)[src(e), dst(e), "class1"] = 100
edge_attr(gag)[src(e), dst(e), "class2"] = 200
end;
julia> edge_attr(gag)
DefaultDict{Tuple{Int64, Int64, String}, Float64, Float64} with 2 entries:
(1, 2, "class1") => 100.0
(1, 2, "class2") => 200.0
Since we decided to use a DefaultDict
though you can still query other edges
julia> edge_attr(gag)[1, 4, ""]
10.0
The general approach is mostly useful for customizable or quick and dirty situations.