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

Add Optimal Sugiyama Layout #4

Merged
merged 27 commits into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b4bcfdc
Fix CI
DilumAluthge Jul 4, 2020
5d3a8da
sortout tests
oxinabox Jul 1, 2020
b450bcc
fix order of coordinates and test more
oxinabox Jul 1, 2020
025fdbe
julia 1
oxinabox Jul 20, 2020
788ced9
Merge pull request #3 from oxinabox/ox/MinDistOne
oxinabox Jul 21, 2020
ed9a3cf
add sugiyama ordering
oxinabox Jul 20, 2020
9f29484
add finding next best solution (demonstrates that currently all solut…
oxinabox Jul 20, 2020
9bee8fd
Fix Ordering
oxinabox Jul 21, 2020
a1f6280
add weight support and fix find_next_best
oxinabox Jul 21, 2020
f2ef825
Add Arranging within layers
oxinabox Jul 22, 2020
edee2e4
Add reference tests
oxinabox Jul 22, 2020
6ef4c9c
add many more tests
oxinabox Jul 25, 2020
766e5d7
add multiple order checking
oxinabox Aug 18, 2020
476bf92
add more large examples
oxinabox Aug 20, 2020
8e970eb
enhance assign_coordinates to: not weight, use ECOS, and center first…
oxinabox Aug 22, 2020
ac0cc3b
default to time-limit of zero
oxinabox Aug 22, 2020
05c19c1
add optional extra crossing constraints
oxinabox Aug 22, 2020
4878947
for much speed do not constaint crossings to be binary
oxinabox Aug 22, 2020
48f9aa6
hush logs, and chillax test constraints.
oxinabox Aug 22, 2020
1fdf788
=make solver a parameter (Something is looking funny though)
oxinabox Aug 29, 2020
cf395ca
Make compile tweaks required
oxinabox Sep 24, 2020
c4ad005
Rename OptimalSugiyama to Zarate
oxinabox Sep 24, 2020
0a50e6e
default to timeout of zero
oxinabox Sep 24, 2020
6a9465c
Relax similarity tolerance
oxinabox Sep 24, 2020
a285a6f
relax tolerance more
oxinabox Sep 24, 2020
f0b5047
relax tolerances even more
oxinabox Sep 24, 2020
3f21d6c
relax tolerances even more
oxinabox Sep 24, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ jobs:
strategy:
matrix:
version:
- '1.0'
- '1.6'
- '1'
- 'nightly'
os:
- ubuntu-latest
Expand Down
16 changes: 10 additions & 6 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ authors = ["Lyndon White <lyndon.white@invenialabs.co.uk> and contributors"]
version = "0.1.0"

[deps]
Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44"
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
ECOS = "e2685f51-7e38-5353-a97d-a921fd2c8199"
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
julia = "1"

[extras]
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
SimpleWeightedGraphs = "47aef6b3-ad0c-573a-a1e2-d07658019622"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92"

[targets]
test = ["Test"]
test = ["ImageIO", "ImageMagick", "Plots", "SimpleWeightedGraphs", "Test", "VisualRegressionTests"]
11 changes: 10 additions & 1 deletion src/LayeredLayouts.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
module LayeredLayouts
using Dates
using JuMP
using ECOS
using Ipopt
using Cbc
using IterTools: IterTools
using LightGraphs
using Random

export LayeredMinDistOne
export LayeredMinDistOne, Zarate
export solve_positions

abstract type AbstractLayout end

include("utils.jl")

include("graph_properties.jl")
include("layering.jl")

include("min_dist_one.jl")
include("zarate.jl")

end
48 changes: 48 additions & 0 deletions src/layering.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# This contains algorithms for breaking up a DAG into layers

function layer_by_longest_path_to_source(graph)
dists = longest_paths(graph, sources(graph))
layer_groups = IterTools.groupby(i->dists[i], sort(vertices(graph), by=i->dists[i]))
return collect.(layer_groups)
end


###################
# helpers


function node2layer_lookup(layer2nodes)
flat_map = [node=>layer_ind for (layer_ind, nodes) in enumerate(layer2nodes) for node in nodes]
sort!(flat_map)
@assert first.(flat_map) == 1:length(flat_map)
return last.(sort(flat_map))
end

"add nodes so that no edges span multiple layers, returns mask"
function add_dummy_nodes!(graph, layer2nodes)
dag_or_error(graph)
nondummy_nodes = vertices(graph)
node2layer = node2layer_lookup(layer2nodes) # doesn't have dummy nodes, doesn't need them
for cur_node in vertices(graph)
cur_layer = node2layer[cur_node]
for out_node in copy(outneighbors(graph, cur_node)) # need to copy as outwise will mutate when the graph is mutated
out_layer = node2layer[out_node]
cur_layer < out_layer || DomainError(node2layer, "Layer assigmenment must be strictly monotonic")
if out_layer != cur_layer + 1
rem_edge!(graph, cur_node, out_node) || error("removing edge failed")
prev_node = cur_node
for step_layer in (cur_layer + 1) : (out_layer - 1)
add_vertex!(graph) || throw(OverflowError("Could not add vertices to graph."))
step_node = nv(graph)
push!(layer2nodes[step_layer], step_node)
add_edge!(graph, prev_node, step_node)
prev_node = step_node
end
add_edge!(graph, prev_node, out_node)
end
end
end
is_dummy_mask = trues(nv(graph))
is_dummy_mask[nondummy_nodes] .= false
return is_dummy_mask
end
25 changes: 13 additions & 12 deletions src/min_dist_one.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ Base.@kwdef struct LayeredMinDistOne <: AbstractLayout
intralayer_seperation::Float64 = 1.0
end

function determine_layers(::LayeredMinDistOne, graph)
dists = longest_paths(graph, sources(graph))
layer_groups = IterTools.groupby(i->dists[i], sort(vertices(graph), by=i->dists[i]))
return layer_groups
end

function solve_positions(layout::LayeredMinDistOne, graph)
layer_groups = determine_layers(layout, graph)
layer_groups = layer_by_longest_path_to_source(graph)

m = Model(Ipopt.Optimizer)
set_silent(m)
set_optimizer_attribute(m, "print_level", 0) # TODO this can be deleted once the version of IPOpt that actually supports `set_silent` is released


ys = map(enumerate(layer_groups)) do (layer, nodes)
y_min = 0 # IPOpt can't find a solution without this

Expand All @@ -25,9 +23,12 @@ function solve_positions(layout::LayeredMinDistOne, graph)
end

node_vars = Dict{Int, VariableRef}() # lookup list from vertex index to variable
for (y, nodes) in zip(ys, layer_groups)
node_layer_indexes = Dict{Int, Int}()
for (layer_ind, (y, nodes)) in enumerate(zip(ys, layer_groups))
for n in nodes
@assert !haskey(node_vars, n)
@assert !haskey(node_layer_indexes, n)
node_layer_indexes[n] = layer_ind
node_vars[n] = y[n] # remember this for later
end
end
Expand All @@ -49,14 +50,14 @@ function solve_positions(layout::LayeredMinDistOne, graph)
))

optimize!(m)
@show termination_status(m)

xs = Float64[]
ys = Float64[]
for (layer, nodes) in enumerate(layer_groups)
for node in nodes
push!(xs, layer)
push!(ys, value(node_vars[node]))
end
for node in vertices(graph)
layer_ind = node_layer_indexes[node]
push!(xs, layout.interlayer_seperation * layer_ind)
push!(ys, value(node_vars[node]))
end
return xs, ys
end
10 changes: 10 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# for things that are not actually related to the problem itself

"""
try_get(collection, inds...)

Like `getindex`, except returns `nothing` if not found or
if the collection itsself is `nothing`.
"""
try_get(::Nothing, inds...) = nothing
try_get(x, inds...) = get(x, inds, nothing)
Loading