diff --git a/src/sfdp.jl b/src/sfdp.jl index f192cdd..5055b2e 100644 --- a/src/sfdp.jl +++ b/src/sfdp.jl @@ -24,27 +24,60 @@ the nodes. positions will be truncated or filled up with random values between [-1,1] in every coordinate. - `seed=1`: Seed for random initial positions. +- `pin=Dict{Int,Bool}()` : Anchors positions of nodes to initial position """ @addcall struct SFDP{Dim,Ptype,T<:AbstractFloat} <: IterativeLayout{Dim,Ptype} tol::T C::T K::T iterations::Int - initialpos::Vector{Point{Dim,Ptype}} + initialpos::Dict{Int, Point{Dim,Ptype}} seed::UInt + pin::Dict{Int, Bool} + function SFDP(tol::T, C::T, K::T, iterations::Int, initialpos::Dict, seed::UInt, pin::Dict) where T<:AbstractFloat + for (ix, p) in pin + if !haskey(initialpos, ix) && p + @warn "No coordinate provided for pinned position $ix" + end + end + dim = get_pt_dim(initialpos) + ptype = get_pt_ptype(initialpos) + new{dim, ptype, typeof(tol)}(tol, C, K, iterations, initialpos, seed, pin) + end end # TODO: check SFDP default parameters -function SFDP(; dim=2, Ptype=Float64, tol=1.0, C=0.2, K=1.0, iterations=100, initialpos=Point{dim,Ptype}[], - seed=1) - if !isempty(initialpos) - initialpos = Point.(initialpos) - Ptype = eltype(eltype(initialpos)) - # TODO fix initial pos if list has points of multiple types - Ptype == Any && error("Please provide list of Point{N,T} with same T") - dim = length(eltype(initialpos)) - end - return SFDP{dim,Ptype,typeof(tol)}(tol, C, K, iterations, initialpos, seed) +function SFDP(; dim=2, Ptype=Float64, tol=1.0, C=0.2, K=1.0, iterations=100, initialpos=Dict{Int, Point{dim,Ptype}}(), + seed::UInt=UInt(1), pin = Dict{Int, Bool}()) + return SFDP(tol, C, K, iterations, initialpos, seed, pin) +end + +function SFDP(tol::T, C::T, K::T, iterations::Int, initialpos::Vector, seed::UInt, pin) where T<:AbstractFloat + initialpos = Dict(zip(1:length(initialpos), Point.(initialpos))) + # TODO fix initial pos if list has points of multiple types + return SFDP(tol, C, K, iterations, initialpos, seed, pin) +end + +function SFDP(tol::T, C::T, K::T, iterations::Int, initialpos::Dict{Int, <:Point}, seed::UInt, pin::Vector{Bool}) where T<:AbstractFloat + dim = get_pt_dim(initialpos) + fixed = Dict(zip(1:length(pin), pin)) + return SFDP(tol, C, K, iterations, initialpos, seed, fixed) +end + +function get_pt_ptype(ip::Dict{Int, <:Point}) + ptype = eltype(eltype(values(ip))) + ptype == Any && error("Please provide list of Point{N,T} with same T") + return ptype +end + +function get_pt_dim(ip::Vector) + dims = length.(ip) + length(unique(dims)) != 1 && error("Please provide list of Point{N,T} with same N") + return first(dims) +end + +function get_pt_dim(ip::Dict{Int, <:Point}) + return typeof(ip).parameters[2].parameters[1] #based on type of point end function Base.iterate(iter::LayoutIterator{SFDP{Dim,Ptype,T}}) where {Dim,Ptype,T} @@ -54,12 +87,23 @@ function Base.iterate(iter::LayoutIterator{SFDP{Dim,Ptype,T}}) where {Dim,Ptype, rng = MersenneTwister(algo.seed) startpos = Vector{Point{Dim,Ptype}}(undef, N) # take the first - for i in 1:min(N, M) - startpos[i] = algo.initialpos[i] + for (i, p) in algo.initialpos + startpos[i] = p end + + # create bounds for random initial positions + if isempty(algo.initialpos) + startposbounds = (min = zero(Point{Dim, Ptype}), max = zero((Point{Dim, Ptype})) .+ one(Ptype)) + else + startposbounds = ( + min = Point{Dim, Ptype}([minimum((p[d] for p in values(algo.initialpos))) for d in 1:Dim]), + max = Point{Dim, Ptype}([maximum((p[d] for p in values(algo.initialpos))) for d in 1:Dim]) + ) + end + # fill the rest with random points - for i in (M + 1):N - startpos[i] = 2 .* rand(rng, Point{Dim,Ptype}) .- 1 + for i in setdiff(1:N, keys(algo.initialpos)) + startpos[i] = (startposbounds.max .- startposbounds.min) .* (2 .* rand(rng, Point{Dim,Ptype}) .- 1) .+ startposbounds.min end # iteratorstate: (#iter, energy, step, progress, old pos, stopflag) return startpos, (1, typemax(T), one(T), 0, startpos, false) @@ -93,7 +137,9 @@ function Base.iterate(iter::LayoutIterator{<:SFDP}, state) ((locs[j] .- locs[i]) / norm(locs[j] .- locs[i]))) end end - locs[i] = locs[i] .+ step .* (force ./ norm(force)) + if !get(algo.pin, i, false) + locs[i] = locs[i] .+ step .* (force ./ norm(force)) + end energy = energy + norm(force)^2 end step, progress = update_step(step, energy, energy0, progress) diff --git a/test/runtests.jl b/test/runtests.jl index c84c651..1c651fd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,7 +6,7 @@ using SparseArrays: sparse using Test function jagmesh() - jagmesh_path = joinpath(dirname(@__FILE__), "jagmesh1.mtx") + jagmesh_path = joinpath(pkgdir(NetworkLayout), "test", "jagmesh1.mtx") array = round.(Int, open(readdlm, jagmesh_path)) row = array[:, 1] col = array[:, 2] @@ -29,6 +29,11 @@ jagmesh_adj = jagmesh() ip = [Point2f(1, 2)] algo = SFDP(; initialpos=ip) @test algo isa SFDP{2,Float32} + ip = Dict(1=>Point(1.0,3.0)) + algo = SFDP(; initialpos = ip) + @test algo isa SFDP{2, Float64} + p = [true, true] + algo = SFDP(; initialpos = ip, pin = p) end @testset "iterator size" begin @@ -46,9 +51,9 @@ jagmesh_adj = jagmesh() @testset "Testing Jagmesh1 graph" begin println("SFDP Jagmesh1") - positions = @time SFDP(; dim=2, Ptype=Float32, tol=0.9, K=1, iterations=10)(jagmesh_adj) + positions = @time SFDP(; dim=2, Ptype=Float32, tol=0.9, K=1.0, iterations=10)(jagmesh_adj) @test typeof(positions) == Vector{Point2f} - positions = @time SFDP(; dim=3, Ptype=Float32, tol=0.9, K=1, iterations=10)(jagmesh_adj) + positions = @time SFDP(; dim=3, Ptype=Float32, tol=0.9, K=1.0, iterations=10)(jagmesh_adj) @test typeof(positions) == Vector{Point3f} end @@ -56,12 +61,14 @@ jagmesh_adj = jagmesh() println("SFDP Wheelgraph") g = wheel_graph(10) adj_matrix = adjacency_matrix(g) - positions = @time SFDP(; dim=2, Ptype=Float32, tol=0.1, K=1)(adj_matrix) + positions = @time SFDP(; dim=2, Ptype=Float32, tol=0.1, K=1.0)(adj_matrix) @test typeof(positions) == Vector{Point2f} - @test positions == sfdp(adj_matrix; dim=2, Ptype=Float32, tol=0.1, K=1) - positions = @time SFDP(; dim=3, Ptype=Float32, tol=0.1, K=1)(adj_matrix) + @test positions == sfdp(adj_matrix; dim=2, Ptype=Float32, tol=0.1, K=1.0) + positions = @time SFDP(; dim=3, Ptype=Float32, tol=0.1, K=1.0)(adj_matrix) @test typeof(positions) == Vector{Point3f} - @test positions == sfdp(adj_matrix; dim=3, Ptype=Float32, tol=0.1, K=1) + @test positions == sfdp(adj_matrix; dim=3, Ptype=Float32, tol=0.1, K=1.0) + ip = [Point2f(3.0, 1.0)] + @test ip[1] == sfdp(adj_matrix; dim=3, Ptype=Float32, tol=0.1, K=1.0, initialpos = ip, pin = [true])[1] end end