Skip to content

type-based interface #27

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

Merged
merged 30 commits into from
Jun 28, 2021
Merged

type-based interface #27

merged 30 commits into from
Jun 28, 2021

Conversation

hexaeder
Copy link
Collaborator

@hexaeder hexaeder commented May 17, 2021

This WIP PR should resolve #25. The main goal is to split the parametrisation of each algorithm from the actual layout call.

The new public interface is as follows. Each algorithm/layout has a struct which holds the algorithm parameters.

algo = Algorithm(; parameters)
algo isa AbstractLayout{Dim, Ptype}

Each AbstractLayout is callable to retrieve the positions for a adjacency matrix

positions = algo(adj_matrix)
positions isa Vector{Point{Dim, Ptype}}

For iterative layouts one can get access to the intermediate states

algo = IterativeAlgorithm(; parameters)

iter = LayoutIterator(algo, adj_matrix)
for p in iter
    # do stuff
end
  • Buchheim Tree
  • Cricular Layout
  • SFDP
  • Shell
  • Spectral
  • Spring
  • Stress
  • get rid of hacky solution for bootstrapping problem (dep cycle between GraphMakie and NetworkLayout in docs)
  • update main docstrings for all layouts since they are the main content of the docs
  • docs for interface
  • doc usage of iterator (animations?)
  • update readme
  • add assert_square
  • remove Circular?
  • better example for SquareGrid?
  • doc Ptype and Dim
  • add spring, buchheim, ... methods
  • remove layout from exports

@codecov
Copy link

codecov bot commented May 19, 2021

Codecov Report

Merging #27 (26ce7d3) into master (00491f9) will decrease coverage by 0.47%.
The diff coverage is 96.70%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #27      +/-   ##
==========================================
- Coverage   98.62%   98.14%   -0.48%     
==========================================
  Files           7        8       +1     
  Lines         363      431      +68     
==========================================
+ Hits          358      423      +65     
- Misses          5        8       +3     
Impacted Files Coverage Δ
src/spectral.jl 96.00% <90.00%> (-4.00%) ⬇️
src/NetworkLayout.jl 91.30% <90.90%> (ø)
src/spring.jl 97.72% <96.55%> (ø)
src/sfdp.jl 98.27% <96.96%> (+0.19%) ⬆️
src/stress.jl 98.71% <97.36%> (+0.12%) ⬆️
src/buchheim.jl 98.78% <100.00%> (+0.11%) ⬆️
src/shell.jl 100.00% <100.00%> (ø)
src/squaregrid.jl 100.00% <100.00%> (ø)

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 00491f9...26ce7d3. Read the comment docs.

@hexaeder
Copy link
Collaborator Author

hexaeder commented May 19, 2021

@hexaeder hexaeder marked this pull request as ready for review May 25, 2021 14:06
@hexaeder
Copy link
Collaborator Author

I think this is ready for review. Right now this package does not export any of the layouts, maybe it should?

There isn't a lot of testing besides checking the return types of the algorithms. Everything in the docs seems to work fine though...

@hexaeder hexaeder requested a review from SimonDanisch May 25, 2021 14:14
@SimonDanisch
Copy link
Member

Right now this package does not export any of the layouts, maybe it should?

Yes, I think we should :)

@SimonDanisch SimonDanisch requested review from simonschoelly and removed request for SimonDanisch May 25, 2021 15:55
@hexaeder
Copy link
Collaborator Author

hexaeder commented Jun 6, 2021

One thing i've noticed writing examples for GraphMakie:
NetworkLayout exports layout. This kinda makes sense because it is an important part of the interface. However, layout is also a very broad term and happes to be a kw arg in GraphMakie, i.e.

layout = Spring(seed=4)
graphplot(g; layout)

won't work anymore. 4 options come to mind:

  • ignore 🤷‍♂️
  • rename the layout function to something more specific like apply_layout, calculate_positions, getpos, locations, idk
  • remove layout from the exports (and probably from the doc strings), thats not too bad because Algo()(adj_matrix) and Algo()(graph) do the same
  • change the kw arg in GraphMakie

I tend to option 3. Option 2 is also good but I'm not happy with any of the names suggested. Any thoughts on that @SimonDanisch?

The second question: the Algorithm(;kw_params)(graph) syntax might be awkward for some applications. I was thinking about adding

algorithm(g; kwargs...) = Algorithm(kwargs...)(g)

to all of the algorithms (either manually or by using a @layout struct Algo <: AbstractLayout macro to annotate the types). Is this feature worth the clutting of the namespace?


"""
AbstractLayout{Dim,Ptype}

Copy link
Member

Choose a reason for hiding this comment

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

It would be good to document what the parameters Dim and Pyype are. and what types the can take.

Also thinking about it, how often is the case, that Dim will not be 2 and Ptype not be Float64? Are they really that important that always specify them in the type? Other possibilities would be to specifying these parameters when calling the layout function, maybe as kwargs or just have the default trait functions defined like this

dim(::AbstractLayout) = 2
ptype(::AbstractLayout) = Float64

then one can override these trait functions for a concrete layout, if they would differ somehow,

Copy link
Collaborator Author

@hexaeder hexaeder Jun 7, 2021

Choose a reason for hiding this comment

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

The functions dim and ptype aren't used at all in the moment tbh, i am not sure whether they are important at all. But the parameters are used in the layout functions quite often.

  • Dim: Right now there are 3 layouts which support 2D and 3D (Spring¹, Stress, SFDP), one pure 3D layout (Spectral) and 3 pure 2D layouts (Buchheim, Circular/Shell², SquareGrid). Each layout has the intrinsic property of a dimensionality, I think this justifies to put this information to the supertype. Otherwise we need to add an explicit dim type parameter to 3 of the layouts (should be a type parameter rather than a field becaus it determines the return value Vector{Point{Dim,Ptype}} of the layout function).
  • Ptype: Well, the number type of the return values was parametrized before and I did not change that. I'm not gonna lie: this whole interface is opinionated and developed with GraphMakie in mind. And since Makie uses the GPU it is quit common to work with Float32 too.

¹ there is a minor parametrization bug in the 3d spring implementation though but I did not consider this part of the interface
² We don't actually need special code for Circular. The resulting layout of Circular() and Shell() without parameters is the same.

src/buchheim.jl Outdated
Comment on lines 23 to 25
struct Buchheim{Ptype,T} <: AbstractLayout{2,Ptype}
nodesize::Vector{T}
end
Copy link
Member

Choose a reason for hiding this comment

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

When we try to separate the layout parameters from the actually layout calculation, does it make sense to store the node/vertex parameters in this struct? This would mean, that we already need to know how many vertices we have, when we create this struct. Maybe it would be better to specify it in

layout(::Buchheim; nodsize=nothing) = ...

This way, it might also be easier to deal with graphs that have metadata attached to their nodes later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, this is for sure the biggest problem with the proposed interface: it is not completely consistent. There are parameters like the stiffness in spring model or random seeds which are independent of the the the actual layout call. There are other parameters which are tied to stuff like the size of the graph.

I've considered multiple options for this problem:

  • Layout.layout(g; kwargs...): the old way. This feels very unjulian to me because it defines several functions rather than methods for the same function. Pros: no additional types needed.
  • layout(::Layout, g; kwargs...) where Layout only stores general parameters. Everything else gets passed to the layout function. I think this is quite confusing because there are several points where to inject parameters. In case of graph plotting on need to either define a closure or add some other container to pass the parameters
lay = g -> layout(Spring(some_para...), g, other_para)
# or
lay = Spring(some_para)
graphplot(g, layout=lay, layoutpara=(other_para...))
  • layout(::Layout, g): all parameters stored in Layout object

I went with the third option. Most of the time it does not matter because users will know the size of their graph and this won't change. Wherever possible I've implemented the restrictions very soft: i.e. if there are more nodes than nodesize: just fill up with ones. If there are N nodes but M>N initial positions? Just take the first N etc.

I think this design actually plays very well with stored meta data in case of graph plotting

g = some_meta_graph()
function mylayout(g::MetaGraph)
    nodesize = extract_data(g)
    return Buchheim(; nodesize)(g)
end
plot(g; layout=mylayout)

Copy link
Collaborator Author

@hexaeder hexaeder Jun 11, 2021

Choose a reason for hiding this comment

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

After a short discussion with SimonD i've removed the layout function from the exported interface. For one-shot application there are now lower case function calls for all of the layouts. So now there are two main ways of applying layouts:

layoutfunction = MyLayout(; kwargs) # creates callable object to apply layout later
positions = mylayout(g; kwargs...) # apply layout right now

the second one is quite similar to the old Spring.layout(g; kwargs...).

`skip=[(i,j)]` means to keep the position in the `i`-th row and `j`-th column
empty.
"""
struct SquareGrid{Ptype,CT} <: AbstractLayout{2,Ptype}
Copy link
Member

Choose a reason for hiding this comment

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

To be honest, I am a bit unsure what the purpose of this layout is, does it really make sense to line up the vertices row by row on a grid? Won't this cause a huge mess when plotting the graph with overlapping edges?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well this one is quite specific for my usecase to be honest. In papers about networks with geographic/planar embedding (for example power grids) it is quite common to use square grids as toy models. The example in the docs is bad though.

grafik

(Franz Kaiser et al 2020 New J. Phys. 22 013053)

grafik

(Manik, D., Molkenthin, N. Topology dependence of on-demand ride-sharing. Appl Netw Sci 5, 49 (2020). https://doi.org/10.1007/s41109-020-00290-2)

hexaeder and others added 8 commits June 7, 2021 17:32
@hexaeder hexaeder merged commit 3cfeeef into master Jun 28, 2021
@hexaeder hexaeder deleted the interface branch June 28, 2021 12:30
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.

common interface between layouts
3 participants