| // 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 spectral |
| |
| import ( |
| "math" |
| |
| "gonum.org/v1/gonum/graph" |
| "gonum.org/v1/gonum/mat" |
| ) |
| |
| // 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 := graph.NodesOf(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 { |
| uid := u.ID() |
| to := graph.NodesOf(g.From(uid)) |
| l.SetSym(j, j, 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) |
| } |
| } |
| } |
| |
| 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 := graph.NodesOf(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 { |
| uid := u.ID() |
| to := graph.NodesOf(g.From(uid)) |
| if len(to) == 0 { |
| continue |
| } |
| l.SetSym(j, j, 1) |
| 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 { |
| to := g.From(vid) |
| k := to.Len() |
| if k < 0 { |
| k = len(graph.NodesOf(to)) |
| } |
| l.SetSym(indexOf[vid], j, -1/(squdeg*math.Sqrt(float64(k)))) |
| } |
| } |
| } |
| |
| 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 := graph.NodesOf(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 := graph.NodesOf(g.From(uid)) |
| 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} |
| } |