Skip to content

Commit cb07573

Browse files
committed
use pairwise distance instead of adj matrix
1 parent fef0acb commit cb07573

File tree

3 files changed

+110
-21
lines changed

3 files changed

+110
-21
lines changed

src/NetworkLayout.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,28 @@ function assertsquare(M::AbstractMatrix)
107107
return a
108108
end
109109

110+
"""
111+
make_symmetric!(M::AbstractMatrix)
112+
113+
Pairwise check [i,j] and [j,i]. If one is zero, make symmetric.
114+
If both are different and nonzero throw ArgumentError.
115+
"""
116+
function make_symmetric!(A::AbstractMatrix)
117+
indsm, indsn = axes(A)
118+
for i in first(indsn):last(indsn), j in (i):last(indsn)
119+
if A[i,j] == A[j,i] # allready symmetric
120+
continue
121+
elseif iszero(A[i,j])
122+
A[i,j] = A[j,i]
123+
elseif iszero(A[j,i])
124+
A[j,i] = A[i,j]
125+
else
126+
throw(ArgumentError("Matrix can't be symmetrized!"))
127+
end
128+
end
129+
return A
130+
end
131+
110132
"""
111133
@addcall
112134

src/stress.jl

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,6 @@ function Stress(; dim=2, Ptype=Float64, iterations=:auto, abstols=(√(eps(Float
8686
Stress{dim,Ptype,IT,FT,WT}(iterations, abstols, reltols, abstolx, weights, initialpos, seed)
8787
end
8888

89-
function initialweights(D, T)::SparseMatrixCSC{T,Int64}
90-
map(D) do d
91-
x = T(d^(-2.0))
92-
return isfinite(x) ? x : zero(T)
93-
end
94-
end
95-
9689
function Base.iterate(iter::LayoutIterator{<:Stress{Dim,Ptype,IT,FT}}) where {Dim,Ptype,IT,FT}
9790
algo, δ = iter.algorithm, iter.adj_matrix
9891
N = size(δ, 1)
@@ -109,45 +102,46 @@ function Base.iterate(iter::LayoutIterator{<:Stress{Dim,Ptype,IT,FT}}) where {Di
109102
end
110103

111104
# calculate iteration if :auto
112-
maxiter = algo.iterations === :auto ? 400 * size(δ, 1)^2 : algo.iterations
105+
maxiter = algo.iterations === :auto ? 400 * N^2 : algo.iterations
113106
@assert maxiter > 0 "Iterations need to be > 0"
114107

115108
# if user provided weights not empty try those
116-
weights = isempty(algo.weights) ? initialweights(δ, FT) : algo.weights
109+
make_symmetric!(δ)
110+
distances = pairwise_distance(δ, FT)
111+
weights = isempty(algo.weights) ? distances .^ (-2) : algo.weights
117112

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

120115
Lw = weightedlaplacian(weights)
121116
pinvLw = pinv(Lw)
122-
s = stress(startpos, δ, weights)
117+
oldstress = stress(startpos, distances, weights)
123118

124-
# the `state` of the iterator is (#iter, old stress, old pos, weights, pinvLw, stopflag)
125-
return startpos, (1, s, startpos, weights, pinvLw, maxiter, false)
119+
# the `state` of the iterator is (#iter, old stress, old pos, weights, distances pinvLw, stopflag)
120+
return startpos, (1, oldstress, startpos, weights, distances, pinvLw, maxiter, false)
126121
end
127122

128123
function Base.iterate(iter::LayoutIterator{<:Stress{Dim,Ptype}}, state) where {Dim,Ptype}
129124
algo, δ = iter.algorithm, iter.adj_matrix
130-
i, oldstress, oldpos, weights, pinvLw, maxiter, stopflag = state
131-
# newstress, oldstress, X0, i = state
125+
i, oldstress, oldpos, weights, distances, pinvLw, maxiter, stopflag = state
132126

133127
if i >= maxiter || stopflag
134128
return nothing
135129
end
136130

137131
# TODO the faster way is to drop the first row and col from the iteration
138-
t = LZ(oldpos, δ, weights)
132+
t = LZ(oldpos, distances, weights)
139133
positions = similar(oldpos) # allocate new array but keep type of oldpos
140134
mul!(positions, pinvLw, (t * oldpos))
141135
@assert all(x -> all(map(isfinite, x)), positions)
142-
newstress = stress(positions, δ, weights)
136+
newstress = stress(positions, distances, weights)
143137

144138
if abs(newstress - oldstress) < algo.reltols * newstress ||
145139
abs(newstress - oldstress) < algo.abstols ||
146140
norm(positions - oldpos) < algo.abstolx
147141
stopflag = true
148142
end
149143

150-
return positions, (i + 1, newstress, positions, weights, pinvLw, maxiter, stopflag)
144+
return positions, (i + 1, newstress, positions, weights, distances, pinvLw, maxiter, stopflag)
151145
end
152146

153147
"""
@@ -160,8 +154,7 @@ Input:
160154
161155
See (1) of Reference
162156
"""
163-
function stress(positions::AbstractArray{Point{T,N}}, d=ones(T, length(positions), length(positions)),
164-
weights=initialweights(d, T)) where {T,N}
157+
function stress(positions::AbstractArray{Point{T,N}}, d, weights) where {T,N}
165158
s = zero(T)
166159
n = length(positions)
167160
@assert n == size(d, 1) == size(d, 2) == size(weights, 1) == size(weights, 2)
@@ -196,8 +189,8 @@ end
196189
Computes L^Z defined in (5) of the Reference
197190
198191
Input: Z: current layout (coordinates)
199-
d: Ideal distances (default: all 1)
200-
weights: weights (default: d.^-2)
192+
d: Ideal distances
193+
weights: weights
201194
"""
202195
function LZ(Z::AbstractVector{Point{N,T}}, d, weights) where {N,T}
203196
n = length(Z)
@@ -217,3 +210,32 @@ function LZ(Z::AbstractVector{Point{N,T}}, d, weights) where {N,T}
217210
end
218211
return L
219212
end
213+
214+
"""
215+
pairwise_distance(δ)
216+
217+
Calculate the pairwise distances of a the graph from a adjacency matrix
218+
using the Floyd-Warshall algorithm.
219+
220+
https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm
221+
"""
222+
function pairwise_distance(δ, ::Type{T}=Float64) where {T}
223+
N = size(δ, 1)
224+
d = Matrix{T}(undef, N, N)
225+
@inbounds for j in 1:N, i in 1:N
226+
if i == j
227+
d[i, j] = zero(eltype(d))
228+
elseif iszero(δ[i, j])
229+
d[i, j] = typemax(eltype(d))
230+
else
231+
d[i, j] = δ[i, j]
232+
end
233+
end
234+
235+
@inbounds for k in 1:N, i in 1:N, j in 1:N
236+
if d[i, k] + d[k, j] < d[i, j]
237+
d[i, j] = d[i, k] + d[k, j]
238+
end
239+
end
240+
return d
241+
end

test/runtests.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,23 @@ jagmesh_adj = jagmesh()
112112
@test typeof(positions) == Vector{Point3f}
113113
@test positions == stress(adj_matrix; iterations=10, dim=3, Ptype=Float32)
114114
end
115+
116+
@testset "test pairwise_distance" begin
117+
using NetworkLayout: make_symmetric!, pairwise_distance
118+
δ = [0 1 0 0 1;
119+
1 0 0 1 0;
120+
0 0 0 1 1;
121+
0 1 1 0 0;
122+
1 0 1 0 0]
123+
d = pairwise_distance(δ)
124+
125+
@test d == make_symmetric!([0 1 2 2 1;
126+
0 0 2 1 2;
127+
0 0 0 1 1;
128+
0 0 0 0 2;
129+
0 0 0 0 0])
130+
131+
end
115132
end
116133

117134
@testset "Testing Spring Algorithm" begin
@@ -299,4 +316,32 @@ jagmesh_adj = jagmesh()
299316
@test_throws ArgumentError squaregrid(M1)
300317
@test_throws ArgumentError stress(M1)
301318
end
319+
320+
@testset "make_symmetric" begin
321+
using LinearAlgebra: issymmetric
322+
using NetworkLayout: make_symmetric!
323+
324+
M = [1 0; 0 1]
325+
make_symmetric!(M)
326+
@test issymmetric(M)
327+
328+
M = [0 1; 0 0]
329+
make_symmetric!(M)
330+
@test issymmetric(M)
331+
332+
M = [0 0; 1 0]
333+
make_symmetric!(M)
334+
@test issymmetric(M)
335+
336+
M = [0 -1; 1 0]
337+
@test_throws ArgumentError make_symmetric!(M)
338+
339+
M = [1 0 2;
340+
6 2 4;
341+
2 0 3]
342+
make_symmetric!(M)
343+
@test M == [1 6 2;
344+
6 2 4;
345+
2 4 3]
346+
end
302347
end

0 commit comments

Comments
 (0)