From b82301370b496d94d41afcc3025fbad1ed3e9476 Mon Sep 17 00:00:00 2001 From: crinders Date: Wed, 19 Jun 2019 22:21:52 -0400 Subject: [PATCH 1/5] Kamada Kawai visualization layout using Optim and Distances --- src/KamadaKawai.jl | 87 ++++++++++++++++++++++++++++++++++++++++++++++ src/layout.jl | 4 ++- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/KamadaKawai.jl diff --git a/src/KamadaKawai.jl b/src/KamadaKawai.jl new file mode 100644 index 0000000..f098599 --- /dev/null +++ b/src/KamadaKawai.jl @@ -0,0 +1,87 @@ +import Optim +using Distances +function kamada_kawai_layout(G, X=nothing; C= 1.0, MAXITER=100 ) + Offset = 0.0 + if X===nothing + locs_x = zeros(nv(G)) + locs_y = zeros(nv(G)) + else + locs_x = X[1] + locs_y = X[2] + end + + function Objective(M,K) + D = pairwise(Euclidean(),M, dims=2) + D-= K + R = sum(D.^2)/2 + return R + end + + function dObjective!(dR,M,K) + dR .= zeros(size(M)) + Vs = size(M,2) + D = pairwise(Euclidean(),M, dims=2) + D += I # Prevent division by zero + D .= K./D # Use negative for simplicity, since diag K = 0 everything is fine. + D .-= 1.0 # (K-(D+I))./(D+I) = K./(D+I) .- 1.0 + D += I # Remove the false diagonal + for v1 in 1:Vs + dR[:,v1] .= -M[:,v1]*sum(D[:,v1]) + end + dR .+= M*D + dR .*=2 + return dR + end + + function scaler(z, a, b) + 2.0*((z - a)/(b - a)) - 1.0 + end + # Stack individual graphs next to each other + for SubGraphVertices in connected_components(G) + SubGraph = induced_subgraph(G,SubGraphVertices)[1] + N = nv(SubGraph) + if X !== nothing + _locs_x = locs_x[SubGraphVertices] + _locs_y = locs_y[SubGraphVertices] + else + Vertices=collect(vertices(SubGraph)) + Vmax=findmax([degree(SubGraph,x) for x in vertices(SubGraph)])[2] + filter!(x->x!=Vmax, Vertices) + Shells=[[Vmax]] + VComplement = copy(Shells[1]) + while length(Vertices)>0 + Interim = filter(x->!(x ∈ VComplement),vcat([collect(neighbors(SubGraph,s)) for s in Shells[end]]...)) + unique!(Interim) + push!(Shells,Interim) + filter!(x->!(x ∈ Shells[end]),Vertices) + append!(VComplement,Shells[end]) + end + _locs_x, _locs_y = shell_layout(SubGraph,Shells) + end + + # The optimal distance between vertices + # Currently only LightGraphs are supported using the Dijkstra shortest path algorithm + K = zeros(N,N) + for v in 1:N + K[:,v] = dijkstra_shortest_paths(SubGraph,v).dists + end + + M0 = vcat(_locs_x',_locs_y') + OptResult = Optim.optimize(x->Objective(x,K),(x,y) -> dObjective!(x,y,K), M0, method=Optim.LBFGS(),iterations = MAXITER ) + M0 = Optim.minimizer(OptResult) + min_x, max_x = minimum(M0[1,:]), maximum(M0[1,:]) + min_y, max_y = minimum(M0[2,:]), maximum(M0[2,:]) + map!(z -> scaler(z, min_x, max_x), M0[1,:], M0[1,:]) + map!(z -> scaler(z, min_y, max_y), M0[2,:], M0[2,:]) + locs_x[SubGraphVertices] .= M0[1,:] .+ Offset + locs_y[SubGraphVertices] .= M0[2,:] + Offset += maximum(M0[1,:])+C + end + # Scale to unit square + min_x, max_x = minimum(locs_x), maximum(locs_x) + min_y, max_y = minimum(locs_y), maximum(locs_y) + map!(z -> scaler(z, min_x, max_x), locs_x, locs_x) + map!(z -> scaler(z, min_y, max_y), locs_y, locs_y) + + return locs_x,locs_y +end diff --git a/src/layout.jl b/src/layout.jl index 938555e..08fef1c 100644 --- a/src/layout.jl +++ b/src/layout.jl @@ -192,7 +192,7 @@ Vector of Vector, Vector of node Vector for each shell. julia> g = graphfamous("karate") julia> nlist = Array{Vector{Int}}(2) julia> nlist[1] = [1:5] -julia> nlist[2] = [6:num_vertiecs(g)] +julia> nlist[2] = [6:num_vertices(g)] julia> locs_x, locs_y = shell_layout(g, nlist) ``` """ @@ -280,3 +280,5 @@ function _spectral(A::SparseMatrixCSC) index = sortperm(real(eigenvalues))[2:3] return real(eigenvectors[:, index[1]]), real(eigenvectors[:, index[2]]) end + +include("KamadaKawai.jl") From e6da6833b2a14b59a228f88ee18785ca9f1e379a Mon Sep 17 00:00:00 2001 From: crinders Date: Wed, 19 Jun 2019 22:25:41 -0400 Subject: [PATCH 2/5] Forgot to add REQUIRE and tomls --- Project.toml | 12 +++++++----- REQUIRE | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index f199533..ee677ff 100644 --- a/Project.toml +++ b/Project.toml @@ -9,11 +9,18 @@ ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" Compose = "a81c6b42-2e10-5240-aca2-a61377ecd94b" DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" +Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Optim = "429524aa-4258-5aef-a3af-852621145aeb" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +[compat] +Compose = "0.7" +LightGraphs = "1.1" +VisualRegressionTests = "0.2" + [extras] Cairo = "159f3aea-2a34-519c-b102-8c37f9878175" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" @@ -22,8 +29,3 @@ VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92" [targets] test = ["Test", "Cairo", "ImageMagick", "VisualRegressionTests"] - -[compat] -"Compose" = "0.7" -"LightGraphs" = "1.1" -"VisualRegressionTests" = "0.2" diff --git a/REQUIRE b/REQUIRE index 5a68070..ae08ca0 100644 --- a/REQUIRE +++ b/REQUIRE @@ -4,3 +4,5 @@ Colors ColorTypes Compose 0.7.0 LightGraphs 1.1.0 +Optim +Distances From 50543207fedda7d87afead9d2b4504537f2ce81c Mon Sep 17 00:00:00 2001 From: crinders Date: Mon, 24 Jun 2019 13:06:55 -0400 Subject: [PATCH 3/5] Updated kamada_kawai_layout --- src/KamadaKawai.jl | 102 +++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/src/KamadaKawai.jl b/src/KamadaKawai.jl index f098599..12c5e51 100644 --- a/src/KamadaKawai.jl +++ b/src/KamadaKawai.jl @@ -1,7 +1,32 @@ import Optim using Distances -function kamada_kawai_layout(G, X=nothing; C= 1.0, MAXITER=100 ) - Offset = 0.0 +@doc raw""" + Computes the layout corresponding to + + ```math +\min \sum_{i,j} (K_{ij} - \|x_i - x_j\|)^\frac{1}{2}, + ``` +where $K_{ij}$ is the shortest path distance between vertices `i` abd `j`, and $x_i$ and $x_j$ are the positions of vertices `i` and `j` in the layout, respectively. + +Inputs: + + - G: Graph to layout + - X: starting positions. If no starting positions are provided a shell_layout is constructed. + +Optional keyword arguments: + + - maxiter: maximum number of iterations Optim.optimize will perform in an attempt to optimize the layout. + - distmx: Distance matrix for the Floyd-Warshall algorithm to discern shortest path distances on the graph. By default the edge weights of the graph. + + Outputs: + + - (`locs_x`, `locs_y`) positions in the x and y directions, respectively. + +""" +function kamada_kawai_layout(G, X=nothing; maxiter=100, distmx=weights(G) ) + if !is_connected(G) + @warn "This graph is disconnected. Results may not be reasonable." + end if X===nothing locs_x = zeros(nv(G)) locs_y = zeros(nv(G)) @@ -36,50 +61,45 @@ function kamada_kawai_layout(G, X=nothing; C= 1.0, MAXITER=100 ) function scaler(z, a, b) 2.0*((z - a)/(b - a)) - 1.0 end - # Stack individual graphs next to each other - for SubGraphVertices in connected_components(G) - SubGraph = induced_subgraph(G,SubGraphVertices)[1] - N = nv(SubGraph) - if X !== nothing - _locs_x = locs_x[SubGraphVertices] - _locs_y = locs_y[SubGraphVertices] - else - Vertices=collect(vertices(SubGraph)) - Vmax=findmax([degree(SubGraph,x) for x in vertices(SubGraph)])[2] - filter!(x->x!=Vmax, Vertices) - Shells=[[Vmax]] - VComplement = copy(Shells[1]) - while length(Vertices)>0 - Interim = filter(x->!(x ∈ VComplement),vcat([collect(neighbors(SubGraph,s)) for s in Shells[end]]...)) - unique!(Interim) - push!(Shells,Interim) - filter!(x->!(x ∈ Shells[end]),Vertices) - append!(VComplement,Shells[end]) - end - _locs_x, _locs_y = shell_layout(SubGraph,Shells) - end + N = nv(G) + Vertices=collect(vertices(G)) - # The optimal distance between vertices - # Currently only LightGraphs are supported using the Dijkstra shortest path algorithm - K = zeros(N,N) - for v in 1:N - K[:,v] = dijkstra_shortest_paths(SubGraph,v).dists + if X !== nothing + _locs_x = locs_x + _locs_y = locs_y + else + Vmax=findmax([degree(G,x) for x in vertices(G)])[2] + filter!(!isequal(Vmax), Vertices) + Shells=[[Vmax]] + VComplement = copy(Shells[1]) + while !isempty(Vertices) + Interim = filter(!∈(VComplement),vcat([collect(neighbors(G,s)) for s in Shells[end]]...)) + unique!(Interim) + push!(Shells,Interim) + filter!(!∈(Shells[end]),Vertices) + append!(VComplement,Shells[end]) end + _locs_x, _locs_y = shell_layout(G,Shells) + end - M0 = vcat(_locs_x',_locs_y') - OptResult = Optim.optimize(x->Objective(x,K),(x,y) -> dObjective!(x,y,K), M0, method=Optim.LBFGS(),iterations = MAXITER ) - M0 = Optim.minimizer(OptResult) - min_x, max_x = minimum(M0[1,:]), maximum(M0[1,:]) - min_y, max_y = minimum(M0[2,:]), maximum(M0[2,:]) - map!(z -> scaler(z, min_x, max_x), M0[1,:], M0[1,:]) - map!(z -> scaler(z, min_y, max_y), M0[2,:], M0[2,:]) - locs_x[SubGraphVertices] .= M0[1,:] .+ Offset - locs_y[SubGraphVertices] .= M0[2,:] - Offset += maximum(M0[1,:])+C + # The optimal distance between vertices + # Currently only LightGraphs are supported using the Dijkstra shortest path algorithm + K = zeros(N,N) + for v in 1:N + K[:,v] = dijkstra_shortest_paths(G,v).dists end + + K = floyd_warshall_shortest_paths(G,distmx).dists + + M0 = vcat(_locs_x',_locs_y') + OptResult = Optim.optimize(x->Objective(x,K),(x,y) -> dObjective!(x,y,K), M0, method=Optim.LBFGS(), iterations = maxiter ) + M0 = Optim.minimizer(OptResult) + + (min_x, max_x), (min_y, max_y) = extrema(M0,dims=2) + locs_x .= M0[1,:] + locs_y .= M0[2,:] + # Scale to unit square - min_x, max_x = minimum(locs_x), maximum(locs_x) - min_y, max_y = minimum(locs_y), maximum(locs_y) map!(z -> scaler(z, min_x, max_x), locs_x, locs_x) map!(z -> scaler(z, min_y, max_y), locs_y, locs_y) From b4f0c13711991b6e401679b5efe36f61df42d8aa Mon Sep 17 00:00:00 2001 From: crinders Date: Mon, 24 Jun 2019 13:47:45 -0400 Subject: [PATCH 4/5] Removed unnecessary dyjkstra component. --- src/KamadaKawai.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/KamadaKawai.jl b/src/KamadaKawai.jl index 12c5e51..71b5192 100644 --- a/src/KamadaKawai.jl +++ b/src/KamadaKawai.jl @@ -84,10 +84,6 @@ function kamada_kawai_layout(G, X=nothing; maxiter=100, distmx=weights(G) ) # The optimal distance between vertices # Currently only LightGraphs are supported using the Dijkstra shortest path algorithm - K = zeros(N,N) - for v in 1:N - K[:,v] = dijkstra_shortest_paths(G,v).dists - end K = floyd_warshall_shortest_paths(G,distmx).dists From 695feb2dd1e70b08c871d196ee3a28d2a39d92e1 Mon Sep 17 00:00:00 2001 From: Simon Schoelly Date: Fri, 28 Jun 2019 13:22:13 +0200 Subject: [PATCH 5/5] Update Project.toml Accidentally had `[compat]` twice in Project.TOML --- Project.toml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index 3d2864d..df0ebe4 100644 --- a/Project.toml +++ b/Project.toml @@ -17,9 +17,9 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] -Compose = "0.7" -LightGraphs = "1.1" -VisualRegressionTests = "0.2" +"Compose" = ">= 0.7" +"LightGraphs" = ">= 1.1" +"VisualRegressionTests" = ">= 0.2" [extras] Cairo = "159f3aea-2a34-519c-b102-8c37f9878175" @@ -29,8 +29,3 @@ VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92" [targets] test = ["Test", "Cairo", "ImageMagick", "VisualRegressionTests"] - -[compat] -"Compose" = ">= 0.7" -"LightGraphs" = ">= 1.1" -"VisualRegressionTests" = ">= 0.2"