Skip to content

Commit

Permalink
move solvers to own file
Browse files Browse the repository at this point in the history
modify tests
  • Loading branch information
axsk committed Nov 5, 2021
1 parent cf0d5ca commit 69b3ebd
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 46 deletions.
1 change: 1 addition & 0 deletions src/PCCA.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module PCCA

include("pccap.jl")
include("schur.jl")

export pcca

Expand Down
46 changes: 4 additions & 42 deletions src/pccap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ function pcca(T::Matrix, n::Integer; optimize = false, solver=BaseSolver())
israte = isratematrix(T)
pi = stationarydensity(T, israte)
X, λ = schurvectors(T, pi, n, israte, solver)
assertstructure(X, pi)
chi = makeprobabilistic(X, optimize)
end

Expand Down Expand Up @@ -37,48 +38,9 @@ function crispassignments(chi)
assignments = mapslices(argmax, chi, dims=2) |> vec
end

struct BaseSolver end
struct ArnoldiSolver end
struct KrylovSolver end

function schurvectors(T, pi, n, israte, ::BaseSolver)
Tw = Diagonal(sqrt.(pi))*T*Diagonal(1 ./ sqrt.(pi)) # rescale to keep markov property
Sw = schur!(Tw) # returns orthonormal vecs by def
Xw, λ = selclusters!(Sw, n, israte)
X = Diagonal(1 ./sqrt.(pi)) * Xw # scale back
X = X[1,1]>0 ? X : -X
X, λ
end

import ArnoldiMethod
function schurvectors(T, pi, n, israte, ::ArnoldiSolver)
s, hist = partialschur(T; nev=n, which=LM())
X = collect(s.Q)
X ./= X[1,1]
X, s.eigenvalues
end

import KrylovKit
function schurvectors(T, pi, n, israte, ::KrylovSolver)
init = pi
R, Q, v, info =schursolve(T, pi, n, :LM, KrylovKit.Arnoldi())
X = reduce(hcat, Q)
X = X ./ X[1,1]
X, v
end

# select the schurvectors corresponding to the n abs-largest eigenvalues
# if reverse==true select highest abs value, otherwise select lowest (for rate matrices)
function selclusters!(S, n, ratematrix)
ind = sortperm(abs.(S.values), rev=!ratematrix) # get indices for dominant eigenvalues
select = zeros(Bool, size(ind)) # create selection vector
select[ind[1:n]] .= true
S = ordschur!(S, select) # reorder selected vectors to the left
if !isapprox(S.T[n+1, n], 0) # check if we are cutting along a schur block
@error("conjugated eigenvector missing")
display(S.T)
end
S.vectors[:,1:n], S.values[1:n] # select first n vectors
function assertstructure(X, pi)
@assert X' * Diagonal(pi) * X I
@assert X[:,1] ones(length(pi))
end

# compute initial guess based on indexmap
Expand Down
52 changes: 52 additions & 0 deletions src/schur.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
### Schur Eigenspace solvers

struct BaseSolver end
struct ArnoldiSolver end
struct KrylovSolver end



import ArnoldiMethod
function schurvectors(T, pi, n, israte, ::ArnoldiSolver)
D = Diagonal(sqrt.(pi))
= D * T * D^-1
s, hist = ArnoldiMethod.partialschur(T̃; nev=n, which=israte ? ArnoldiMethod.LR() : ArnoldiMethod.LM())
= collect(s.Q)
X = D^-1 *
X ./= X[1,1]
X, s.eigenvalues
end

import KrylovKit
function schurvectors(T, pi, n, israte, ::KrylovSolver)
D = Diagonal(sqrt.(pi))
= D * T * D^-1
R, Q, v, info = KrylovKit.schursolve(T̃, pi, n, israte ? :LR : :LM, KrylovKit.Arnoldi())
= reduce(hcat, Q)
X = D^-1 *
X = X ./ X[1,1]
X, v
end

function schurvectors(T, pi, n, israte, ::BaseSolver)
Tw = Diagonal(sqrt.(pi))*T*Diagonal(1 ./ sqrt.(pi)) # rescale to keep markov property
Sw = schur!(Tw) # returns orthonormal vecs by def
Xw, λ = selclusters!(Sw, n, israte)
X = Diagonal(1 ./sqrt.(pi)) * Xw # scale back
X = X[1,1]>0 ? X : -X
X, λ
end

# select the schurvectors corresponding to the n abs-largest eigenvalues
# if reverse==true select highest abs value, otherwise select lowest (for rate matrices)
function selclusters!(S, n, ratematrix)
ind = sortperm(abs.(S.values), rev=!ratematrix) # get indices for dominant eigenvalues
select = zeros(Bool, size(ind)) # create selection vector
select[ind[1:n]] .= true
S = ordschur!(S, select) # reorder selected vectors to the left
if !isapprox(S.T[n+1, n], 0) # check if we are cutting along a schur block
@error("conjugated eigenvector missing")
display(S.T)
end
S.vectors[:,1:n], S.values[1:n] # select first n vectors
end
11 changes: 7 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ using Test
P ./= sum(P, dims=2)
end

P = randomstochasticmatrix(8)

for method in [PCCA.BaseSolver, PCCA.ArnoldiSolver, PCCA.KrylovSolver]

@testset "$method" begin
χ = pcca(P, 2, solver=method())
@testset "Reversible = $rev" for rev in [true, false]
P = [randomstochasticmatrix(3+mod(i,12), rev) for i in 1:10]
@testset "Method $method" for method in [PCCA.BaseSolver, PCCA.ArnoldiSolver, PCCA.KrylovSolver]
for P in P, n in 2:size(P,1)-1
χ = pcca(P, n, solver=method(), optimize=true)
a = PCCA.crispassignments(χ)

#@show χ
@test all(sum(χ, dims=2) .≈ 1)
end
end
end

end

0 comments on commit 69b3ebd

Please sign in to comment.