-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Location and access for metadata #14
Comments
I would vote for separating the metadata from the edges, and think of the edges as indices for accessing the metadata (and indicating where there is nontrivial/nonzero metadata). Say we have a metagraph wrapping a simple graph like: struct MetaGraph{T<:Integer,D} <: AbstractGraph{T,SimpleEdge{T}}
graph::SimpleGraph{T}
edge_data::MyComplicatedEdgeDataStorage{SimpleEdge{T},D}
end It could require some non-trivial calculation to access edge data from the struct DataEdge{T<:Integer,D} <: AbstractEdge{T,D}
src::T
dst::T
data::D
end
function GraphsBase.out_edges(g::MetaGraph{T}, u) where {T}
return DataEdge{T}(u, v, get_data(g.edge_data, SimpleEdge(u, v)) for v in out_neighbors(g, u))
end This seems like a lot of unnecessary work to do each time you ask for the edges. Also, it seems like another disadvantage is that there would be many edge types which different graphs would have to understand. If I had both a As for the weights, I think they can be thought of as just a special kind of metadata, which again I don't think should be stored with the edges, but instead accessed from the graph using an edge in some generic way ( |
Here's my preferred interface for metadata. First the user defines their own vertex and edge metadata types. These could be any objects with mutable struct VertexData
a::Int
b::String
end
mutable struct EdgeData
c::Float64
d::String
end Then the user can create a graph that uses those metadata types. Suppose we use g = Graph{Char, VertexData, EdgeData}() After adding vertices, edges, and metadata, the metadata can be accessed as follows: # Access vertex metadata:
g[u].a
# Access edge metadata:
g[e].c
g[u, v].d And, if the underlying metadata types are mutable, then the metadata can also be set with the following syntax: # Set vertex metadata:
g[u].a = 42
# Set edge metadata:
g[e].c = 3.14
g[u, v].d = "hello" |
@CameronBieganek agreed, that is very similar to the design of DataGraphs.jl, based on a One unfortunate thing with committing to julia> Dict((1, 2) => "x", (2, 1) => "y")[1, 2]
"x" But that leaves open the question of how to access edge metadata, in I think metadata can mostly be orthogonalized from the basic design and interface (though of course should be anticipated), since most graph algorithms don't require (or just ignore) metadata. The primary examples where metadata is needed that I came across is in operations where graphs are being merged so then you need to know how to merge metadata, but that would need some specialized functionality for different metagraph types anyway since you can't really anticipate how metadata will be stored (similar to how you can't really assume much about the storage structure of |
I don't really see a particular need for requiring Regarding the issue around getdata(g, v) # Get vertex data
getdata(g, u, v) # Get edge data
setdata!(g, v, value) # Set vertex data if mutable
setdata!(g, u, v, value) # Set edge data if mutable and let particular graph subtypes decide how |
That's a very good point. I thought about that a while ago, and then I forgot about it. 😅
You're right, the user should be able to use whatever object they want to store their metadata, including Here's a modified proposal:
Here's a demonstration of what it would look like in practice: # User defined metadata types. User can use any type of object.
mutable struct VertexData
a::Int
b::String
end
mutable struct EdgeData
c::Float64
d::String
end
# ... (Create graph here.)
vd = vertex_data(g)
ed = edge_data(g)
vd['x'] = VertexData(1, "hello")
vd['y'] = VertexData(2, "world")
vd['x'].a = 100
vd['y'].b = "foo"
vd['x'].a # 100
vd['x'].b # "hello"
ed['x', 'y'] = EdgeData(1.2, "asdf")
ed[Edge('x', 'y')].d = "qwer"
ed['x', 'y'].c # 1.2
ed['x', 'y'].d # "qwer" I just wish there was a more compact way to write it than vdata(g)['x'].a = 123 If one uses a dictionary to store their metadata instead of a struct or named tuple, that statement would look something like this: vdata(g)['x'][:a] = 123 There are some oddball ways that we could do it, but they might be a little too odd: g[] # Returns an object that can be indexed to obtain vertex metadata.
g() # Returns an object that can be indexed to obtain edge metadata.
# Examples:
g[]['x'].a = 123
g()['x', 'y'].a = 3.14 or this: g(:vertex) # Returns an object that can be indexed to obtain vertex metadata.
g(:edge) # Returns an object that can be indexed to obtain edge metadata.
# Examples:
g(:vertex)['x'].a = 123
g(:edge)['x', 'y'].a = 3.14 I considered |
That's close to the design I have in getdata(g, v) = vertex_data(g)[v] # Get vertex data
getdata(g, u, v) = edge_data(g)[Edge(u, v)] # Get edge data
setdata!(g, v, value) = (vertex_data(g)[v] = value) # Set vertex data if mutable
setdata!(g, u, v, value) = (edge_data(g)[Edge(u, v)] = value) # Set edge data if mutable I just use In general, it may not be practical or efficient to implement One reason to lean towards just making Of course you could have |
To clarify, by "users" do you mean implementers of new graph types, or users of existing graph types? Users of existing graph types shouldn't be overloading anything. And implementers of new graph types can, as you said, do anything they want with |
Yes, I meant implementers of new graph types. I was just arguing that |
Of course it's partly a matter of taste. I prefer the square bracket syntax sugar for getting and setting values, rather than a function like
I would imagine most graph implementers would use the following pattern: struct VertexData{V}
g::Graph{V}
end
struct EdgeData{V}
g::Graph{V}
end
vertex_data(g::Graph) = VertexData(g)
edge_data(g::Graph) = EdgeData(g)
# Define getindex and setindex! overloads for VertexData and EdgeData. So, Now, if we want to support multigraphs, then getdata(g::AbstractGraph, v)
getdata(g::AbstractGraph, u, v)
getdata(g::AbstractGraph, e::AbstractEdge) But as you pointed out above, this is flawed, because the generic graph interface allows for graphs where the vertex type is a subtype of get_vertex_data(g, v)
set_vertex_data!(g, v, value)
get_edge_data(g, u, v)
get_edge_data(g, e)
set_edge_data!(g, u, v, value)
set_edge_data!(g, e, value) But that starts to get a bit unwieldy. |
I would just disallow |
I think in general it is overly burdensome to require creating objects like |
Sorry, I didn't see that you were referring to multigraphs. Indeed, I hadn't thought about that case, that sounds like an argument for having separate I could imagine having |
Yeah, presumably each edge connecting vertices |
They're not explicitly required, though in practice something like
Perhaps I should have called them |
I understand they could be lightweight, but my primary point still stands that it (in many cases, most that I can think of) requires defining types (even if they are just simply wrapping the graph) that are beyond the original graph type instead of just overloading functions on the original graph type, which is extra burden for developers and I don't think is a great idea to have as part of the required interface (perhaps a standard to go by is: do you think that kind of design would make it into Base Julia?). Anyway, it's mostly splitting hairs and I think we're in general agreement about the design direction. |
For multigraphs, it seems like the interface for get_vertex_data(g, v)
set_vertex_data!(g, v, value)
get_edge_data(g, e)
set_edge_data!(g, e, value) seems to cover that. Probably |
If we remove the various |
I think we'll have to agree to disagree about this point for now, might have to just try them both and see how they work in the wild. |
Should it be in the vertex / edge object itself?
What is the right syntax to access it? Can it be linked with the edge weight?
The text was updated successfully, but these errors were encountered: