Skip to content

Commit fd9f90a

Browse files
alystgdalle
andauthored
Some normalized_cut() fixes (#84)
* eigs(): cleanup, sort complex eigvals by real part * normalized_cut(): check size of weightmatrix early * normalized_cut(): take care of isolated vertices because they result in degenerated matrix * normalized_cut(): check if 2nd eigvec is missing * normalized_cut(): use real part of eigenvec --------- Co-authored-by: Guillaume Dalle <[email protected]>
1 parent ab9fcaf commit fd9f90a

File tree

2 files changed

+23
-15
lines changed

2 files changed

+23
-15
lines changed

src/graphcut/normalized_cut.jl

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,26 +117,39 @@ end
117117

118118
function _recursive_normalized_cut(W, thres, num_cuts)
119119
m, n = size(W)
120-
D = Diagonal(vec(sum(W; dims=2)))
121-
122-
m == 1 && return [1]
120+
(m <= 1) && return ones(Int, m) # trivial
121+
D = Diagonal(vec(sum(W, dims=2)))
122+
123+
# check that the diagonal is not degenerated as otherwise invDroot errors
124+
dnz = abs.(diag(D)) .>= 1E-16
125+
if !all(dnz)
126+
# vertices with incident edges summing to almost zero
127+
# are not connected to the rest of the subnetwork,
128+
# put them to separate modules and cut the remaining submatrix
129+
nzlabels = _recursive_normalized_cut(W[dnz, dnz], thres, num_cuts)
130+
nzix = 0
131+
zix = maximum(nzlabels)
132+
return Int[nz ? nzlabels[nzix += 1] : (zix += 1) for nz in dnz]
133+
end
123134

124-
# get eigenvector corresponding to second smallest eigenvalue
135+
# get eigenvector corresponding to the second smallest generalized eigenvalue:
125136
# v = eigs(D-W, D, nev=2, which=SR())[2][:,2]
126137
# At least some versions of ARPACK have a bug, this is a workaround
127138
invDroot = sqrt.(inv(D)) # equal to Cholesky factorization for diagonal D
128139
if n > 12
129-
λ, Q = eigs(invDroot' * (D - W) * invDroot; nev=12, which=SR())
130-
ret = real(Q[:, 2])
140+
_, Q = eigs(invDroot' * (D - W) * invDroot, nev=12, which=SR())
141+
(size(Q, 2) <= 1) && return collect(1:m) # no 2nd eigenvector
142+
ret = convert(Vector, real(view(Q, :, 2)))
131143
else
132144
ret = eigen(Matrix(invDroot' * (D - W) * invDroot)).vectors[:, 2]
133145
end
134-
v = invDroot * ret
146+
v = real(invDroot * ret)
135147

136148
# perform n-cuts with different partitions of v and find best one
137149
min_cost = Inf
138150
best_thres = -1
139-
for t in range(minimum(v); stop=maximum(v), length=num_cuts)
151+
vmin, vmax = extrema(v)
152+
for t in range(vmin, stop=vmax, length=num_cuts)
140153
cut = v .> t
141154
cost = _normalized_cut_cost(cut, W, D)
142155
if cost < min_cost

src/linalg/LinAlg.jl

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,8 @@ function eigs(A; kwargs...)
5151
schr = partialschur(A; kwargs...)
5252
vals, vectors = partialeigen(schr[1])
5353
reved = (kwargs[:which] == LR() || kwargs[:which] == LM())
54-
k::Int = get(kwargs, :nev, length(vals))
55-
k = min(k, length(vals))
56-
perm = collect(1:k)
57-
if vals[1] isa (Real)
58-
perm = sortperm(vals; rev=reved)
59-
perm = perm[1:k]
60-
end
54+
k = min(get(kwargs, :nev, length(vals))::Int, length(vals))
55+
perm = sortperm(vals, by=real, rev=reved)[1:k]
6156
λ = vals[perm]
6257
Q = vectors[:, perm]
6358
return λ, Q

0 commit comments

Comments
 (0)