Skip to content
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

Added functionality to save graphs in DOT format #23

Merged
merged 8 commits into from
May 10, 2020

Conversation

abhinavmehndiratta
Copy link
Contributor

@abhinavmehndiratta abhinavmehndiratta commented Dec 16, 2018

#19

julia> using ParserCombinator,LightGraphs,GraphIO

julia> x = SimpleGraph(5,10)
{5, 10} undirected simple Int64 graph

julia> y = SimpleDiGraph(5,10)
{5, 10} directed simple Int64 graph

julia> savegraph("/home/abhinav/undirected.dot",x,"undg",DOTFormat())
┌ Warning: `GraphIO.DOTFormat`  has been moved to submodule `GraphIO.DOT` and needs `ParserCombinator.jl` to be imported first. I.e. use
│     using ParserCombinator
│     GraphIO.DOT.DOTFormat()
│   caller = top-level scope at none:0
└ @ Core none:0
1

julia> savegraph("/home/abhinav/directed.dot",y,"dg",DOTFormat())
┌ Warning: `GraphIO.DOTFormat`  has been moved to submodule `GraphIO.DOT` and needs `ParserCombinator.jl` to be imported first. I.e. use
│     using ParserCombinator
│     GraphIO.DOT.DOTFormat()
│   caller = top-level scope at none:0
└ @ Core none:0
1

julia> p = loadgraph("/home/abhinav/undirected.dot","undg",DOTFormat())
┌ Warning: `GraphIO.DOTFormat`  has been moved to submodule `GraphIO.DOT` and needs `ParserCombinator.jl` to be imported first. I.e. use
│     using ParserCombinator
│     GraphIO.DOT.DOTFormat()
│   caller = top-level scope at none:0
└ @ Core none:0
{5, 10} undirected simple Int64 graph

julia> q = loadgraph("/home/abhinav/directed.dot","dg",DOTFormat())
┌ Warning: `GraphIO.DOTFormat`  has been moved to submodule `GraphIO.DOT` and needs `ParserCombinator.jl` to be imported first. I.e. use
│     using ParserCombinator
│     GraphIO.DOT.DOTFormat()
│   caller = top-level scope at none:0
└ @ Core none:0
{5, 10} directed simple Int64 graph

julia> p == x
true

julia> q == y
true

@codecov
Copy link

codecov bot commented Dec 16, 2018

Codecov Report

Merging #23 into master will decrease coverage by 15.99%.
The diff coverage is 10.34%.

Impacted file tree graph

@@           Coverage Diff            @@
##           master      #23    +/-   ##
========================================
- Coverage   99.67%   83.67%   -16%     
========================================
  Files          10       11     +1     
  Lines         304      435   +131     
========================================
+ Hits          303      364    +61     
- Misses          1       71    +70
Impacted Files Coverage Δ
src/DOT/Dot.jl 55.17% <10.34%> (-44.83%) ⬇️
src/GraphIO.jl 44.44% <0%> (-55.56%) ⬇️
src/LGCompressed/LGCompressed.jl 55.55% <0%> (-33.34%) ⬇️
src/NET/Net.jl 88.88% <0%> (-11.12%) ⬇️
src/Edgelist/Edgelist.jl 91.66% <0%> (-8.34%) ⬇️
src/CDF/Cdf.jl 93.54% <0%> (-6.46%) ⬇️
src/Graph6/Graph6.jl 94.18% <0%> (-5.82%) ⬇️
src/GEXF/Gexf.jl 96.42% <0%> (-3.58%) ⬇️
src/GraphML/GraphML.jl 97.4% <0%> (-2.6%) ⬇️
src/GML/Gml.jl 100% <0%> (ø) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5e6207f...d9d86b8. Read the comment docs.

@codecov
Copy link

codecov bot commented Dec 16, 2018

Codecov Report

Merging #23 into master will decrease coverage by 8.05%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #23      +/-   ##
==========================================
- Coverage   99.67%   91.62%   -8.06%     
==========================================
  Files          10       11       +1     
  Lines         304      358      +54     
==========================================
+ Hits          303      328      +25     
- Misses          1       30      +29
Impacted Files Coverage Δ
src/DOT/Dot.jl 100% <100%> (ø) ⬆️
src/LGCompressed/LGCompressed.jl 44.44% <0%> (-44.45%) ⬇️
src/GraphIO.jl 75% <0%> (-25%) ⬇️
src/Edgelist/Edgelist.jl 94.73% <0%> (-5.27%) ⬇️
src/CDF/Cdf.jl 95.65% <0%> (-4.35%) ⬇️
src/Graph6/Graph6.jl 98.43% <0%> (-1.57%) ⬇️
src/GraphML/GraphML.jl 100% <0%> (ø) ⬆️
src/deprecations.jl 0% <0%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5e6207f...1165562. Read the comment docs.

Copy link
Member

@simonschoelly simonschoelly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thing we get this functionality. As you see, you are getting some warnings. This is, because DOTFormat has been moved to the submodule GraphIO.DOT.DOTFormat. So you either have to write DOT.DOTFormat. or using GraphIO.DOT: DOTFormat.

I wonder if this would be a good opportunity to add the ability to add the ability to attach labels to vertices and edges, as this is not too difficult with this format. We could do this with optional keyword arguments. What do you think @sbromberger ?

src/DOT/Dot.jl Outdated
continue
end
s = string(n)
edg = edg * "\n\t" * string(u) * " -> {" * s[2:length(s)-1] * "}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats a clever trick, but the output of string(n) changes slightly in the future, this might break. consider doing something like join(map(string, outneighbors(g, u)), ", ") instead.

src/DOT/Dot.jl Outdated
edg = ""
if isdir
for u in LightGraphs.vertices(g)
n = LightGraphs.outneighbors(g, u)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n is not a good name here, usually people assume that this would be an integer.

src/DOT/Dot.jl Outdated
for e in LightGraphs.edges(g)
source = string(LightGraphs.src(e))
dest = string(LightGraphs.dst(e))
edg = edg * "\n\t" * source * " -- " * dest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that you use * to build a large string. This creates a lot of unnecessary allocations and will become really slow when the graph gets big. Its better to work with IOBuffers or write each line directly to io as soon as you have finished creating it. In fact you can use println(io, "some string"), so you will not have to insert \n manually.

@abhinavmehndiratta
Copy link
Contributor Author

using ParserCombinator,LightGraphs,GraphIO
x = SimpleGraph(5,10)
y = SimpleDiGraph(5,2)
savegraph("/home/abhinav/undirected.dot",x,"undg",GraphIO.DOT.DOTFormat())
savegraph("/home/abhinav/directed.dot",y,"dg",GraphIO.DOT.DOTFormat())
p = loadgraph("/home/abhinav/undirected.dot","undg",GraphIO.DOT.DOTFormat())
q = loadgraph("/home/abhinav/directed.dot","dg",GraphIO.DOT.DOTFormat())
println(p == x)
println(q == y)

Output -

true
true

@sbromberger
Copy link
Contributor

I wonder if this would be a good opportunity to add the ability to add the ability to attach labels to vertices and edges, as this is not too difficult with this format. We could do this with optional keyword arguments. What do you think @sbromberger ?

I think this might be better as a method forMetaGraphs.jl..

@purchawek
Copy link

Hi, I'm not a contributor here, but I'm waiting for this functionality to be added and I just took a look on the code you've submitted.

This is a fairly straightforward functionality and I'm wondering why you keep all the logic in a single function. In line 14 you distinguish directed and undirected graphs, and hence use the variable isdir to handle these two mutually exclusive cases. Wouldn't it be nicer to have two separate methods for directed and undirected graphs and call one of them accordingly to the value stored in isdir?

@simonschoelly
Copy link
Member

I wonder if this would be a good opportunity to add the ability to add the ability to attach labels to vertices and edges, as this is not too difficult with this format. We could do this with optional keyword arguments. What do you think @sbromberger ?

I think this might be better as a method forMetaGraphs.jl..

There are two reasons, why I think it might be a good idea to do that here:

  1. DOT is often used with graphviz for plotting, this would mean, that we would have to convert to a MetaGraph first, if we want to put labels on edges and vertices.
  2. If we had the functionality to add labels here, then we could base the code MetaGraphs.jl on it. Otherwise we would have to do an implementation for all other packages that support labels on edges and vertices.

@simonschoelly
Copy link
Member

We really should make some progress here. Lets skip the issue with the metadata, that can be added later. I think in your current code, isolated vertices will not be added to the dotfile? Can you fix that?

@abhinavmehndiratta
Copy link
Contributor Author

We really should make some progress here. Lets skip the issue with the metadata, that can be added later. I think in your current code, isolated vertices will not be added to the dotfile? Can you fix that?

Isolated vertices are added. The problem is that when you save the DOT file and load it again the graphs will not be the same because of this -

nodedict = Dict(zip(collect(Parsers.DOT.nodes(pg)), 1:nvg))

@simonschoelly
Copy link
Member

You are right, I totally forgot about that. But your code outputs the graph in the right order and it is the importer that messes that up? Because in that case, we don't have to fix it here.

@simonschoelly
Copy link
Member

Can you add some tests? We can get around the issue that the vertex order is messed up in the importer by either using vertex-transitive graphs for testing or by using the isomorphism algorithms for reordering.

@abhinavmehndiratta
Copy link
Contributor Author

You are right, I totally forgot about that. But your code outputs the graph in the right order and it is the importer that messes that up? Because in that case, we don't have to fix it here.

Yes the DOT parser returns nodes in random order, so the loaded graph is not the same but yes the saved graph is correct.

@abhinavmehndiratta
Copy link
Contributor Author

abhinavmehndiratta commented Mar 23, 2019

Can you add some tests? We can get around the issue that the vertex order is messed up in the importer by either using vertex-transitive graphs for testing or by using the isomorphism algorithms for reordering.

If I understand correctly does that mean checking if the loaded graph is isomorphic to the original saved graph in the tests ? But the problem will still remain, right ? I mean is it only for testing ?

@abhinavmehndiratta
Copy link
Contributor Author

We could fix this problem for LightGraphs only btw, like b79931c I was doing before, but then it is not generalized.

@simonschoelly
Copy link
Member

Yes, only for testing. Maybe also add a comment there, why we are doing this.

Let's think about what would be correct for the importer in another issue/pr, so we can finally merge this.

@abhinavmehndiratta
Copy link
Contributor Author

Yes, only for testing. Maybe also add a comment there, why we are doing this.

Let's think about what would be correct for the importer in another issue/pr, so we can finally merge this.

Okay I'll add some tests then.

@abhinavmehndiratta
Copy link
Contributor Author

I have added the tests but code coverage seems to be wrong.

@simonschoelly
Copy link
Member

Ouch, this got completely forgotten. Maybe you can implement that thing that I suggested and then rebase on master. Then lets see if code coverage still complains..

@abhinavmehndiratta
Copy link
Contributor Author

The diff coverage seems fine.

@sbromberger
Copy link
Contributor

Are we good to merge this?

@sbromberger sbromberger merged commit 6db4c3d into JuliaGraphs:master May 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants