Skip to content

Commit

Permalink
handle unconnected graphs in Stress
Browse files Browse the repository at this point in the history
  • Loading branch information
hexaeder committed Dec 5, 2024
1 parent 9b1d655 commit c781745
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
29 changes: 25 additions & 4 deletions src/stress.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using LinearAlgebra: checksquare, norm, pinv, mul!
using LinearAlgebra: checksquare, norm, pinv, mul!, nullspace

export Stress, stress

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

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

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

IT, FT, WT, RNG = typeof(iterations), typeof(abstols), typeof(weights), typeof(rng)
Stress{dim,Ptype,IT,FT,WT,RNG}(iterations, abstols, reltols, abstolx, weights, _initialpos, _pin, rng)
IT, FT, WT, RNG, F = typeof(iterations), typeof(abstols), typeof(weights), typeof(rng), typeof(uncon_dist)
Stress{dim,Ptype,IT,FT,WT,F,RNG}(iterations, abstols, reltols, abstolx, weights, _initialpos, _pin, uncon_dist, rng)
end

function Base.iterate(iter::LayoutIterator{<:Stress{Dim,Ptype,IT,FT}}) where {Dim,Ptype,IT,FT}
Expand Down Expand Up @@ -116,6 +123,20 @@ function Base.iterate(iter::LayoutIterator{<:Stress{Dim,Ptype,IT,FT}}) where {Di
# if user provided weights not empty try those
make_symmetric!(δ)
distances = pairwise_distance(δ, FT)

# check for unconnected commponents and set pairwise distances
if any(isequal(typemax(FT)), distances)
maxd = maximum(filter(isfinite, distances))
laplacian = weightedlaplacian(δ)
Ncomponents = size(nullspace(laplacian),2)
_dist = algo.uncon_dist(maxd, Ncomponents)
for i in eachindex(distances)
if isinf(distances[i])
distances[i] = _dist
end
end
end

weights = isempty(algo.weights) ? distances .^ (-2) : algo.weights

@assert length(startpos) == size(δ, 1) == size(δ, 2) == size(weights, 1) == size(weights, 2) "Wrong size of weights?"
Expand Down
37 changes: 37 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ using StaticArrays
using StableRNGs
using Test
using Random
using LinearAlgebra

NetworkLayout.DEFAULT_RNG[] = StableRNG

function jagmesh()
jagmesh_path = joinpath(dirname(@__FILE__), "jagmesh1.mtx")
Expand Down Expand Up @@ -148,6 +151,40 @@ jagmesh_adj = jagmesh()
0 0 0 0 0])

end

@testset "test unconnected graphs" begin
= [0 1 1;
1 0 1;
1 1 0]
N = 10# 10 fully connected 3-node graphs
δ = kron(Matrix(I, N, N), _δ);
g = SimpleGraph(δ)
pos = Stress()(g)
@test all(norm.(pos) .< 2)
# graphplot(g; layout=Stress())

sgkeys = [:bull, :chvatal, :cubical, :desargues,
:diamond, :dodecahedral, :frucht, :heawood,
:house, :housex, :icosahedral, :karate, :krackhardtkite,
:moebiuskantor, :octahedral, :pappus, :petersen,
:sedgewickmaze, :tetrahedral, :truncatedcube,
:truncatedtetrahedron, :truncatedtetrahedron_dir, :tutte]
gs = filter(!is_directed, smallgraph.(sgkeys))
absdim = mapreduce(nv, +, gs)
adj = zeros(Int, absdim, absdim);
let i = 1
for g in gs
r = i:i+nv(g)-1
adj[r, r] .= adjacency_matrix(g)
i += nv(g)
end
end
g = SimpleGraph(adj)

pos = Stress()(g)
@test all(norm.(pos) .< 20)
# graphplot(g; layout=Stress())
end
end

@testset "Testing Spring Algorithm" begin
Expand Down

0 comments on commit c781745

Please sign in to comment.