mirror of
https://github.com/gonum/gonum.git
synced 2025-10-06 23:52:47 +08:00
213 lines
5.8 KiB
Go
213 lines
5.8 KiB
Go
// Copyright ©2017 The Gonum Authors. All rights reserved.
|
||
// Use of this source code is governed by a BSD-style
|
||
// license that can be found in the LICENSE file.
|
||
|
||
package network
|
||
|
||
import (
|
||
"math"
|
||
|
||
"gonum.org/v1/gonum/graph"
|
||
"gonum.org/v1/gonum/mat"
|
||
)
|
||
|
||
// Diffuse performs a heat diffusion across nodes of the undirected
|
||
// graph described by the given Laplacian using the initial heat distribution,
|
||
// h, according to the Laplacian with a diffusion time of t.
|
||
// The resulting heat distribution is returned, written into the map dst and
|
||
// returned,
|
||
// d = exp(-Lt)×h
|
||
// where L is the graph Laplacian. Indexing into h and dst is defined by the
|
||
// Laplacian Index field. If dst is nil, a new map is created.
|
||
//
|
||
// Nodes without corresponding entries in h are given an initial heat of zero,
|
||
// and entries in h without a corresponding node in the original graph are
|
||
// not altered when written to dst.
|
||
func Diffuse(dst, h map[int64]float64, by Laplacian, t float64) map[int64]float64 {
|
||
heat := make([]float64, len(by.Index))
|
||
for id, i := range by.Index {
|
||
heat[i] = h[id]
|
||
}
|
||
v := mat.NewVecDense(len(heat), heat)
|
||
|
||
var m, tl mat.Dense
|
||
tl.Scale(-t, by)
|
||
m.Exp(&tl)
|
||
v.MulVec(&m, v)
|
||
|
||
if dst == nil {
|
||
dst = make(map[int64]float64)
|
||
}
|
||
for i, n := range heat {
|
||
dst[by.Nodes[i].ID()] = n
|
||
}
|
||
return dst
|
||
}
|
||
|
||
// DiffuseToEquilibrium performs a heat diffusion across nodes of the
|
||
// graph described by the given Laplacian using the initial heat
|
||
// distribution, h, according to the Laplacian until the update function
|
||
// h_{n+1} = h_n - L×h_n
|
||
// results in a 2-norm update difference within tol, or iters updates have
|
||
// been made.
|
||
// The resulting heat distribution is returned as eq, written into the map dst,
|
||
// and a boolean indicating whether the equilibrium converged to within tol.
|
||
// Indexing into h and dst is defined by the Laplacian Index field. If dst
|
||
// is nil, a new map is created.
|
||
//
|
||
// Nodes without corresponding entries in h are given an initial heat of zero,
|
||
// and entries in h without a corresponding node in the original graph are
|
||
// not altered when written to dst.
|
||
func DiffuseToEquilibrium(dst, h map[int64]float64, by Laplacian, tol float64, iters int) (eq map[int64]float64, ok bool) {
|
||
heat := make([]float64, len(by.Index))
|
||
for id, i := range by.Index {
|
||
heat[i] = h[id]
|
||
}
|
||
v := mat.NewVecDense(len(heat), heat)
|
||
|
||
last := make([]float64, len(by.Index))
|
||
for id, i := range by.Index {
|
||
last[i] = h[id]
|
||
}
|
||
lastV := mat.NewVecDense(len(last), last)
|
||
|
||
var tmp mat.VecDense
|
||
for {
|
||
iters--
|
||
if iters < 0 {
|
||
break
|
||
}
|
||
lastV, v = v, lastV
|
||
tmp.MulVec(by.Matrix, lastV)
|
||
v.SubVec(lastV, &tmp)
|
||
if normDiff(heat, last) < tol {
|
||
ok = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if dst == nil {
|
||
dst = make(map[int64]float64)
|
||
}
|
||
for i, n := range v.RawVector().Data {
|
||
dst[by.Nodes[i].ID()] = n
|
||
}
|
||
return dst, ok
|
||
}
|
||
|
||
// Laplacian is a graph Laplacian matrix.
|
||
type Laplacian struct {
|
||
// Matrix holds the Laplacian matrix.
|
||
mat.Matrix
|
||
|
||
// Nodes holds the input graph nodes.
|
||
Nodes []graph.Node
|
||
|
||
// Index is a mapping from the graph
|
||
// node IDs to row and column indices.
|
||
Index map[int64]int
|
||
}
|
||
|
||
// NewLaplacian returns a Laplacian matrix for the simple undirected graph g.
|
||
// The Laplacian is defined as D-A where D is a diagonal matrix holding the
|
||
// degree of each node and A is the graph adjacency matrix of the input graph.
|
||
// If g contains self edges, NewLaplacian will panic.
|
||
func NewLaplacian(g graph.Undirected) Laplacian {
|
||
nodes := g.Nodes()
|
||
indexOf := make(map[int64]int, len(nodes))
|
||
for i, n := range nodes {
|
||
id := n.ID()
|
||
indexOf[id] = i
|
||
}
|
||
|
||
l := mat.NewSymDense(len(nodes), nil)
|
||
for j, u := range nodes {
|
||
to := g.From(u)
|
||
l.SetSym(j, j, float64(len(to)))
|
||
uid := u.ID()
|
||
for _, v := range to {
|
||
vid := v.ID()
|
||
if uid == vid {
|
||
panic("network: self edge in graph")
|
||
}
|
||
if uid < vid {
|
||
l.SetSym(indexOf[vid], j, -1)
|
||
}
|
||
}
|
||
}
|
||
|
||
return Laplacian{Matrix: l, Nodes: nodes, Index: indexOf}
|
||
}
|
||
|
||
// NewSymNormLaplacian returns a symmetric normalized Laplacian matrix for the
|
||
// simple undirected graph g.
|
||
// The normalized Laplacian is defined as I-D^(-1/2)AD^(-1/2) where D is a
|
||
// diagonal matrix holding the degree of each node and A is the graph adjacency
|
||
// matrix of the input graph.
|
||
// If g contains self edges, NewSymNormLaplacian will panic.
|
||
func NewSymNormLaplacian(g graph.Undirected) Laplacian {
|
||
nodes := g.Nodes()
|
||
indexOf := make(map[int64]int, len(nodes))
|
||
for i, n := range nodes {
|
||
id := n.ID()
|
||
indexOf[id] = i
|
||
}
|
||
|
||
l := mat.NewSymDense(len(nodes), nil)
|
||
for j, u := range nodes {
|
||
to := g.From(u)
|
||
if len(to) == 0 {
|
||
continue
|
||
}
|
||
l.SetSym(j, j, 1)
|
||
uid := u.ID()
|
||
squdeg := math.Sqrt(float64(len(to)))
|
||
for _, v := range to {
|
||
vid := v.ID()
|
||
if uid == vid {
|
||
panic("network: self edge in graph")
|
||
}
|
||
if uid < vid {
|
||
l.SetSym(indexOf[vid], j, -1/(squdeg*math.Sqrt(float64(len(g.From(v))))))
|
||
}
|
||
}
|
||
}
|
||
|
||
return Laplacian{Matrix: l, Nodes: nodes, Index: indexOf}
|
||
}
|
||
|
||
// NewRandomWalkLaplacian returns a damp-scaled random walk Laplacian matrix for
|
||
// the simple graph g.
|
||
// The random walk Laplacian is defined as I-D^(-1)A where D is a diagonal matrix
|
||
// holding the degree of each node and A is the graph adjacency matrix of the input
|
||
// graph.
|
||
// If g contains self edges, NewRandomWalkLaplacian will panic.
|
||
func NewRandomWalkLaplacian(g graph.Graph, damp float64) Laplacian {
|
||
nodes := g.Nodes()
|
||
indexOf := make(map[int64]int, len(nodes))
|
||
for i, n := range nodes {
|
||
id := n.ID()
|
||
indexOf[id] = i
|
||
}
|
||
|
||
l := mat.NewDense(len(nodes), len(nodes), nil)
|
||
for j, u := range nodes {
|
||
uid := u.ID()
|
||
to := g.From(u)
|
||
if len(to) == 0 {
|
||
continue
|
||
}
|
||
l.Set(j, j, 1-damp)
|
||
rudeg := (damp - 1) / float64(len(to))
|
||
for _, v := range to {
|
||
vid := v.ID()
|
||
if uid == vid {
|
||
panic("network: self edge in graph")
|
||
}
|
||
l.Set(indexOf[vid], j, rudeg)
|
||
}
|
||
}
|
||
|
||
return Laplacian{Matrix: l, Nodes: nodes, Index: indexOf}
|
||
}
|