Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support float32 and float64 #5

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import (
)

func main() {
graph := pagerank.NewGraph()
graph := pagerank.NewGraph64()

graph.Link(1, 2, 1.0)
graph.Link(1, 3, 2.0)
graph.Link(2, 3, 3.0)
graph.Link(2, 4, 4.0)
graph.Link(3, 1, 5.0)

graph.Rank(0.85, 0.000001, func(node uint32, rank float64) {
graph.Rank(0.85, 0.000001, func(node uint64, rank float64) {
fmt.Println("Node", node, "has a rank of", rank)
})
}
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/pointlander/pagerank

go 1.13
117 changes: 0 additions & 117 deletions pagerank.go

This file was deleted.

204 changes: 204 additions & 0 deletions pagerank32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
Package pagerank implements the *weighted* PageRank algorithm.
*/
package pagerank

import (
"fmt"
"runtime"
"sync"
)

var (
// NumCPU is the number of cpus
NumCPU = runtime.NumCPU()
)

// Node32 is a node in a graph
type Node32 struct {
sync.RWMutex
weight [2]float32
outbound float32
edges map[uint]float32
}

// Graph32 holds node and edge data.
type Graph32 struct {
Verbose bool
count uint
index map[uint64]uint
nodes []Node32
}

// NewGraph32 initializes and returns a new graph.
func NewGraph32(size ...int) *Graph32 {
capacity := 8
if len(size) == 1 {
capacity = size[0]
}
return &Graph32{
index: make(map[uint64]uint, capacity),
nodes: make([]Node32, 0, capacity),
}
}

// Link creates a weighted edge between a source-target node pair.
// If the edge already exists, the weight is incremented.
func (g *Graph32) Link(source, target uint64, weight float32) {
s, ok := g.index[source]
if !ok {
s = g.count
g.index[source] = s
g.nodes = append(g.nodes, Node32{})
g.count++
}

g.nodes[s].outbound += weight

t, ok := g.index[target]
if !ok {
t = g.count
g.index[target] = t
g.nodes = append(g.nodes, Node32{})
g.count++
}

if g.nodes[s].edges == nil {
g.nodes[s].edges = map[uint]float32{}
}

g.nodes[s].edges[t] += weight
}

// Rank computes the PageRank of every node in the directed graph.
// α (alpha) is the damping factor, usually set to 0.85.
// ε (epsilon) is the convergence criteria, usually set to a tiny value.
//
// This method will run as many iterations as needed, until the graph converges.
func (g *Graph32) Rank(α, ε float32, callback func(id uint64, rank float32)) {
Δ := float32(1.0)
nodes := g.nodes
inverse := 1 / float32(len(nodes))

// Normalize all the edge weights so that their sum amounts to 1.
if g.Verbose {
fmt.Println("normalize...")
}
done := make(chan bool, 8)
normalize := func(node *Node32) {
if outbound := node.outbound; outbound > 0 {
for target := range node.edges {
node.edges[target] /= outbound
}
}
done <- true
}
i, flight := 0, 0
for i < len(nodes) && flight < NumCPU {
go normalize(&nodes[i])
flight++
i++
}
for i < len(nodes) {
<-done
flight--
go normalize(&nodes[i])
flight++
i++
}
for j := 0; j < flight; j++ {
<-done
}

if g.Verbose {
fmt.Println("initialize...")
}
leak := float32(0)

a, b := 0, 1
for source := range nodes {
nodes[source].weight[a] = inverse

if nodes[source].outbound == 0 {
leak += inverse
}
}

update := func(adjustment float32, node *Node32) {
node.RLock()
aa := α * node.weight[a]
node.RUnlock()
for target, weight := range node.edges {
nodes[target].Lock()
nodes[target].weight[b] += aa * weight
nodes[target].Unlock()
}
node.Lock()
bb := node.weight[b]
node.weight[b] = bb + adjustment
node.Unlock()
done <- true
}
for Δ > ε {
if g.Verbose {
fmt.Println("updating...")
}
adjustment := (1-α)*inverse + α*leak*inverse
i, flight := 0, 0
for i < len(nodes) && flight < NumCPU {
go update(adjustment, &nodes[i])
flight++
i++
}
for i < len(nodes) {
<-done
flight--
go update(adjustment, &nodes[i])
flight++
i++
}
for j := 0; j < flight; j++ {
<-done
}

if g.Verbose {
fmt.Println("computing delta...")
}
Δ, leak = 0, 0
for source := range nodes {
node := &nodes[source]
aa, bb := node.weight[a], node.weight[b]
if difference := aa - bb; difference < 0 {
Δ -= difference
} else {
Δ += difference
}

if node.outbound == 0 {
leak += bb
}
nodes[source].weight[a] = 0
}

a, b = b, a

if g.Verbose {
fmt.Println(Δ, ε)
}
}

for key, value := range g.index {
callback(key, nodes[value].weight[a])
}
}

// Reset clears all the current graph data.
func (g *Graph32) Reset(size ...int) {
capacity := 8
if len(size) == 1 {
capacity = size[0]
}
g.count = 0
g.index = make(map[uint64]uint, capacity)
g.nodes = make([]Node32, 0, capacity)
}
Loading