-
Notifications
You must be signed in to change notification settings - Fork 527
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
graph/encoding/dot: improve documentation and add more examples #910
Comments
Thank you for the report. Yes, the encoding/dot documentation could be improved here, and some examples added. The graphs in simple do not provide the hooks necessary for dot to annotate them with attributes or DOT IDs. These hooks must be added to your graph. You can either implement the entire graph with them, or use embedding to achieve the same thing. There are many example in the test code that show how this works, but here is a working example that does what your example tries to achieve.
The new types, The reason we do it this way is to allow flexibility in behaviour of the encoding/decoding and to minimise the complexity of the graph types we provide. |
Thank you for the very useful reply and example.
I assumed, while looking at the tests, that there are many more features which are actually supported than it might seem at first sight. However, from my little to no knowledge about graph theory, it is hard to grasp the relevant pieces.
This was precisely what I was missing. I had found https://godoc.org/gonum.org/v1/gonum/graph/encoding, but I did not know how to mix those
I understand it, and I find it to be very elegant. However, this style of having lots of very small and simple types/funcs is a nightmare for the newcomer. This is because some good understanding of how all the pieces are combined is required in order to compose a rather minimal working example. Anyway, I am aware that I am not exactly the target audience, and the style fits for users targeting heavier workloads. I understand that it is not easy to oversimplify things when you are focused on very low-level implementation details. So, in case it might be useful, I'd suggest adding a few short posts (or a notebook) about https://github.com/kortschak/graphprac ( I'm closing this because the issue is solved. I'm heading to extending the example 😄 |
Reopening to improve docs. |
Note that graphprac is purely t class teaching notebook and not how I would do any of this in the real world. I have been working through the Gonum User Survey responses and one of the main things to take from it is the need for more documentation, including examples and tutorials. This will be an are we will work on. (This is why I have re-opened this). |
I'm sorry for closing it before. I did not realize that you had changed the title.
My use case is quite simple, so I'm ok with traversing and processing the graph naively. For further insight, I have a dependency graph of some tasks: multiple sources need to be manipulated and mixed in order to generate targets. I want to retrieve a subgraph for each target (node with no output edges, only inputs). Hence, the relevant feature is to preserve labels (which define the names of the taks). I don't need any complex analysis. For now, I could successfully: // Read the graph
g := Unmarshal([]byte(src))
// Get a list of targets
tgts := getTargets(g)
// Induce a subgraph for each target
s := induce(g, tgts)
// Marshal and print each of the induced subgraphs:
for _, j := range s {
fmt.Println("\nSUBGRAPH:")
printGraph(j)
b, e := dot.Marshal(j, "", "", "")
if e == nil {
fmt.Println(string(b))
}
} where func getTargets(g *simple.DirectedGraph) []graph.Node {
ns := make([]graph.Node, 0)
for _, n := range graph.NodesOf(g.Nodes()) {
if len(graph.NodesOf(g.From(n.ID()))) == 0 {
ns = append(ns, n)
}
}
return ns
}
func induce(g *simple.DirectedGraph, ns []graph.Node) []*simple.DirectedGraph {
addNode := func(s *simple.DirectedGraph, n graph.Node) {
if s.Node(n.ID()) == nil {
s.AddNode(n)
}
}
var walk func(g, s *simple.DirectedGraph, n graph.Node) []graph.Node
walk = func(g, s *simple.DirectedGraph, n graph.Node) []graph.Node {
addNode(s, n)
if l := graph.NodesOf(g.From(n.ID())); len(l) > 0 {
for _, x := range l {
addNode(s, x)
s.SetEdge(g.NewEdge(n, x))
walk(g, s, x)
}
}
return nil
}
ReverseEdges(g)
o := make([]*simple.DirectedGraph, 0)
for _, n := range ns {
s := simple.NewDirectedGraph()
walk(g, s, n)
ReverseEdges(s)
o = append(o, s)
}
ReverseEdges(g)
return o
}
func ReverseEdges(g *simple.DirectedGraph) {
for _, e := range graph.EdgesOf(g.Edges()) {
// FIXME The attributes of the edge are not honored because the generic
// implementations of ReversedEdge and/or SetEdge do not include them.
g.SetEdge(e.ReversedEdge())
g.RemoveEdge(e.From().ID(), e.To().ID())
}
}
func printGraph(g *simple.DirectedGraph) {
for _, n := range graph.NodesOf(g.Nodes()) {
fmt.Printf("%+v\n", n)
}
for _, e := range graph.EdgesOf(g.Edges()) {
fmt.Printf("%+v\n", e)
}
} The 'induction' works, subgraphs are properly generated and the labels are retained in the nodes. However, on the one hand, labels on the edges are lost, as commented in the code above. On the other hand, labels on the nodes are lost when data is marshaled. For example: src := `strict digraph {
// Node definitions.
A [label="foo 2"];
B [label="bar 2"];
// Edge definitions.
A -> B [label="baz 2"];
A -> C -> E -> F;
C -> D;
B -> F;
}` SUBGRAPH:
&{Node:4 dotID:F attrs:map[]}
&{Node:1 dotID:B attrs:map[label:bar 2]}
&{Node:0 dotID:A attrs:map[label:foo 2]}
&{Node:3 dotID:E attrs:map[]}
&{Node:2 dotID:C attrs:map[]}
{F:0xc0001307b0 T:0xc000130870}
{F:0xc0001306f0 T:0xc0001307b0}
{F:0xc000130600 T:0xc000130870}
{F:0xc000130540 T:0xc000130600}
{F:0xc000130540 T:0xc0001306f0}
strict digraph {
// Node definitions.
0;
1;
2;
3;
4;
// Edge definitions.
0 -> 1;
0 -> 2;
1 -> 4;
2 -> 3;
3 -> 4;
}
SUBGRAPH:
&{Node:5 dotID:D attrs:map[]}
&{Node:2 dotID:C attrs:map[]}
&{Node:0 dotID:A attrs:map[label:foo 2]}
{F:0xc000130540 T:0xc0001306f0}
{F:0xc0001306f0 T:0xc0001309c0}
strict digraph {
// Node definitions.
0;
2;
5;
// Edge definitions.
0 -> 2;
2 -> 5;
} I'd like to ask three questions:
|
Answering your questions here, but if there are more, please take them to gonum-dev.
|
What are you trying to do?
I am reading a simple directed graph with
graph/encoding/dot
:Now, I would like to build a
map[string]int
with the labels of the Nodes, i.e.:What did you try?
Which generates:
&{map[1:1 0:0] map[0:map[1:0xc0000468e0] 1:map[]] map[0:map[] 1:map[0:0xc0000468e0]] {1 map[0:{} 1:{}] map[]}} 1 0
How does Gonum not allow you to achieve your goal?
I'd expect attributes, such as the labels, to be accesible from
type Node
.What version of Go and Gonum are you using?
I tried docker image
golang:alpine
and natively on a Win10 host:Is this feature absent from the current master?
I don't know. I'd expect it to be supported, but I cannot find any reference or example about how to do it.
Are you able to help contribute the feature?
I think that this is either lack of knowledge on my side, or lack of documentation.
The text was updated successfully, but these errors were encountered: