Skip to content

Commit

Permalink
add option to force equal y-positions across different layers
Browse files Browse the repository at this point in the history
fix typo

accidentally updated wrong file: fix

add tests

improve docstring

actually include new tests

Typo

Update Project.toml: bump to v0.2.6

Update Project.toml

Resolve merge conflict: bump to v0.2.7

Commit suggestion

Co-authored-by: Frames White <oxinabox@ucc.asn.au>
  • Loading branch information
thchr and oxinabox committed Apr 11, 2023
1 parent 53c8eed commit d30bc1e
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "LayeredLayouts"
uuid = "f4a74d36-062a-4d48-97cd-1356bad1de4e"
authors = ["Frames White <oxinabox@ucc.asn.au> and contributors"]
version = "0.2.6"
version = "0.2.7"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
2 changes: 1 addition & 1 deletion src/layering.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ function add_dummy_nodes!(graph, layer2nodes)
is_dummy_mask = trues(nv(graph))
is_dummy_mask[nondummy_nodes] .= false
return is_dummy_mask, edge_to_paths
end
end
56 changes: 48 additions & 8 deletions src/zarate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Base.@kwdef struct Zarate <: AbstractLayout
end

"""
solve_positions(::Zarate, graph; force_layer, force_order)
solve_positions(::Zarate, graph; force_layer, force_order, force_equal_layers)
Returns:
- `xs`: the xs coordinates of vertices in the layout
Expand All @@ -45,10 +45,16 @@ Optional arguments:
specifies the layer for each node
e.g. [3=>1, 5=>5] specifies layer 1 for node 3 and layer 5 to node 5
force_order: Vector{Pair{Int, Int}}
`force_order`: Vector{Pair{Int, Int}}
this vector forces the ordering of the nodes in each layer,
e.g. `force_order = [3=>2, 1=>3]` forces node 3 to lay before node 2, and node 1 to lay before node 3
`force_equal_layers`: Vector{Pair{Int, Int}}
force the nodes of distinct layers to have identical y-positions.
e.g. `force_equal_layers = [i=>j, …]` specifies that the node orderings and
y-positions in layers `i` and `j` must be equal; note that the number of nodes in layers
`i` and `j` must be identical.
# Example:
```julia
using Graphs, Plots
Expand All @@ -66,6 +72,7 @@ function solve_positions(
layout::Zarate, original_graph;
force_layer::Vector{Pair{Int, Int}} = Vector{Pair{Int, Int}}(),
force_order::Vector{Pair{Int, Int}} = Vector{Pair{Int, Int}}(),
force_equal_layers::Vector{Pair{Int, Int}} = Vector{Pair{Int, Int}}()
)
graph = copy(original_graph)

Expand All @@ -78,7 +85,9 @@ function solve_positions(
min_total_distance = Inf
min_num_crossing = Inf
local best_pos
ordering_model, is_before = ordering_problem(layout, graph, layer2nodes; force_order=force_order)
ordering_model, is_before = ordering_problem(layout, graph, layer2nodes;
force_order=force_order,
force_equal_layers=force_equal_layers)
for round in 1:typemax(Int)
round > 1 && forbid_solution!(ordering_model, is_before)

Expand All @@ -93,7 +102,8 @@ function solve_positions(
order_layers!(layer2nodes, is_before)

# 3. Node Arrangement
xs, ys, total_distance = assign_coordinates(layout, graph, layer2nodes)
xs, ys, total_distance = assign_coordinates(layout, graph, layer2nodes;
force_equal_layers=force_equal_layers)
if total_distance < min_total_distance
min_total_distance = total_distance
best_pos = (xs, ys)
Expand All @@ -105,7 +115,7 @@ function solve_positions(
end

"""
ordering_problem(::Zarate, graph, layer2nodes; force_order)
ordering_problem(::Zarate, graph, layer2nodes; force_order, force_equal_layers)
Formulates the problem of working out optimal ordering as a MILP.
Expand All @@ -116,7 +126,8 @@ Returns:
if `n1` is best arrange before `n2`.
"""
function ordering_problem(layout::Zarate, graph, layer2nodes;
force_order=Vector{Pair{Int, Int}}())
force_order=Vector{Pair{Int, Int}}(),
force_equal_layers=Vector{Pair{Int, Int}}())
m = Model(layout.ordering_solver)
set_silent(m)

Expand Down Expand Up @@ -147,6 +158,22 @@ function ordering_problem(layout::Zarate, graph, layer2nodes;
end
end

# force identical pairings in certain layers
for (layerA, layerB) in force_equal_layers # ordering in layer1 and layer2 must be identical
nodesA = sort(layer2nodes[layerA]) # internal ordering in each layer matters
nodesB = sort(layer2nodes[layerB])
for (i1, (nA1, nB1)) in enumerate(zip(nodesA, nodesB))
for i2 in i1+1:length(nodesA)
nA2 = nodesA[i2]
nB2 = nodesB[i2]
variable_layerA = variable_by_name(m, "befores_$layerA[$nA1,$nA2]")
variable_layerB = variable_by_name(m, "befores_$layerB[$nB1,$nB2]")
@constraint(m, variable_layerA == variable_layerB)
end
end
end


weights_mat = weights(graph)

function crossings(src_layer)
Expand Down Expand Up @@ -229,15 +256,17 @@ function forbid_solution!(m, is_before)
end

"""
assign_coordinates(graph, layer2nodes)
assign_coordinates(layout, graph, layer2nodes; force_equal_layers)
Works out the `x` and `y` coordinates for each node in the `graph`.
This is via formulating the problem as a QP, and minimizing total distance
of links.
It maintains the order given in `layer2nodes`s.
`force_equal_layers` enforces equal y-positions across paired nodes in specified layers.
returns: `xs, ys, total_distance`
"""
function assign_coordinates(layout, graph, layer2nodes)
function assign_coordinates(layout, graph, layer2nodes;
force_equal_layers=Vector{Pair{Int,Int}}())
m = Model(layout.arranging_solver)
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
Expand All @@ -254,6 +283,17 @@ function assign_coordinates(layout, graph, layer2nodes)
end
end

# force identical y-positions in certain layers
for (layerA, layerB) in force_equal_layers # ordering in layer1 and layer2 must be identical
nodesA = sort(layer2nodes[layerA]) # internal ordering in each layer matters
nodesB = sort(layer2nodes[layerB])
for (nA, nB) in zip(nodesA, nodesB)
yA = node2y[nA]
yB = node2y[nB]
@constraint(m, yA == yB)
end
end

all_distances = AffExpr[]
# minimize link distances
for cur in vertices(graph)
Expand Down
2 changes: 2 additions & 0 deletions test/demos.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ end
test_example(layout, :tree, 0.07)
test_example(layout, :two_lines, 0.07; force_layer=[6=>4, 8=>5])
test_example(layout, :two_lines, 0.07; force_order=[1=>2])
test_example(layout, :two_lines_flipped_vertex_order)
test_example(layout, :two_lines_flipped_vertex_order; force_equal_layers=[1=>3])
#test_example(layout, :large_depgraph) # too big
#test_example(layout, :extra_large_depgraph) # too big
end
5 changes: 5 additions & 0 deletions test/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ module Examples
2 => 4, 4=>6, 6=>8,
]))

two_lines_flipped_vertex_order = SimpleDiGraph(Edge.([
1=>3, 3=>6, 5=>7, 7=>9, # flips insertion order of vertex 5 and 6
2=>4, 4=>5, 6=>8,
]))

loop = SimpleDiGraph(Edge.([
1 .=> [2, 3];
2 => 4; 4 => 6;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d30bc1e

Please sign in to comment.