Skip to content

Commit c781745

Browse files
committed
handle unconnected graphs in Stress
1 parent 9b1d655 commit c781745

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

src/stress.jl

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using LinearAlgebra: checksquare, norm, pinv, mul!
1+
using LinearAlgebra: checksquare, norm, pinv, mul!, nullspace
22

33
export Stress, stress
44

@@ -57,8 +57,13 @@ The main equation to solve is (8) in Gansner, Koren and North (2005,
5757
5858
Create rng based on seed. Defaults to `MersenneTwister`, can be specified
5959
by overwriting `DEFAULT_RNG[]`
60+
61+
- `uncon_dist=(maxdist, Ncomps)->maxdist*Ncomps^(1/3)`
62+
63+
Per default, unconnected vertices in the graph get a pairwise "ideal" distance which scales
64+
with the number of connected components and the maximum distance within the components.
6065
"""
61-
@addcall struct Stress{Dim,Ptype,IT<:Union{Symbol,Int},FT<:AbstractFloat,M<:AbstractMatrix,RNG} <:
66+
@addcall struct Stress{Dim,Ptype,IT<:Union{Symbol,Int},FT<:AbstractFloat,M<:AbstractMatrix,F,RNG} <:
6267
IterativeLayout{Dim,Ptype}
6368
iterations::IT
6469
abstols::FT
@@ -67,6 +72,7 @@ The main equation to solve is (8) in Gansner, Koren and North (2005,
6772
weights::M
6873
initialpos::Dict{Int,Point{Dim,Ptype}}
6974
pin::Dict{Int,SVector{Dim,Bool}}
75+
uncon_dist::F
7076
rng::RNG
7177
end
7278

@@ -76,6 +82,7 @@ function Stress(; dim=2,
7682
abstols=0.0,
7783
reltols=10e-6,
7884
abstolx=10e-6,
85+
uncon_dist=(maxd, N)->maxd*N^(1/3),
7986
weights=Array{Float64}(undef, 0, 0),
8087
initialpos=[], pin=[],
8188
seed=1, rng=DEFAULT_RNG[](seed))
@@ -86,8 +93,8 @@ function Stress(; dim=2,
8693

8794
_initialpos, _pin = _sanitize_initialpos_pin(dim, Ptype, initialpos, pin)
8895

89-
IT, FT, WT, RNG = typeof(iterations), typeof(abstols), typeof(weights), typeof(rng)
90-
Stress{dim,Ptype,IT,FT,WT,RNG}(iterations, abstols, reltols, abstolx, weights, _initialpos, _pin, rng)
96+
IT, FT, WT, RNG, F = typeof(iterations), typeof(abstols), typeof(weights), typeof(rng), typeof(uncon_dist)
97+
Stress{dim,Ptype,IT,FT,WT,F,RNG}(iterations, abstols, reltols, abstolx, weights, _initialpos, _pin, uncon_dist, rng)
9198
end
9299

93100
function Base.iterate(iter::LayoutIterator{<:Stress{Dim,Ptype,IT,FT}}) where {Dim,Ptype,IT,FT}
@@ -116,6 +123,20 @@ function Base.iterate(iter::LayoutIterator{<:Stress{Dim,Ptype,IT,FT}}) where {Di
116123
# if user provided weights not empty try those
117124
make_symmetric!(δ)
118125
distances = pairwise_distance(δ, FT)
126+
127+
# check for unconnected commponents and set pairwise distances
128+
if any(isequal(typemax(FT)), distances)
129+
maxd = maximum(filter(isfinite, distances))
130+
laplacian = weightedlaplacian(δ)
131+
Ncomponents = size(nullspace(laplacian),2)
132+
_dist = algo.uncon_dist(maxd, Ncomponents)
133+
for i in eachindex(distances)
134+
if isinf(distances[i])
135+
distances[i] = _dist
136+
end
137+
end
138+
end
139+
119140
weights = isempty(algo.weights) ? distances .^ (-2) : algo.weights
120141

121142
@assert length(startpos) == size(δ, 1) == size(δ, 2) == size(weights, 1) == size(weights, 2) "Wrong size of weights?"

test/runtests.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ using StaticArrays
77
using StableRNGs
88
using Test
99
using Random
10+
using LinearAlgebra
11+
12+
NetworkLayout.DEFAULT_RNG[] = StableRNG
1013

1114
function jagmesh()
1215
jagmesh_path = joinpath(dirname(@__FILE__), "jagmesh1.mtx")
@@ -148,6 +151,40 @@ jagmesh_adj = jagmesh()
148151
0 0 0 0 0])
149152

150153
end
154+
155+
@testset "test unconnected graphs" begin
156+
= [0 1 1;
157+
1 0 1;
158+
1 1 0]
159+
N = 10# 10 fully connected 3-node graphs
160+
δ = kron(Matrix(I, N, N), _δ);
161+
g = SimpleGraph(δ)
162+
pos = Stress()(g)
163+
@test all(norm.(pos) .< 2)
164+
# graphplot(g; layout=Stress())
165+
166+
sgkeys = [:bull, :chvatal, :cubical, :desargues,
167+
:diamond, :dodecahedral, :frucht, :heawood,
168+
:house, :housex, :icosahedral, :karate, :krackhardtkite,
169+
:moebiuskantor, :octahedral, :pappus, :petersen,
170+
:sedgewickmaze, :tetrahedral, :truncatedcube,
171+
:truncatedtetrahedron, :truncatedtetrahedron_dir, :tutte]
172+
gs = filter(!is_directed, smallgraph.(sgkeys))
173+
absdim = mapreduce(nv, +, gs)
174+
adj = zeros(Int, absdim, absdim);
175+
let i = 1
176+
for g in gs
177+
r = i:i+nv(g)-1
178+
adj[r, r] .= adjacency_matrix(g)
179+
i += nv(g)
180+
end
181+
end
182+
g = SimpleGraph(adj)
183+
184+
pos = Stress()(g)
185+
@test all(norm.(pos) .< 20)
186+
# graphplot(g; layout=Stress())
187+
end
151188
end
152189

153190
@testset "Testing Spring Algorithm" begin

0 commit comments

Comments
 (0)