commit c309d6158a2135d2f5378db203a3f76e14301586 Author: Adam Colton Date: Tue Mar 1 15:38:55 2016 -0500 Initial commit diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..dc4f8be --- /dev/null +++ b/readme.md @@ -0,0 +1,8 @@ +## TrustGraph + +An implementation of the basic EigenTrust algorithm (http://nlp.stanford.edu/pubs/eigentrust.pdf). + +The algorithm is meant to find the trustworthiness of peers in a distributed system. A (potentially sparse) matrix is populated with values representing how much peers trust each other. A map is also populated with how much trust is extended by default to a sub-set of peers. From that starting point, the algorithm converges on the global trustworthiness of each peer. + +### To-Do +This is a first pass. It does not yet implement distributed EigenTrust. There is also some room to improve error handling. \ No newline at end of file diff --git a/trustgraph.go b/trustgraph.go new file mode 100644 index 0000000..079d842 --- /dev/null +++ b/trustgraph.go @@ -0,0 +1,142 @@ +// Package trustGraph is based on EigenTrust +// http://nlp.stanford.edu/pubs/eigentrust.pdf +package trustGraph + +import ( + "errors" +) + +// Group represents a group of peers. Peers need to be given unique, int IDs. +// Certainty represents the threshold of RMS change at which the algorithm will +// escape. Max is the maximum number of loos the algorithm will perform before +// escaping (regardless of certainty). These default to 0.001 and 200 +// respectivly and generally don't need to be changed. +type Group struct { + trustGrid map[int]map[int]float32 + initialTrust map[int]float32 + Certainty float32 + Max int + Alpha float32 +} + +// NewGroup is the constructor for Group. +func NewGroup() Group { + return Group{ + trustGrid: map[int]map[int]float32{}, + initialTrust: map[int]float32{}, + Certainty: 0.001, + Max: 200, + Alpha: 0.95, + } +} + +// Add will add or override a trust relationship. The first arg is the peer who +// is extending trust, the second arg is the peer being trusted (by the peer +// in the first arg). The 3rd arg is the amount of trust, which must be +func (g Group) Add(truster, trusted int, amount float32) (err error) { + err = float32InRange(amount) + if err == nil { + a, ok := g.trustGrid[truster] + if !ok { + a = map[int]float32{} + g.trustGrid[truster] = a + } + a[trusted] = amount + } + return +} + +// InitialTrust sets the vaulues used to seed the calculation as well as the +// corrective factor used by Alpha. +func (g Group) InitialTrust(trusted int, amount float32) (err error) { + err = float32InRange(amount) + if err == nil { + g.initialTrust[trusted] = amount + } + return +} + +// float32InRange is a helper to check that a value is 0.0 <= x <= 1.0 +func float32InRange(x float32) error { + if x < 0 { + return errors.New("Trust amount cannot be less than 0") + } + if x > 1 { + return errors.New("Trust amount cannot be greater than 1") + } + return nil +} + +// Compute will approximate the trustworthyness of each peer from the +// information known of how much peers trust eachother. +// It wil loop, upto g.Max times or until the average difference between +// iterations is less than g.Certainty. +func (g Group) Compute() map[int]float32 { + if len(g.initialTrust) == 0 { + return map[int]float32{} + } + t0 := g.initialTrust //trust map for previous iteration + + for i := 0; i < g.Max; i++ { + t1 := *g.computeIteration(&t0) // trust map for current iteration + d := avgD(&t0, &t1) + t0 = t1 + if d < g.Certainty { + break + } + } + + return t0 +} + +// computeIteration is broken out of Compute to aid comprehension. It is the +// inner loop of Compute. It loops over every value in t (the current trust map) +// and looks up how much trust that peer extends to every other peer. The +// product of the direct trust and indirect trust +func (g Group) computeIteration(t0 *map[int]float32) *map[int]float32 { + + t1 := map[int]float32{} + for truster, directTrust := range *t0 { + for trusted, indirectTrust := range g.trustGrid[truster] { + if trusted != truster { + t1[trusted] += directTrust * indirectTrust + } + } + } + + // normalize the trust values + // in the EigenTrust paper, this was not done every step, but I prefer to + // Not doing it means the diff (d) needs to be normalized in + // proportion to the values (because they increase with every iteration) + highestTrust := float32(0) + for _, v := range t1 { + if v > highestTrust { + highestTrust = v + } + } + //Todo handle highestTrust == 0 + for i, v := range t1 { + t1[i] = (v/highestTrust)*g.Alpha + (1-g.Alpha)*g.initialTrust[i] + } + + return &t1 +} + +// abs is helper to take abs of float32 +func abs(x float32) float32 { + if x < 0 { + return -x + } + return x +} + +// avgD is helper to compare 2 maps of float32s and return the average +// difference between them +func avgD(t0, t1 *map[int]float32) float32 { + d := float32(0) + for i, v := range *t1 { + d += abs(v - (*t0)[i]) + } + d = d / float32(len(*t0)) + return d +} diff --git a/trustgraph_test.go b/trustgraph_test.go new file mode 100644 index 0000000..0f66b06 --- /dev/null +++ b/trustgraph_test.go @@ -0,0 +1,113 @@ +package trustGraph + +import ( + "math" + "math/rand" + "testing" + "time" +) + +func TestBasic(t *testing.T) { + g := NewGroup() + g.Add(1, 2, 1) + g.Add(1, 3, .5) + g.Add(2, 1, 1) + g.Add(2, 3, .5) + g.Add(3, 1, 1) + g.Add(3, 2, 1) + + g.InitialTrust(1, 1) + + out := g.Compute() + + if out[1] < 0.975 { + t.Error("Trust in node 1 should be closer to 1.00") + } + + if out[2] < 0.93 { + t.Error("Trust in node 2 should be closer to 1.00") + } + if out[3] < 0.4 || out[3] > 0.6 { + t.Error("Trust in node 3 should be closer to 0.50") + } +} + +func TestRand(t *testing.T) { + peers := 200 + rand.Seed(time.Now().UTC().UnixNano()) + g := NewGroup() + + //randomly set actual trust values for peers + actualTrust := make([]float32, peers) + for i := 0; i < peers; i++ { + actualTrust[i] = rand.Float32() + } + + // peer0 is set to and granted 100% trust + actualTrust[0] = 1 + g.InitialTrust(0, 1) + + // set 30% of trust values to +/- 10% of actual trust + for i := 0; i < peers; i++ { + for j := 0; j < peers; j++ { + if rand.Float32() > .7 { + g.Add(i, j, randNorm(actualTrust[j])) + } + } + } + + // compute trust + out := g.Compute() + + // find RMS error + e := float32(0) + for i := 0; i < peers; i++ { + x := actualTrust[i] - out[i] + e += x * x + } + e = float32(math.Sqrt(float64(e / float32(peers)))) + + if e > .2 { + t.Error("RMS Error should be less than 20% for a 30% full trust grid of 200 nodes") + } +} + +// randNorm takes a float and returns a value within +/- 10%, +// without going over 1 +func randNorm(x float32) float32 { + r := rand.Float32()*.2 + .9 + x *= r + if x > 1 { + return 1 + } + return x +} + +func TestRangeError(t *testing.T) { + g := NewGroup() + + err := g.Add(1, 2, 1.1) + if err.Error() != "Trust amount cannot be greater than 1" { + t.Error("Expected error") + } + + err = g.Add(1, 2, -1) + if err.Error() != "Trust amount cannot be less than 0" { + t.Error("Expected error less than 0 error") + } + + err = g.Add(1, 2, 1) + if err != nil { + t.Error("Did not expected error") + } + + err = g.Add(1, 2, 0) + if err != nil { + t.Error("Did not expected error") + } + + err = g.Add(1, 2, 0.5) + if err != nil { + t.Error("Did not expected error") + } +}