Skip to content

Commit

Permalink
Merge pull request #87 from yuehhua/develop
Browse files Browse the repository at this point in the history
Inplace normalized adjacency matrix
  • Loading branch information
yuehhua authored Feb 19, 2022
2 parents f75cf6d + 131f7ef commit 77907ad
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 23 deletions.
18 changes: 14 additions & 4 deletions src/featuredgraph.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const MATRIX_TYPES = [:adjm, :laplacian, :normalized, :scaled]
const MATRIX_TYPES = [:adjm, :normedadjm, :laplacian, :normalized, :scaled]
const DIRECTEDS = [:auto, :directed, :undirected]

abstract type AbstractFeaturedGraph end
Expand Down Expand Up @@ -70,10 +70,12 @@ mutable struct FeaturedGraph{T,Tn,Te,Tg} <: AbstractFeaturedGraph

function FeaturedGraph(graph::SparseGraph, nf::Tn, ef::Te, gf::Tg,
mt::Symbol) where {Tn<:AbstractMatrix,Te<:AbstractMatrix,Tg<:AbstractVector}
mt MATRIX_TYPES || throw(ArgumentError("matrix_type must be one of :adjm, :normedadjm, :laplacian, :normalized or :scaled"))
new{typeof(graph),Tn,Te,Tg}(graph, nf, ef, gf, mt)
end
function FeaturedGraph{T,Tn,Te,Tg}(graph, nf, ef, gf, mt
) where {T,Tn<:AbstractMatrix,Te<:AbstractMatrix,Tg<:AbstractVector}
mt MATRIX_TYPES || throw(ArgumentError("matrix_type must be one of :adjm, :normedadjm, :laplacian, :normalized or :scaled"))
new{T,Tn,Te,Tg}(T(graph), Tn(nf), Te(ef), Tg(gf), mt)
end
end
Expand All @@ -86,12 +88,12 @@ function FeaturedGraph(graph, mat_type::Symbol; directed::Symbol=:auto, T=eltype
nf=Fill(zero(T), (0, N)), ef=Fill(zero(T), (0, E)), gf=Fill(zero(T), 0))
@assert directed DIRECTEDS "directed must be one of :auto, :directed and :undirected"
dir = (directed == :auto) ? is_directed(graph) : directed == :directed
return FeaturedGraph(SparseGraph(graph, dir), nf, ef, gf, mat_type)
return FeaturedGraph(SparseGraph(graph, dir, T), nf, ef, gf, mat_type)
end

## Graph from JuliaGraphs

FeaturedGraph(graph::AbstractGraph; kwargs...) = FeaturedGraph(graph, :adjm; kwargs...)
FeaturedGraph(graph::AbstractGraph; kwargs...) = FeaturedGraph(graph, :adjm; T=Float32, kwargs...)

## Graph in adjacency list

Expand Down Expand Up @@ -135,7 +137,6 @@ check_num_nodes(g, nf) = check_num_nodes(nv(g), nf)
check_num_edges(g, ef) = check_num_edges(ne(g), ef)

function check_precondition(graph, nf, ef, mt::Symbol)
@assert mt MATRIX_TYPES "matrix_type must be one of :adjm, :laplacian, :normalized or :scaled"
check_num_edges(ne(graph), ef)
check_num_nodes(nv(graph), nf)
return
Expand All @@ -157,6 +158,7 @@ end

matrixrepr(fg::FeaturedGraph) = matrixrepr(Val(matrixtype(fg)))
matrixrepr(::Val{:adjm}) = "adjacency matrix"
matrixrepr(::Val{:normedadjm}) = "normalized adjacency matrix"
matrixrepr(::Val{:laplacian}) = "Laplacian matrix"
matrixrepr(::Val{:normalized}) = "normalized Laplacian"
matrixrepr(::Val{:scaled}) = "scaled Laplacian"
Expand Down Expand Up @@ -319,6 +321,14 @@ scaled_laplacian(fg::FeaturedGraph, T::DataType=eltype(graph(fg))) = scaled_lapl

## Inplace operations

function normalized_adjacency_matrix!(fg::FeaturedGraph, T::DataType=eltype(graph(fg)); selfloop::Bool=false)
if fg.matrix_type == :adjm
fg.graph.S .= normalized_adjacency_matrix(graph(fg), T; selfloop=selfloop)
fg.matrix_type = :normedadjm
end
fg
end

function laplacian_matrix!(fg::FeaturedGraph, T::DataType=eltype(graph(fg)); dir::Symbol=:out)
if fg.matrix_type == :adjm
fg.graph.S .= laplacian_matrix(graph(fg), T; dir=dir)
Expand Down
34 changes: 23 additions & 11 deletions src/sparsegraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,38 @@ function SparseGraph{D}(A::AbstractMatrix{Tv}, edges::AbstractVector{Ti}, E::Int
return SparseGraph{D,typeof(A),typeof(edges),typeof(E)}(A, edges, E)
end

function SparseGraph(A::AbstractMatrix{Tv}, edges::AbstractVector{Ti}, directed::Bool) where {Tv,Ti}
function SparseGraph(
A::AbstractMatrix{Tv},
edges::AbstractVector{Ti},
directed::Bool,
::Type{T}=eltype(A)
) where {Tv,Ti,T}
E = length(unique(edges))
spA = SparseMatrixCSC{Tv,Ti}(A)
spA = (Tv === T) ? SparseMatrixCSC{Tv,Ti}(A) : SparseMatrixCSC{T,Ti}(A)
return SparseGraph{directed,typeof(spA),typeof(edges),typeof(E)}(spA, edges, E)
end

SparseGraph(A::SparseCSC, directed::Bool) = SparseGraph(A, order_edges(A, directed=directed), directed)
SparseGraph(A::AbstractMatrix, directed::Bool) = SparseGraph(sparsecsc(A), directed)
SparseGraph(A::SparseCSC, directed::Bool, ::Type{T}=eltype(A)) where {T} =
SparseGraph(A, order_edges(A, directed=directed), directed, T)
SparseGraph(A::AbstractMatrix, directed::Bool, ::Type{T}=eltype(A)) where {T} =
SparseGraph(sparsecsc(A), directed, T)

function SparseGraph(adjl::AbstractVector{T}, directed::Bool) where {T<:AbstractVector}
function SparseGraph(
adjl::AbstractVector{T},
directed::Bool,
::Type{Te}=eltype(eltype(adjl))
) where {T<:AbstractVector,Te}
n = length(adjl)
colptr, rowval, nzval = to_csc(adjl)
spA = SparseMatrixCSC(n, n, colptr, rowval, nzval)
spA = SparseMatrixCSC(n, n, UInt32.(colptr), UInt32.(rowval), Te.(nzval))
return SparseGraph(spA, directed)
end

SparseGraph(g::G, directed::Bool=is_directed(G)) where {G<:AbstractSimpleGraph} =
SparseGraph(g.fadjlist, directed)
SparseGraph(g::G, directed::Bool=is_directed(G), ::Type{T}=eltype(g)) where {G<:AbstractSimpleGraph,T} =
SparseGraph(g.fadjlist, directed, T)

SparseGraph(g::G, directed::Bool=is_directed(G)) where {G<:AbstractSimpleWeightedGraph} =
SparseGraph(weights(g)', directed)
SparseGraph(g::G, directed::Bool=is_directed(G), ::Type{T}=eltype(g)) where {G<:AbstractSimpleWeightedGraph,T} =
SparseGraph(weights(g)', directed, T)

function to_csc(adjl::AbstractVector{T}) where {T<:AbstractVector}
ET = eltype(adjl[1])
Expand All @@ -76,7 +87,8 @@ end
SparseArrays.sparse(sg::SparseGraph) = sg.S
Base.collect(sg::SparseGraph) = collect(sg.S)

Base.show(io::IO, sg::SparseGraph) = print(io, "SparseGraph(#V=", nv(sg), ", #E=", ne(sg), ")")
Base.show(io::IO, sg::SparseGraph) =
print(io, "SparseGraph{", eltype(sg), "}(#V=", nv(sg), ", #E=", ne(sg), ")")

Graphs.nv(sg::SparseGraph) = size(sg.S, 1)
Graphs.ne(sg::SparseGraph) = sg.E
Expand Down
5 changes: 5 additions & 0 deletions test/linalg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@
@test eltype(NL) == T
end

fg_ = GraphSignals.normalized_adjacency_matrix!(deepcopy(fg), T)
@test graph(fg_).S T.(I - norm_lap)
@test matrixtype(fg_) == :normedadjm
@test repr(fg_) == "FeaturedGraph(\n\tUndirected graph with (#V=4, #E=4) in normalized adjacency matrix,\n)"

fg_ = normalized_laplacian!(deepcopy(fg), T)
@test graph(fg_).S T.(norm_lap)
@test matrixtype(fg_) == :normalized
Expand Down
19 changes: 11 additions & 8 deletions test/sparsegraph.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@testset "SparseGraph" begin
T = Float32
@testset "undirected graph" begin
# undirected graph with self loop
V = 5
Expand Down Expand Up @@ -28,27 +29,28 @@
add_edge!(wug, 3, 3, 5); add_edge!(wug, 4, 5, 2)

for g in [adjm, adjl, ug, wug]
sg = SparseGraph(g, false)
sg = SparseGraph(g, false, T)
@test (sg.S .!= 0) == adjm
@test sg.edges == [1, 3, 4, 1, 2, 3, 5, 4, 5]
@test sg.E == E
@test eltype(sg) == T
end
end

@testset "conversions" begin
sg = SparseGraph(adjm, false)
sg = SparseGraph(adjm, false, T)
@test GraphSignals.adjacency_matrix(sg) == adjm
@test collect(sg) == adjm
@test SparseArrays.sparse(sg) == adjm
@test adjacency_list(sg) == adjl
end

sg = SparseGraph(adjm, false)
sg = SparseGraph(adjm, false, T)
@test nv(sg) == V
@test ne(sg) == E
@test !Graphs.is_directed(sg)
@test !Graphs.is_directed(typeof(sg))
@test repr(sg) == "SparseGraph(#V=5, #E=5)"
@test repr(sg) == "SparseGraph{Float32}(#V=5, #E=5)"
@test Graphs.has_self_loops(sg)

@test Graphs.has_vertex(sg, 3)
Expand Down Expand Up @@ -128,27 +130,28 @@
add_edge!(wdg, 5, 4, 4)

for g in [adjm, adjl, dg, wdg]
sg = SparseGraph(g, true)
sg = SparseGraph(g, true, T)
@test (sg.S .!= 0) == adjm
@test sg.edges == collect(1:7)
@test sg.E == E
@test eltype(sg) == T
end
end

@testset "conversions" begin
sg = SparseGraph(adjm, true)
sg = SparseGraph(adjm, true, T)
@test GraphSignals.adjacency_matrix(sg) == adjm
@test collect(sg) == adjm
@test SparseArrays.sparse(sg) == adjm
@test adjacency_list(sg) == adjl
end

sg = SparseGraph(adjm, true)
sg = SparseGraph(adjm, true, T)
@test nv(sg) == V
@test ne(sg) == E
@test Graphs.is_directed(sg)
@test Graphs.is_directed(typeof(sg))
@test repr(sg) == "SparseGraph(#V=5, #E=7)"
@test repr(sg) == "SparseGraph{Float32}(#V=5, #E=7)"
@test Graphs.has_self_loops(sg)

@test Graphs.has_vertex(sg, 3)
Expand Down

0 comments on commit 77907ad

Please sign in to comment.