From 794a913a8306d5d39754a1e06be44c6a37365785 Mon Sep 17 00:00:00 2001 From: balasan Date: Fri, 2 Apr 2021 18:59:05 -0400 Subject: [PATCH] rename again --- detrep/graph.go | 156 +++++++++++------- detrep/pagerank.go | 83 ++++++---- detrep/pagerank_test.go | 296 ++++++++++++++++++---------------- detrep/processResults.go | 12 +- {rep => detrep}/test_utils.go | 0 rep/graph.go | 156 +++++++----------- rep/pagerank.go | 83 ++++------ rep/pagerank_test.go | 296 ++++++++++++++++------------------ rep/processResults.go | 12 +- 9 files changed, 547 insertions(+), 547 deletions(-) rename {rep => detrep}/test_utils.go (100%) diff --git a/detrep/graph.go b/detrep/graph.go index ca2d2e8..2c7e942 100644 --- a/detrep/graph.go +++ b/detrep/graph.go @@ -1,4 +1,4 @@ -// Package rep is an implementation of the Relevant Reputaiton protocol: +// Package detrep is a deterministic implementation of the Relevant Reputaiton protocol: // a personalized pagerank algorithm that supports negative links // personalized version offers sybil resistance // can be used for voting, governance, ranking @@ -10,18 +10,13 @@ // TODO: edge case (only impacts display) - if a node has no inputs we should set its score to 0 to avoid // a stale score if all of nodes inputs are cancelled out // would need to keep track of node inputs... -package rep +package detrep import ( - "math" "strconv" -) -// MaxNegOffset defines the cutoff for when a node will have it's outging links counted -// if previously NegativeRank / PositiveRank > MaxNegOffset / (MaxNegOffset + 1) we will not consider -// any outgoing links -// otherwise, we counter the outgoing lings with one 'heavy' link proportional to the MaxNegOffset ratio -const MaxNegOffset = float64(10) + sdk "github.com/cosmos/cosmos-sdk/types" +) // NodeType is positive or negative // each node in the graph can be represented by two nodes, @@ -35,22 +30,33 @@ const ( Negative ) +// Decimals is the default decimal precision used in computation +const Decimals = 18 + +// MaxNegOffset defines the cutoff for when a node will have it's outging links counted +// if previously NegativeRank / PositiveRank > MaxNegOffset / (MaxNegOffset + 1) we will not consider +// any outgoing links +// otherwise, we counter the outgoing lings with one 'heavy' link proportional to the MaxNegOffset ratio +const MaxNegOffset = 10 + // Node is an internal node struct type Node struct { ID string - PRank float64 // pos page rank of the node - NRank float64 // only used when combining results - degree float64 // sum of all outgoing links + PRank sdk.Uint // pos page rank of the node + NRank sdk.Uint // only used when combining results + degree sdk.Uint // sum of all outgoing links nodeType NodeType } // Graph holds node and edge data. type Graph struct { - nodes map[string]*Node - negNodes map[string]*Node - edges map[string](map[string]float64) - params RankParams - negConsumer Node + nodes map[string]*Node + negNodes map[string]*Node + edges map[string](map[string]sdk.Uint) + params RankParams + negConsumer Node + Precision sdk.Uint + MaxNegOffset sdk.Uint } // RankParams is the pagerank parameters @@ -58,27 +64,29 @@ type Graph struct { // ε is the min global error between iterations // personalization is the personalization vector (can be nil for non-personalized pr) type RankParams struct { - α, ε float64 - personalization []string // array of ids + α, ε sdk.Uint + personalization []string } // NewGraph initializes and returns a new graph. -func NewGraph(α, ε, negConsumerRank float64) *Graph { +func NewGraph(α sdk.Uint, ε sdk.Uint, negConsumerRank sdk.Uint) *Graph { return &Graph{ nodes: make(map[string]*Node), negNodes: make(map[string]*Node), - edges: make(map[string](map[string]float64)), + edges: make(map[string](map[string]sdk.Uint)), params: RankParams{ - α: α, // this is the probabilty of not doing a jump, usually .85 - ε: ε, // this is the error margin used to determin convergence, usually something small + α: α, + ε: ε, personalization: make([]string, 0), }, - negConsumer: Node{ID: "negConsumer", PRank: negConsumerRank, NRank: 0}, + negConsumer: Node{ID: "negConsumer", PRank: negConsumerRank, NRank: sdk.ZeroUint()}, + Precision: sdk.NewUintFromBigInt(sdk.NewIntWithDecimal(1, Decimals).BigInt()), + MaxNegOffset: sdk.NewUintFromBigInt(sdk.NewIntWithDecimal(MaxNegOffset, Decimals).BigInt()), } } // NewNode is ahelper method to create a node input struct -func NewNode(id string, pRank float64, nRank float64) Node { +func NewNode(id string, pRank sdk.Uint, nRank sdk.Uint) Node { return Node{ID: id, PRank: pRank, NRank: nRank} } @@ -93,11 +101,15 @@ func (graph *Graph) AddPersonalizationNode(pNode Node) { // Link creates a weighted edge between a source-target node pair. // If the edge already exists, the weight is incremented. -func (graph *Graph) Link(source, target Node, weight float64) { +func (graph *Graph) Link(source, target Node, weight sdk.Int) { - // if a node's neg/post rank ration is too high we don't process its links - if source.PRank > 0 && source.NRank/source.PRank > MaxNegOffset/(MaxNegOffset+1) { - return + // if a node's neg/pos rank is > MaxNegOffset / (MaxNegOffset + 1) we don't process it + if source.PRank.GT(sdk.ZeroUint()) { + negPosRatio := source.NRank.Mul(graph.Precision).Quo(source.PRank) + one := graph.Precision + if negPosRatio.GT(graph.MaxNegOffset.Mul(graph.Precision).Quo(graph.MaxNegOffset.Add(one))) { + return + } } sourceKey := getKey(source.ID, Positive) @@ -105,19 +117,29 @@ func (graph *Graph) Link(source, target Node, weight float64) { // if weight is negative we use negative receiving node var nodeType NodeType - if nodeType = Positive; weight < 0 { + var weightUint sdk.Uint + if weight.LT(sdk.ZeroInt()) { nodeType = Negative + weightUint = sdk.NewUintFromBigInt(weight.Neg().BigInt()) + } else { + nodeType = Positive + weightUint = sdk.NewUintFromBigInt(weight.BigInt()) } targetKey := getKey(target.ID, nodeType) graph.initNode(targetKey, target, nodeType) - sourceNode.degree += math.Abs(weight) + sourceNode.degree = sourceNode.degree.Add(weightUint) if _, ok := graph.edges[sourceKey]; ok == false { - graph.edges[sourceKey] = map[string]float64{} + graph.edges[sourceKey] = map[string]sdk.Uint{} + } + + if _, ok := graph.edges[sourceKey][targetKey]; ok == false { + graph.edges[sourceKey][targetKey] = sdk.ZeroUint() } - graph.edges[sourceKey][targetKey] += math.Abs(weight) + + graph.edges[sourceKey][targetKey] = graph.edges[sourceKey][targetKey].Add(weightUint) // note: use target.id here to make sure we reference the original id graph.cancelOpposites(*sourceNode, target.ID, nodeType) @@ -139,40 +161,47 @@ func (graph *Graph) processNegatives() { if _, ok := graph.nodes[negNode.ID]; ok == false { return } + // node has no outgpoing links - if graph.nodes[negNode.ID].degree == 0 { + if graph.nodes[negNode.ID].degree.IsZero() { return } posNode := graph.nodes[negNode.ID] - if posNode.PRank == 0 { + if posNode.PRank.IsZero() || negNode.PRank.IsZero() { return } + negConsumer := graph.initNode(negConsumerInput.ID, negConsumerInput, Positive) - if negNode.PRank >= posNode.PRank { - panic("negative ranking nodes should not have any degree") // this should never happen - } + one := graph.Precision - negConsumer := graph.initNode(negConsumerInput.ID, negConsumerInput, Positive) + // posNode.rank is not 0 check above + negPosRatio := negNode.PRank.Mul(graph.Precision).Quo(posNode.PRank) - var negMultiple float64 + var negMultiple sdk.Uint + // if negPosRatio > MaxNegOffset / (MaxNegOffset + 1) we use the MaxNegOffset // this first case should not happen because we ignore these links - if negNode.PRank/posNode.PRank > MaxNegOffset/(MaxNegOffset+1) { - // cap the degree multiple at MAX_NEG_OFFSET - negMultiple = MaxNegOffset + if negPosRatio.GT(graph.MaxNegOffset.Mul(graph.Precision).Quo(graph.MaxNegOffset.Add(one))) { + negMultiple = graph.MaxNegOffset } else { - negMultiple = 1/(1-negNode.PRank/posNode.PRank) - 1 + denom := one.Sub(negPosRatio) + negMultiple = one.Mul(graph.Precision).Quo(denom).Sub(one) } + // cap the vote decrease at 10x + negWeight := negMultiple.Mul(graph.nodes[negNode.ID].degree).Quo(graph.Precision) - // this is the weight we add to the outgoing node - negWeight := negMultiple * graph.nodes[negNode.ID].degree - + // this should actually never happen if degree is > 0 if _, ok := graph.edges[negNode.ID]; ok == false { - graph.edges[negNode.ID] = map[string]float64{} + graph.edges[negNode.ID] = map[string]sdk.Uint{} + } + + if _, ok := graph.edges[negNode.ID][negConsumer.ID]; ok == false { + graph.edges[negNode.ID][negConsumer.ID] = sdk.ZeroUint() } - graph.edges[negNode.ID][negConsumer.ID] += negWeight - graph.nodes[negNode.ID].degree += negWeight + + graph.edges[negNode.ID][negConsumer.ID] = graph.edges[negNode.ID][negConsumer.ID].Add(negWeight) + graph.nodes[negNode.ID].degree = graph.nodes[negNode.ID].degree.Add(negWeight) } } @@ -192,36 +221,39 @@ func (graph *Graph) cancelOpposites(sourceNode Node, target string, nodeType Nod opositeEdge := graph.edges[sourceNode.ID][oppositeKey] switch { - case opositeEdge > edge: + case opositeEdge.GT(edge): graph.removeEdge(sourceNode.ID, key) - graph.edges[sourceNode.ID][oppositeKey] -= edge + graph.edges[sourceNode.ID][oppositeKey] = opositeEdge.Sub(edge) // remove degree from both delete node and the adjustment - sourceNode.degree -= 2 * edge + sourceNode.degree = sourceNode.degree.Sub(edge.Mul(sdk.NewUint(2))) - case edge > opositeEdge: + case edge.GT(opositeEdge): graph.removeEdge(sourceNode.ID, oppositeKey) - graph.edges[sourceNode.ID][key] -= opositeEdge + graph.edges[sourceNode.ID][key] = edge.Sub(opositeEdge) // remove degree from both delete node and the adjustment - sourceNode.degree -= 2 * opositeEdge + sourceNode.degree = sourceNode.degree.Sub(opositeEdge.Mul(sdk.NewUint(2))) - case edge == opositeEdge: + case edge.Equal(opositeEdge): graph.removeEdge(sourceNode.ID, oppositeKey) graph.removeEdge(sourceNode.ID, key) - sourceNode.degree -= 2 * opositeEdge + + sourceNode.degree = sourceNode.degree.Sub(opositeEdge.Mul(sdk.NewUint(2))) } } -// InitPosNode is a helper method that initializes a positive node +// InitPosNode initialized a positive node func (graph *Graph) InitPosNode(inputNode Node) *Node { return graph.initNode(inputNode.ID, inputNode, Positive) } -// initNode initializes a node +// initNode initialized a node func (graph *Graph) initNode(key string, inputNode Node, nodeType NodeType) *Node { if _, ok := graph.nodes[key]; ok == false { graph.nodes[key] = &Node{ ID: inputNode.ID, // id is independent of pos/neg keys - degree: 0, + degree: sdk.ZeroUint(), + PRank: sdk.ZeroUint(), + NRank: sdk.ZeroUint(), nodeType: nodeType, } // store negative nodes so we can easily merge them later @@ -230,7 +262,7 @@ func (graph *Graph) initNode(key string, inputNode Node, nodeType NodeType) *Nod } } // update rank here in case we initilized with 0 early on - var prevRank float64 + var prevRank sdk.Uint if prevRank = inputNode.PRank; nodeType == Negative { prevRank = inputNode.NRank } diff --git a/detrep/pagerank.go b/detrep/pagerank.go index 2b94a8f..7b24b14 100644 --- a/detrep/pagerank.go +++ b/detrep/pagerank.go @@ -1,7 +1,7 @@ -package rep +package detrep import ( - "math" + sdk "github.com/cosmos/cosmos-sdk/types" ) // Rank computes the PageRank of every node in the directed graph. @@ -9,11 +9,13 @@ import ( // ε (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 (graph Graph) Rank(callback func(key string, pRank float64, nRank float64)) { +func (graph Graph) Rank(callback func(key string, pRank sdk.Uint, nRank sdk.Uint)) { graph.Finalize() - Δ := float64(1.0) - N := float64(len(graph.nodes)) + one := graph.Precision + + Δ := one + N := sdk.NewUint(uint64(len(graph.nodes))) pVector := graph.params.personalization ε := graph.params.ε α := graph.params.α @@ -26,9 +28,9 @@ func (graph Graph) Rank(callback func(key string, pRank float64, nRank float64)) // Normalize all the edge weights so that their sum amounts to 1. for source := range graph.nodes { - if graph.nodes[source].degree > 0 { + if graph.nodes[source].degree.GT(sdk.ZeroUint()) { for target := range graph.edges[source] { - graph.edges[source][target] /= graph.nodes[source].degree + graph.edges[source][target] = graph.edges[source][target].Mul(graph.Precision).Quo(graph.nodes[source].degree) } } } @@ -36,29 +38,30 @@ func (graph Graph) Rank(callback func(key string, pRank float64, nRank float64)) graph.initScores(N, pWeights) iter := 0 - for Δ > ε { - danglingWeight := float64(0) - nodes := map[string]float64{} + for Δ.GT(ε) { + danglingWeight := sdk.ZeroUint() + nodes := map[string]sdk.Uint{} for key, value := range graph.nodes { nodes[key] = value.PRank - if value.degree == 0 { - danglingWeight += value.PRank + if value.degree.IsZero() { + danglingWeight = danglingWeight.Add(value.PRank) } - graph.nodes[key].PRank = 0 + graph.nodes[key].PRank = sdk.ZeroUint() } - danglingWeight *= α + danglingWeight = danglingWeight.Mul(α).Quo(graph.Precision) for source := range graph.nodes { for target, weight := range graph.edges[source] { - graph.nodes[target].PRank += α * nodes[source] * weight + addWeight := α.Mul(nodes[source]).Quo(graph.Precision).Mul(weight).Quo(graph.Precision) + graph.nodes[target].PRank = graph.nodes[target].PRank.Add(addWeight) } if !personalized { - graph.nodes[source].PRank += (1-α)/N + danglingWeight/N + graph.nodes[source].PRank = graph.nodes[source].PRank.Add(one.Sub(α).Quo(N).Add(danglingWeight.Quo(N))) } } @@ -66,14 +69,23 @@ func (graph Graph) Rank(callback func(key string, pRank float64, nRank float64)) // this makes pagerank sybil resistant if personalized { for i, root := range pVector { - graph.nodes[root].PRank += (1 - α + danglingWeight) * pWeights[i] + graph.nodes[root].PRank = graph.nodes[root].PRank.Add((one.Sub(α).Add(danglingWeight)).Mul(pWeights[i])).Quo(graph.Precision) } } - Δ = 0 + Δ = sdk.ZeroUint() for key, value := range graph.nodes { - Δ += math.Abs(value.PRank - nodes[key]) + var diff sdk.Uint + // if _, ok := nodes[key]; ok == false { + // nodes[key] = sdk.ZeroUint() + // } + if value.PRank.LT(nodes[key]) { + diff = nodes[key].Sub(value.PRank) + } else { + diff = value.PRank.Sub(nodes[key]) + } + Δ = Δ.Add(diff) } iter++ } @@ -84,15 +96,16 @@ func (graph Graph) Rank(callback func(key string, pRank float64, nRank float64)) // make sure the total start sum of all scores is 1 // we initialze the start scores to optimize the computation -func (graph Graph) initScores(N float64, pWeights []float64) { +func (graph Graph) initScores(N sdk.Uint, pWeights []sdk.Uint) { // get sum of all node scores personalization := graph.params.personalization - var totalScore float64 + totalScore := sdk.ZeroUint() for _, node := range graph.nodes { - totalScore += node.PRank + totalScore = totalScore.Add(node.PRank) } - if totalScore > .9 { + // if start sum is close to 1 we are done + if totalScore.GT(graph.Precision.MulUint64(9).QuoUint64(10)) { return } @@ -100,40 +113,40 @@ func (graph Graph) initScores(N float64, pWeights []float64) { if len(pWeights) == 0 { // initialize all nodes if there is no personalizeation vector for key := range graph.nodes { - graph.nodes[key].PRank += (1 - totalScore) / N + graph.nodes[key].PRank = graph.nodes[key].PRank.Add(graph.Precision.Sub(totalScore).Quo(N)) } return } // initialize personalization vector for i, root := range personalization { - graph.nodes[root].PRank += (1 - totalScore) * pWeights[i] + graph.nodes[root].PRank = graph.nodes[root].PRank.Add(graph.Precision.Sub(totalScore).Mul(graph.Precision).Quo(pWeights[i])) } } // compute personalization weights based on degree // this ensures source nodes will have the same weight // we also update start scores here -func (graph Graph) initPersonalizationNodes() []float64 { +func (graph Graph) initPersonalizationNodes() []sdk.Uint { pVector := graph.params.personalization - pWeights := make([]float64, len(pVector)) + pWeights := make([]sdk.Uint, len(pVector)) - var pWeightsSum float64 - var scoreSum float64 + pWeightsSum := sdk.ZeroUint() + scoreSum := sdk.ZeroUint() for i, key := range pVector { - var d float64 + var d sdk.Uint // root node score and weight should not be 0 - if d = 1; graph.nodes[key].degree > 0 { + if d = graph.Precision; graph.nodes[key].degree.GT(sdk.ZeroUint()) { d = graph.nodes[key].degree } pWeights[i] = d - pWeightsSum += d - scoreSum += graph.nodes[key].PRank + pWeightsSum = pWeightsSum.Add(d) + scoreSum = scoreSum.Add(graph.nodes[key].PRank) } // normalize personalization weights for i, key := range pVector { - pWeights[i] /= pWeightsSum - graph.nodes[key].PRank = scoreSum * pWeights[i] + pWeights[i] = pWeights[i].Mul(graph.Precision).Quo(pWeightsSum) + graph.nodes[key].PRank = scoreSum.Mul(pWeights[i]).Quo(graph.Precision) } return pWeights diff --git a/detrep/pagerank_test.go b/detrep/pagerank_test.go index 3d8f621..db1ec0e 100644 --- a/detrep/pagerank_test.go +++ b/detrep/pagerank_test.go @@ -1,22 +1,26 @@ -package rep +package detrep import ( "reflect" "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" ) type Result struct { - pRank float64 - nRank float64 + pRank sdk.Uint + nRank sdk.Uint } +var zero = sdk.ZeroUint() + func TestEmpty(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) actual := map[string]Result{} expected := map[string]Result{} - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -29,85 +33,91 @@ func TestEmpty(t *testing.T) { } func TestSimple(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) - d := NewNode("d", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) + d := NewNodeInputHelper("d", 0, 0) // circle - graph.Link(a, b, 1.0) - graph.Link(b, c, 1.0) - graph.Link(c, d, 1.0) - graph.Link(d, a, 1.0) + graph.LinkHelper(a, b, 1.0) + graph.LinkHelper(b, c, 1.0) + graph.LinkHelper(c, d, 1.0) + graph.LinkHelper(d, a, 1.0) actual := map[string]Result{} expected := map[string]Result{ - "a": {pRank: 0.25, nRank: 0}, - "b": {pRank: 0.25, nRank: 0}, - "c": {pRank: 0.25, nRank: 0}, - "d": {pRank: 0.25, nRank: 0}, + "a": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, + "b": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, + "c": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, + "d": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, } - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if reflect.DeepEqual(actual, expected) != true { - t.Error("Expected", expected, "but got", actual) + for key, value := range actual { + expVal := expected[key] + if !value.pRank.Equal(expVal.pRank) { + t.Error("Expected", expVal.pRank.String(), "but got", value.pRank.String()) + } + if !value.nRank.Equal(expVal.nRank) { + t.Error("Expected", expVal.nRank.String(), "but got", value.nRank.String()) + } } } func TestWeighted(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) - graph.Link(a, b, 1.0) - graph.Link(a, c, 2.0) + graph.LinkHelper(a, b, 1.0) + graph.LinkHelper(a, c, 2.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if actual["b"].pRank >= actual["c"].pRank { - t.Errorf("rank of b %f is not > c %f", actual["b"].pRank, actual["c"].pRank) + if actual["b"].pRank.GTE(actual["c"].pRank) { + t.Errorf("rank of b %s is not > c %s", actual["b"].pRank.String(), actual["c"].pRank.String()) } } func TestPersonalized(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) - d := NewNode("d", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) + d := NewNodeInputHelper("d", 0, 0) graph.AddPersonalizationNode(a) - graph.Link(a, b, 1.0) - graph.Link(d, c, 2.0) + graph.LinkHelper(a, b, 1.0) + graph.LinkHelper(d, c, 2.0) actual := map[string]Result{} expected := map[string]Result{ - "a": {pRank: 0.25, nRank: 0}, - "b": {pRank: 0.25, nRank: 0}, - "c": {pRank: 0, nRank: 0}, - "d": {pRank: 0, nRank: 0}, + "a": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, + "b": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, + "c": {pRank: FtoBD(0), nRank: FtoBD(0)}, + "d": {pRank: FtoBD(0), nRank: FtoBD(0)}, } - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -122,29 +132,29 @@ func TestPersonalized(t *testing.T) { } } -func TestPersonalizedNoLink(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) +func TestPersonalizedNoLinkHelper(t *testing.T) { + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) - d := NewNode("d", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) + d := NewNodeInputHelper("d", 0, 0) graph.AddPersonalizationNode(a) - graph.Link(b, c, 1.0) - graph.Link(d, c, 1.0) + graph.LinkHelper(b, c, 1.0) + graph.LinkHelper(d, c, 1.0) actual := map[string]Result{} expected := map[string]Result{ - "a": {pRank: 1.0, nRank: 0}, - "b": {pRank: 0, nRank: 0}, - "c": {pRank: 0, nRank: 0}, - "d": {pRank: 0, nRank: 0}, + "a": {pRank: FtoBD(1.0), nRank: FtoBD(0)}, + "b": {pRank: FtoBD(0), nRank: FtoBD(0)}, + "c": {pRank: FtoBD(0), nRank: FtoBD(0)}, + "d": {pRank: FtoBD(0), nRank: FtoBD(0)}, } - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -157,81 +167,85 @@ func TestPersonalizedNoLink(t *testing.T) { } func TestCancelOpposites(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) - d := NewNode("d", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) + d := NewNodeInputHelper("d", 0, 0) graph.AddPersonalizationNode(a) - graph.Link(a, b, 1.0) - graph.Link(a, b, -1.0) - graph.Link(a, c, 2.0) - graph.Link(a, c, -1.0) - graph.Link(a, d, 1.0) - graph.Link(a, d, -2.0) + graph.LinkHelper(a, b, 1.0) + graph.LinkHelper(a, b, -1.0) + graph.LinkHelper(a, c, 2.0) + graph.LinkHelper(a, c, -1.0) + graph.LinkHelper(a, d, 1.0) + graph.LinkHelper(a, d, -2.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if actual["b"].pRank+actual["b"].nRank != 0 { - t.Errorf("rank of b should be 0") + if !actual["b"].pRank.Add(actual["b"].nRank).Equal(sdk.ZeroUint()) { + t.Errorf("rank of b should be 0 but its %s", actual["b"].pRank.String()) } - if actual["c"].nRank != 0 { + if !actual["c"].nRank.Equal(sdk.ZeroUint()) { t.Errorf("c rank should be positive") } - if actual["d"].pRank != 0 { + if !actual["d"].pRank.Equal(sdk.ZeroUint()) { t.Errorf("d rank should be negative") } } func TestNegativeLink(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) - d := NewNode("d", 0, 0) - e := NewNode("e", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) + d := NewNodeInputHelper("d", 0, 0) + e := NewNodeInputHelper("e", 0, 0) graph.AddPersonalizationNode(a) - graph.Link(a, b, 2.0) - graph.Link(a, c, 1.0) - graph.Link(c, d, 1.0) - graph.Link(b, d, -1.0) - graph.Link(a, e, -1.0) + graph.LinkHelper(a, b, 2.0) + graph.LinkHelper(a, c, 1.0) + graph.LinkHelper(c, d, 1.0) + graph.LinkHelper(b, d, -1.0) + graph.LinkHelper(a, e, -1.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if actual["d"].pRank-actual["d"].nRank >= 0 { + if actual["d"].nRank.Sub(actual["d"].pRank).LTE(sdk.ZeroUint()) { t.Errorf("rank of d should be neagative") } - if actual["e"].pRank != 0 || actual["e"].nRank == 0 { + if !actual["e"].pRank.Equal(sdk.ZeroUint()) || actual["e"].nRank.Equal(sdk.ZeroUint()) { t.Errorf("pure neagative node has incorrect results") } // use prev computation as input for the next iteration + initNegativeConsumer := actual["negConsumer"].pRank + if _, ok := actual["negConsumer"]; ok == false { + initNegativeConsumer = zero + } - graph = NewGraph(0.85, 0.000001, actual["negConsumer"].pRank) + graph = NewGraphHelper(0.85, 0.000001, initNegativeConsumer) a = NewNode("a", actual["a"].pRank, actual["a"].nRank) b = NewNode("b", actual["b"].pRank, actual["b"].nRank) @@ -241,44 +255,44 @@ func TestNegativeLink(t *testing.T) { graph.AddPersonalizationNode(a) - graph.Link(a, b, 2.0) - graph.Link(a, c, 1.0) - graph.Link(c, d, 1.0) - graph.Link(b, d, -1.0) - graph.Link(d, e, 1.0) + graph.LinkHelper(a, b, 2.0) + graph.LinkHelper(a, c, 1.0) + graph.LinkHelper(c, d, 1.0) + graph.LinkHelper(b, d, -1.0) + graph.LinkHelper(d, e, 1.0) - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if actual["e"].pRank != 0 { + if !actual["e"].pRank.Equal(sdk.ZeroUint()) { t.Errorf("weight of neg node should be 0") } } func TestNegativeConsumer(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) - d := NewNode("d", 0, 0) - e := NewNode("e", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) + d := NewNodeInputHelper("d", 0, 0) + e := NewNodeInputHelper("e", 0, 0) graph.AddPersonalizationNode(a) - graph.Link(a, b, 1.0) - graph.Link(a, c, 2.0) - graph.Link(c, d, 1.0) - graph.Link(b, d, -1.0) - graph.Link(d, e, 1.0) + graph.LinkHelper(a, b, 1.0) + graph.LinkHelper(a, c, 2.0) + graph.LinkHelper(c, d, 1.0) + graph.LinkHelper(b, d, -1.0) + graph.LinkHelper(d, e, 1.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -286,8 +300,12 @@ func TestNegativeConsumer(t *testing.T) { }) // use prev computation as input for the next iteration + initNegativeConsumer := actual["negConsumer"].pRank + if _, ok := actual["negConsumer"]; ok == false { + initNegativeConsumer = zero + } - graph = NewGraph(0.85, 0.000001, actual["negConsumer"].pRank) + graph = NewGraphHelper(0.85, 0.000001, initNegativeConsumer) eRank := actual["e"].pRank @@ -299,44 +317,44 @@ func TestNegativeConsumer(t *testing.T) { graph.AddPersonalizationNode(a) - graph.Link(a, b, 1.0) - graph.Link(a, c, 2.0) - graph.Link(c, d, 1.0) - graph.Link(b, d, -1.0) - graph.Link(d, e, 1.0) + graph.LinkHelper(a, b, 1.0) + graph.LinkHelper(a, c, 2.0) + graph.LinkHelper(c, d, 1.0) + graph.LinkHelper(b, d, -1.0) + graph.LinkHelper(d, e, 1.0) - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if eRank <= actual["e"].pRank { - t.Errorf("weight of neg node should decrease %f, %f", eRank, actual["e"].pRank) + if eRank.LT(actual["e"].pRank) { + t.Errorf("weight of neg node should decrease") } } func TestMaxNeg(t *testing.T) { - graph := NewGraph(0.85, 0.000001, 0) + graph := NewGraphHelper(0.85, 0.000001, zero) - a := NewNode("a", 0, 0) - b := NewNode("b", 0, 0) - c := NewNode("c", 0, 0) - d := NewNode("d", 0, 0) - e := NewNode("e", 0, 0) + a := NewNodeInputHelper("a", 0, 0) + b := NewNodeInputHelper("b", 0, 0) + c := NewNodeInputHelper("c", 0, 0) + d := NewNodeInputHelper("d", 0, 0) + e := NewNodeInputHelper("e", 0, 0) graph.AddPersonalizationNode(a) - graph.Link(a, b, MaxNegOffset+1) - graph.Link(a, c, MaxNegOffset+2) - graph.Link(c, d, 1.0) - graph.Link(b, d, -1.0) - graph.Link(d, e, 1.0) + graph.LinkHelper(a, b, 11.0) + graph.LinkHelper(a, c, 12.0) + graph.LinkHelper(c, d, 1.0) + graph.LinkHelper(b, d, -1.0) + graph.LinkHelper(d, e, 1.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -344,8 +362,12 @@ func TestMaxNeg(t *testing.T) { }) // use prev computation as input for the next iteration + initNegativeConsumer := actual["negConsumer"].pRank + if _, ok := actual["negConsumer"]; ok == false { + initNegativeConsumer = zero + } - graph = NewGraph(0.85, 0.000001, actual["negConsumer"].pRank) + graph = NewGraphHelper(0.85, 0.000001, initNegativeConsumer) eRank := actual["e"].pRank @@ -353,26 +375,24 @@ func TestMaxNeg(t *testing.T) { b = NewNode("b", actual["b"].pRank, actual["b"].nRank) c = NewNode("c", actual["c"].pRank, actual["c"].nRank) d = NewNode("d", actual["d"].pRank, actual["d"].nRank) - e = NewNode("e", 0, 0) + e = NewNode("e", actual["e"].pRank, actual["e"].nRank) graph.AddPersonalizationNode(a) - graph.Link(a, b, MaxNegOffset+1) - graph.Link(a, c, MaxNegOffset+2) - graph.Link(c, d, 1.0) - graph.Link(b, d, -1.0) - graph.Link(d, e, 1.0) - - actual = map[string]Result{} + graph.LinkHelper(a, b, 11.0) + graph.LinkHelper(a, c, 12.0) + graph.LinkHelper(c, d, 1.0) + graph.LinkHelper(b, d, -1.0) + graph.LinkHelper(d, e, 1.0) - graph.Rank(func(id string, pRank float64, nRank float64) { + graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if eRank <= actual["e"].pRank { - t.Errorf("weight of neg node should decrease %f, %f", eRank, actual["e"].pRank) + if eRank.LT(actual["e"].pRank) { + t.Errorf("weight of neg node should decrease") } } diff --git a/detrep/processResults.go b/detrep/processResults.go index 90c5e24..9dab16d 100644 --- a/detrep/processResults.go +++ b/detrep/processResults.go @@ -1,6 +1,10 @@ -package rep +package detrep -func (graph Graph) processResults(callback func(id string, pRank float64, nRank float64)) { +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (graph Graph) processResults(callback func(id string, pRank sdk.Uint, nRank sdk.Uint)) { graph.mergeNegatives() for key, node := range graph.nodes { callback(key, node.PRank, node.NRank) @@ -13,8 +17,8 @@ func (graph Graph) mergeNegatives() { if _, ok := graph.nodes[node.ID]; ok == false { graph.nodes[node.ID] = &Node{ ID: key, - PRank: 0, - degree: 0, + PRank: sdk.ZeroUint(), + degree: sdk.ZeroUint(), nodeType: Positive, } } diff --git a/rep/test_utils.go b/detrep/test_utils.go similarity index 100% rename from rep/test_utils.go rename to detrep/test_utils.go diff --git a/rep/graph.go b/rep/graph.go index 2c7e942..ca2d2e8 100644 --- a/rep/graph.go +++ b/rep/graph.go @@ -1,4 +1,4 @@ -// Package detrep is a deterministic implementation of the Relevant Reputaiton protocol: +// Package rep is an implementation of the Relevant Reputaiton protocol: // a personalized pagerank algorithm that supports negative links // personalized version offers sybil resistance // can be used for voting, governance, ranking @@ -10,14 +10,19 @@ // TODO: edge case (only impacts display) - if a node has no inputs we should set its score to 0 to avoid // a stale score if all of nodes inputs are cancelled out // would need to keep track of node inputs... -package detrep +package rep import ( + "math" "strconv" - - sdk "github.com/cosmos/cosmos-sdk/types" ) +// MaxNegOffset defines the cutoff for when a node will have it's outging links counted +// if previously NegativeRank / PositiveRank > MaxNegOffset / (MaxNegOffset + 1) we will not consider +// any outgoing links +// otherwise, we counter the outgoing lings with one 'heavy' link proportional to the MaxNegOffset ratio +const MaxNegOffset = float64(10) + // NodeType is positive or negative // each node in the graph can be represented by two nodes, // a positive and a negative one @@ -30,33 +35,22 @@ const ( Negative ) -// Decimals is the default decimal precision used in computation -const Decimals = 18 - -// MaxNegOffset defines the cutoff for when a node will have it's outging links counted -// if previously NegativeRank / PositiveRank > MaxNegOffset / (MaxNegOffset + 1) we will not consider -// any outgoing links -// otherwise, we counter the outgoing lings with one 'heavy' link proportional to the MaxNegOffset ratio -const MaxNegOffset = 10 - // Node is an internal node struct type Node struct { ID string - PRank sdk.Uint // pos page rank of the node - NRank sdk.Uint // only used when combining results - degree sdk.Uint // sum of all outgoing links + PRank float64 // pos page rank of the node + NRank float64 // only used when combining results + degree float64 // sum of all outgoing links nodeType NodeType } // Graph holds node and edge data. type Graph struct { - nodes map[string]*Node - negNodes map[string]*Node - edges map[string](map[string]sdk.Uint) - params RankParams - negConsumer Node - Precision sdk.Uint - MaxNegOffset sdk.Uint + nodes map[string]*Node + negNodes map[string]*Node + edges map[string](map[string]float64) + params RankParams + negConsumer Node } // RankParams is the pagerank parameters @@ -64,29 +58,27 @@ type Graph struct { // ε is the min global error between iterations // personalization is the personalization vector (can be nil for non-personalized pr) type RankParams struct { - α, ε sdk.Uint - personalization []string + α, ε float64 + personalization []string // array of ids } // NewGraph initializes and returns a new graph. -func NewGraph(α sdk.Uint, ε sdk.Uint, negConsumerRank sdk.Uint) *Graph { +func NewGraph(α, ε, negConsumerRank float64) *Graph { return &Graph{ nodes: make(map[string]*Node), negNodes: make(map[string]*Node), - edges: make(map[string](map[string]sdk.Uint)), + edges: make(map[string](map[string]float64)), params: RankParams{ - α: α, - ε: ε, + α: α, // this is the probabilty of not doing a jump, usually .85 + ε: ε, // this is the error margin used to determin convergence, usually something small personalization: make([]string, 0), }, - negConsumer: Node{ID: "negConsumer", PRank: negConsumerRank, NRank: sdk.ZeroUint()}, - Precision: sdk.NewUintFromBigInt(sdk.NewIntWithDecimal(1, Decimals).BigInt()), - MaxNegOffset: sdk.NewUintFromBigInt(sdk.NewIntWithDecimal(MaxNegOffset, Decimals).BigInt()), + negConsumer: Node{ID: "negConsumer", PRank: negConsumerRank, NRank: 0}, } } // NewNode is ahelper method to create a node input struct -func NewNode(id string, pRank sdk.Uint, nRank sdk.Uint) Node { +func NewNode(id string, pRank float64, nRank float64) Node { return Node{ID: id, PRank: pRank, NRank: nRank} } @@ -101,15 +93,11 @@ func (graph *Graph) AddPersonalizationNode(pNode Node) { // Link creates a weighted edge between a source-target node pair. // If the edge already exists, the weight is incremented. -func (graph *Graph) Link(source, target Node, weight sdk.Int) { +func (graph *Graph) Link(source, target Node, weight float64) { - // if a node's neg/pos rank is > MaxNegOffset / (MaxNegOffset + 1) we don't process it - if source.PRank.GT(sdk.ZeroUint()) { - negPosRatio := source.NRank.Mul(graph.Precision).Quo(source.PRank) - one := graph.Precision - if negPosRatio.GT(graph.MaxNegOffset.Mul(graph.Precision).Quo(graph.MaxNegOffset.Add(one))) { - return - } + // if a node's neg/post rank ration is too high we don't process its links + if source.PRank > 0 && source.NRank/source.PRank > MaxNegOffset/(MaxNegOffset+1) { + return } sourceKey := getKey(source.ID, Positive) @@ -117,29 +105,19 @@ func (graph *Graph) Link(source, target Node, weight sdk.Int) { // if weight is negative we use negative receiving node var nodeType NodeType - var weightUint sdk.Uint - if weight.LT(sdk.ZeroInt()) { + if nodeType = Positive; weight < 0 { nodeType = Negative - weightUint = sdk.NewUintFromBigInt(weight.Neg().BigInt()) - } else { - nodeType = Positive - weightUint = sdk.NewUintFromBigInt(weight.BigInt()) } targetKey := getKey(target.ID, nodeType) graph.initNode(targetKey, target, nodeType) - sourceNode.degree = sourceNode.degree.Add(weightUint) + sourceNode.degree += math.Abs(weight) if _, ok := graph.edges[sourceKey]; ok == false { - graph.edges[sourceKey] = map[string]sdk.Uint{} - } - - if _, ok := graph.edges[sourceKey][targetKey]; ok == false { - graph.edges[sourceKey][targetKey] = sdk.ZeroUint() + graph.edges[sourceKey] = map[string]float64{} } - - graph.edges[sourceKey][targetKey] = graph.edges[sourceKey][targetKey].Add(weightUint) + graph.edges[sourceKey][targetKey] += math.Abs(weight) // note: use target.id here to make sure we reference the original id graph.cancelOpposites(*sourceNode, target.ID, nodeType) @@ -161,47 +139,40 @@ func (graph *Graph) processNegatives() { if _, ok := graph.nodes[negNode.ID]; ok == false { return } - // node has no outgpoing links - if graph.nodes[negNode.ID].degree.IsZero() { + if graph.nodes[negNode.ID].degree == 0 { return } posNode := graph.nodes[negNode.ID] - if posNode.PRank.IsZero() || negNode.PRank.IsZero() { + if posNode.PRank == 0 { return } - negConsumer := graph.initNode(negConsumerInput.ID, negConsumerInput, Positive) - one := graph.Precision + if negNode.PRank >= posNode.PRank { + panic("negative ranking nodes should not have any degree") // this should never happen + } - // posNode.rank is not 0 check above - negPosRatio := negNode.PRank.Mul(graph.Precision).Quo(posNode.PRank) + negConsumer := graph.initNode(negConsumerInput.ID, negConsumerInput, Positive) - var negMultiple sdk.Uint + var negMultiple float64 - // if negPosRatio > MaxNegOffset / (MaxNegOffset + 1) we use the MaxNegOffset // this first case should not happen because we ignore these links - if negPosRatio.GT(graph.MaxNegOffset.Mul(graph.Precision).Quo(graph.MaxNegOffset.Add(one))) { - negMultiple = graph.MaxNegOffset + if negNode.PRank/posNode.PRank > MaxNegOffset/(MaxNegOffset+1) { + // cap the degree multiple at MAX_NEG_OFFSET + negMultiple = MaxNegOffset } else { - denom := one.Sub(negPosRatio) - negMultiple = one.Mul(graph.Precision).Quo(denom).Sub(one) + negMultiple = 1/(1-negNode.PRank/posNode.PRank) - 1 } - // cap the vote decrease at 10x - negWeight := negMultiple.Mul(graph.nodes[negNode.ID].degree).Quo(graph.Precision) - // this should actually never happen if degree is > 0 - if _, ok := graph.edges[negNode.ID]; ok == false { - graph.edges[negNode.ID] = map[string]sdk.Uint{} - } + // this is the weight we add to the outgoing node + negWeight := negMultiple * graph.nodes[negNode.ID].degree - if _, ok := graph.edges[negNode.ID][negConsumer.ID]; ok == false { - graph.edges[negNode.ID][negConsumer.ID] = sdk.ZeroUint() + if _, ok := graph.edges[negNode.ID]; ok == false { + graph.edges[negNode.ID] = map[string]float64{} } - - graph.edges[negNode.ID][negConsumer.ID] = graph.edges[negNode.ID][negConsumer.ID].Add(negWeight) - graph.nodes[negNode.ID].degree = graph.nodes[negNode.ID].degree.Add(negWeight) + graph.edges[negNode.ID][negConsumer.ID] += negWeight + graph.nodes[negNode.ID].degree += negWeight } } @@ -221,39 +192,36 @@ func (graph *Graph) cancelOpposites(sourceNode Node, target string, nodeType Nod opositeEdge := graph.edges[sourceNode.ID][oppositeKey] switch { - case opositeEdge.GT(edge): + case opositeEdge > edge: graph.removeEdge(sourceNode.ID, key) - graph.edges[sourceNode.ID][oppositeKey] = opositeEdge.Sub(edge) + graph.edges[sourceNode.ID][oppositeKey] -= edge // remove degree from both delete node and the adjustment - sourceNode.degree = sourceNode.degree.Sub(edge.Mul(sdk.NewUint(2))) + sourceNode.degree -= 2 * edge - case edge.GT(opositeEdge): + case edge > opositeEdge: graph.removeEdge(sourceNode.ID, oppositeKey) - graph.edges[sourceNode.ID][key] = edge.Sub(opositeEdge) + graph.edges[sourceNode.ID][key] -= opositeEdge // remove degree from both delete node and the adjustment - sourceNode.degree = sourceNode.degree.Sub(opositeEdge.Mul(sdk.NewUint(2))) + sourceNode.degree -= 2 * opositeEdge - case edge.Equal(opositeEdge): + case edge == opositeEdge: graph.removeEdge(sourceNode.ID, oppositeKey) graph.removeEdge(sourceNode.ID, key) - - sourceNode.degree = sourceNode.degree.Sub(opositeEdge.Mul(sdk.NewUint(2))) + sourceNode.degree -= 2 * opositeEdge } } -// InitPosNode initialized a positive node +// InitPosNode is a helper method that initializes a positive node func (graph *Graph) InitPosNode(inputNode Node) *Node { return graph.initNode(inputNode.ID, inputNode, Positive) } -// initNode initialized a node +// initNode initializes a node func (graph *Graph) initNode(key string, inputNode Node, nodeType NodeType) *Node { if _, ok := graph.nodes[key]; ok == false { graph.nodes[key] = &Node{ ID: inputNode.ID, // id is independent of pos/neg keys - degree: sdk.ZeroUint(), - PRank: sdk.ZeroUint(), - NRank: sdk.ZeroUint(), + degree: 0, nodeType: nodeType, } // store negative nodes so we can easily merge them later @@ -262,7 +230,7 @@ func (graph *Graph) initNode(key string, inputNode Node, nodeType NodeType) *Nod } } // update rank here in case we initilized with 0 early on - var prevRank sdk.Uint + var prevRank float64 if prevRank = inputNode.PRank; nodeType == Negative { prevRank = inputNode.NRank } diff --git a/rep/pagerank.go b/rep/pagerank.go index 7b24b14..2b94a8f 100644 --- a/rep/pagerank.go +++ b/rep/pagerank.go @@ -1,7 +1,7 @@ -package detrep +package rep import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "math" ) // Rank computes the PageRank of every node in the directed graph. @@ -9,13 +9,11 @@ import ( // ε (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 (graph Graph) Rank(callback func(key string, pRank sdk.Uint, nRank sdk.Uint)) { +func (graph Graph) Rank(callback func(key string, pRank float64, nRank float64)) { graph.Finalize() - one := graph.Precision - - Δ := one - N := sdk.NewUint(uint64(len(graph.nodes))) + Δ := float64(1.0) + N := float64(len(graph.nodes)) pVector := graph.params.personalization ε := graph.params.ε α := graph.params.α @@ -28,9 +26,9 @@ func (graph Graph) Rank(callback func(key string, pRank sdk.Uint, nRank sdk.Uint // Normalize all the edge weights so that their sum amounts to 1. for source := range graph.nodes { - if graph.nodes[source].degree.GT(sdk.ZeroUint()) { + if graph.nodes[source].degree > 0 { for target := range graph.edges[source] { - graph.edges[source][target] = graph.edges[source][target].Mul(graph.Precision).Quo(graph.nodes[source].degree) + graph.edges[source][target] /= graph.nodes[source].degree } } } @@ -38,30 +36,29 @@ func (graph Graph) Rank(callback func(key string, pRank sdk.Uint, nRank sdk.Uint graph.initScores(N, pWeights) iter := 0 - for Δ.GT(ε) { - danglingWeight := sdk.ZeroUint() - nodes := map[string]sdk.Uint{} + for Δ > ε { + danglingWeight := float64(0) + nodes := map[string]float64{} for key, value := range graph.nodes { nodes[key] = value.PRank - if value.degree.IsZero() { - danglingWeight = danglingWeight.Add(value.PRank) + if value.degree == 0 { + danglingWeight += value.PRank } - graph.nodes[key].PRank = sdk.ZeroUint() + graph.nodes[key].PRank = 0 } - danglingWeight = danglingWeight.Mul(α).Quo(graph.Precision) + danglingWeight *= α for source := range graph.nodes { for target, weight := range graph.edges[source] { - addWeight := α.Mul(nodes[source]).Quo(graph.Precision).Mul(weight).Quo(graph.Precision) - graph.nodes[target].PRank = graph.nodes[target].PRank.Add(addWeight) + graph.nodes[target].PRank += α * nodes[source] * weight } if !personalized { - graph.nodes[source].PRank = graph.nodes[source].PRank.Add(one.Sub(α).Quo(N).Add(danglingWeight.Quo(N))) + graph.nodes[source].PRank += (1-α)/N + danglingWeight/N } } @@ -69,23 +66,14 @@ func (graph Graph) Rank(callback func(key string, pRank sdk.Uint, nRank sdk.Uint // this makes pagerank sybil resistant if personalized { for i, root := range pVector { - graph.nodes[root].PRank = graph.nodes[root].PRank.Add((one.Sub(α).Add(danglingWeight)).Mul(pWeights[i])).Quo(graph.Precision) + graph.nodes[root].PRank += (1 - α + danglingWeight) * pWeights[i] } } - Δ = sdk.ZeroUint() + Δ = 0 for key, value := range graph.nodes { - var diff sdk.Uint - // if _, ok := nodes[key]; ok == false { - // nodes[key] = sdk.ZeroUint() - // } - if value.PRank.LT(nodes[key]) { - diff = nodes[key].Sub(value.PRank) - } else { - diff = value.PRank.Sub(nodes[key]) - } - Δ = Δ.Add(diff) + Δ += math.Abs(value.PRank - nodes[key]) } iter++ } @@ -96,16 +84,15 @@ func (graph Graph) Rank(callback func(key string, pRank sdk.Uint, nRank sdk.Uint // make sure the total start sum of all scores is 1 // we initialze the start scores to optimize the computation -func (graph Graph) initScores(N sdk.Uint, pWeights []sdk.Uint) { +func (graph Graph) initScores(N float64, pWeights []float64) { // get sum of all node scores personalization := graph.params.personalization - totalScore := sdk.ZeroUint() + var totalScore float64 for _, node := range graph.nodes { - totalScore = totalScore.Add(node.PRank) + totalScore += node.PRank } - // if start sum is close to 1 we are done - if totalScore.GT(graph.Precision.MulUint64(9).QuoUint64(10)) { + if totalScore > .9 { return } @@ -113,40 +100,40 @@ func (graph Graph) initScores(N sdk.Uint, pWeights []sdk.Uint) { if len(pWeights) == 0 { // initialize all nodes if there is no personalizeation vector for key := range graph.nodes { - graph.nodes[key].PRank = graph.nodes[key].PRank.Add(graph.Precision.Sub(totalScore).Quo(N)) + graph.nodes[key].PRank += (1 - totalScore) / N } return } // initialize personalization vector for i, root := range personalization { - graph.nodes[root].PRank = graph.nodes[root].PRank.Add(graph.Precision.Sub(totalScore).Mul(graph.Precision).Quo(pWeights[i])) + graph.nodes[root].PRank += (1 - totalScore) * pWeights[i] } } // compute personalization weights based on degree // this ensures source nodes will have the same weight // we also update start scores here -func (graph Graph) initPersonalizationNodes() []sdk.Uint { +func (graph Graph) initPersonalizationNodes() []float64 { pVector := graph.params.personalization - pWeights := make([]sdk.Uint, len(pVector)) + pWeights := make([]float64, len(pVector)) - pWeightsSum := sdk.ZeroUint() - scoreSum := sdk.ZeroUint() + var pWeightsSum float64 + var scoreSum float64 for i, key := range pVector { - var d sdk.Uint + var d float64 // root node score and weight should not be 0 - if d = graph.Precision; graph.nodes[key].degree.GT(sdk.ZeroUint()) { + if d = 1; graph.nodes[key].degree > 0 { d = graph.nodes[key].degree } pWeights[i] = d - pWeightsSum = pWeightsSum.Add(d) - scoreSum = scoreSum.Add(graph.nodes[key].PRank) + pWeightsSum += d + scoreSum += graph.nodes[key].PRank } // normalize personalization weights for i, key := range pVector { - pWeights[i] = pWeights[i].Mul(graph.Precision).Quo(pWeightsSum) - graph.nodes[key].PRank = scoreSum.Mul(pWeights[i]).Quo(graph.Precision) + pWeights[i] /= pWeightsSum + graph.nodes[key].PRank = scoreSum * pWeights[i] } return pWeights diff --git a/rep/pagerank_test.go b/rep/pagerank_test.go index db1ec0e..3d8f621 100644 --- a/rep/pagerank_test.go +++ b/rep/pagerank_test.go @@ -1,26 +1,22 @@ -package detrep +package rep import ( "reflect" "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" ) type Result struct { - pRank sdk.Uint - nRank sdk.Uint + pRank float64 + nRank float64 } -var zero = sdk.ZeroUint() - func TestEmpty(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) actual := map[string]Result{} expected := map[string]Result{} - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -33,91 +29,85 @@ func TestEmpty(t *testing.T) { } func TestSimple(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) - d := NewNodeInputHelper("d", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) + d := NewNode("d", 0, 0) // circle - graph.LinkHelper(a, b, 1.0) - graph.LinkHelper(b, c, 1.0) - graph.LinkHelper(c, d, 1.0) - graph.LinkHelper(d, a, 1.0) + graph.Link(a, b, 1.0) + graph.Link(b, c, 1.0) + graph.Link(c, d, 1.0) + graph.Link(d, a, 1.0) actual := map[string]Result{} expected := map[string]Result{ - "a": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, - "b": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, - "c": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, - "d": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, + "a": {pRank: 0.25, nRank: 0}, + "b": {pRank: 0.25, nRank: 0}, + "c": {pRank: 0.25, nRank: 0}, + "d": {pRank: 0.25, nRank: 0}, } - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - for key, value := range actual { - expVal := expected[key] - if !value.pRank.Equal(expVal.pRank) { - t.Error("Expected", expVal.pRank.String(), "but got", value.pRank.String()) - } - if !value.nRank.Equal(expVal.nRank) { - t.Error("Expected", expVal.nRank.String(), "but got", value.nRank.String()) - } + if reflect.DeepEqual(actual, expected) != true { + t.Error("Expected", expected, "but got", actual) } } func TestWeighted(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) - graph.LinkHelper(a, b, 1.0) - graph.LinkHelper(a, c, 2.0) + graph.Link(a, b, 1.0) + graph.Link(a, c, 2.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if actual["b"].pRank.GTE(actual["c"].pRank) { - t.Errorf("rank of b %s is not > c %s", actual["b"].pRank.String(), actual["c"].pRank.String()) + if actual["b"].pRank >= actual["c"].pRank { + t.Errorf("rank of b %f is not > c %f", actual["b"].pRank, actual["c"].pRank) } } func TestPersonalized(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) - d := NewNodeInputHelper("d", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) + d := NewNode("d", 0, 0) graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 1.0) - graph.LinkHelper(d, c, 2.0) + graph.Link(a, b, 1.0) + graph.Link(d, c, 2.0) actual := map[string]Result{} expected := map[string]Result{ - "a": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, - "b": {pRank: FtoBD(0.25), nRank: FtoBD(0)}, - "c": {pRank: FtoBD(0), nRank: FtoBD(0)}, - "d": {pRank: FtoBD(0), nRank: FtoBD(0)}, + "a": {pRank: 0.25, nRank: 0}, + "b": {pRank: 0.25, nRank: 0}, + "c": {pRank: 0, nRank: 0}, + "d": {pRank: 0, nRank: 0}, } - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -132,29 +122,29 @@ func TestPersonalized(t *testing.T) { } } -func TestPersonalizedNoLinkHelper(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) +func TestPersonalizedNoLink(t *testing.T) { + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) - d := NewNodeInputHelper("d", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) + d := NewNode("d", 0, 0) graph.AddPersonalizationNode(a) - graph.LinkHelper(b, c, 1.0) - graph.LinkHelper(d, c, 1.0) + graph.Link(b, c, 1.0) + graph.Link(d, c, 1.0) actual := map[string]Result{} expected := map[string]Result{ - "a": {pRank: FtoBD(1.0), nRank: FtoBD(0)}, - "b": {pRank: FtoBD(0), nRank: FtoBD(0)}, - "c": {pRank: FtoBD(0), nRank: FtoBD(0)}, - "d": {pRank: FtoBD(0), nRank: FtoBD(0)}, + "a": {pRank: 1.0, nRank: 0}, + "b": {pRank: 0, nRank: 0}, + "c": {pRank: 0, nRank: 0}, + "d": {pRank: 0, nRank: 0}, } - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -167,85 +157,81 @@ func TestPersonalizedNoLinkHelper(t *testing.T) { } func TestCancelOpposites(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) - d := NewNodeInputHelper("d", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) + d := NewNode("d", 0, 0) graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 1.0) - graph.LinkHelper(a, b, -1.0) - graph.LinkHelper(a, c, 2.0) - graph.LinkHelper(a, c, -1.0) - graph.LinkHelper(a, d, 1.0) - graph.LinkHelper(a, d, -2.0) + graph.Link(a, b, 1.0) + graph.Link(a, b, -1.0) + graph.Link(a, c, 2.0) + graph.Link(a, c, -1.0) + graph.Link(a, d, 1.0) + graph.Link(a, d, -2.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if !actual["b"].pRank.Add(actual["b"].nRank).Equal(sdk.ZeroUint()) { - t.Errorf("rank of b should be 0 but its %s", actual["b"].pRank.String()) + if actual["b"].pRank+actual["b"].nRank != 0 { + t.Errorf("rank of b should be 0") } - if !actual["c"].nRank.Equal(sdk.ZeroUint()) { + if actual["c"].nRank != 0 { t.Errorf("c rank should be positive") } - if !actual["d"].pRank.Equal(sdk.ZeroUint()) { + if actual["d"].pRank != 0 { t.Errorf("d rank should be negative") } } func TestNegativeLink(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) - d := NewNodeInputHelper("d", 0, 0) - e := NewNodeInputHelper("e", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) + d := NewNode("d", 0, 0) + e := NewNode("e", 0, 0) graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 2.0) - graph.LinkHelper(a, c, 1.0) - graph.LinkHelper(c, d, 1.0) - graph.LinkHelper(b, d, -1.0) - graph.LinkHelper(a, e, -1.0) + graph.Link(a, b, 2.0) + graph.Link(a, c, 1.0) + graph.Link(c, d, 1.0) + graph.Link(b, d, -1.0) + graph.Link(a, e, -1.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if actual["d"].nRank.Sub(actual["d"].pRank).LTE(sdk.ZeroUint()) { + if actual["d"].pRank-actual["d"].nRank >= 0 { t.Errorf("rank of d should be neagative") } - if !actual["e"].pRank.Equal(sdk.ZeroUint()) || actual["e"].nRank.Equal(sdk.ZeroUint()) { + if actual["e"].pRank != 0 || actual["e"].nRank == 0 { t.Errorf("pure neagative node has incorrect results") } // use prev computation as input for the next iteration - initNegativeConsumer := actual["negConsumer"].pRank - if _, ok := actual["negConsumer"]; ok == false { - initNegativeConsumer = zero - } - graph = NewGraphHelper(0.85, 0.000001, initNegativeConsumer) + graph = NewGraph(0.85, 0.000001, actual["negConsumer"].pRank) a = NewNode("a", actual["a"].pRank, actual["a"].nRank) b = NewNode("b", actual["b"].pRank, actual["b"].nRank) @@ -255,44 +241,44 @@ func TestNegativeLink(t *testing.T) { graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 2.0) - graph.LinkHelper(a, c, 1.0) - graph.LinkHelper(c, d, 1.0) - graph.LinkHelper(b, d, -1.0) - graph.LinkHelper(d, e, 1.0) + graph.Link(a, b, 2.0) + graph.Link(a, c, 1.0) + graph.Link(c, d, 1.0) + graph.Link(b, d, -1.0) + graph.Link(d, e, 1.0) - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if !actual["e"].pRank.Equal(sdk.ZeroUint()) { + if actual["e"].pRank != 0 { t.Errorf("weight of neg node should be 0") } } func TestNegativeConsumer(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) - d := NewNodeInputHelper("d", 0, 0) - e := NewNodeInputHelper("e", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) + d := NewNode("d", 0, 0) + e := NewNode("e", 0, 0) graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 1.0) - graph.LinkHelper(a, c, 2.0) - graph.LinkHelper(c, d, 1.0) - graph.LinkHelper(b, d, -1.0) - graph.LinkHelper(d, e, 1.0) + graph.Link(a, b, 1.0) + graph.Link(a, c, 2.0) + graph.Link(c, d, 1.0) + graph.Link(b, d, -1.0) + graph.Link(d, e, 1.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -300,12 +286,8 @@ func TestNegativeConsumer(t *testing.T) { }) // use prev computation as input for the next iteration - initNegativeConsumer := actual["negConsumer"].pRank - if _, ok := actual["negConsumer"]; ok == false { - initNegativeConsumer = zero - } - graph = NewGraphHelper(0.85, 0.000001, initNegativeConsumer) + graph = NewGraph(0.85, 0.000001, actual["negConsumer"].pRank) eRank := actual["e"].pRank @@ -317,44 +299,44 @@ func TestNegativeConsumer(t *testing.T) { graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 1.0) - graph.LinkHelper(a, c, 2.0) - graph.LinkHelper(c, d, 1.0) - graph.LinkHelper(b, d, -1.0) - graph.LinkHelper(d, e, 1.0) + graph.Link(a, b, 1.0) + graph.Link(a, c, 2.0) + graph.Link(c, d, 1.0) + graph.Link(b, d, -1.0) + graph.Link(d, e, 1.0) - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if eRank.LT(actual["e"].pRank) { - t.Errorf("weight of neg node should decrease") + if eRank <= actual["e"].pRank { + t.Errorf("weight of neg node should decrease %f, %f", eRank, actual["e"].pRank) } } func TestMaxNeg(t *testing.T) { - graph := NewGraphHelper(0.85, 0.000001, zero) + graph := NewGraph(0.85, 0.000001, 0) - a := NewNodeInputHelper("a", 0, 0) - b := NewNodeInputHelper("b", 0, 0) - c := NewNodeInputHelper("c", 0, 0) - d := NewNodeInputHelper("d", 0, 0) - e := NewNodeInputHelper("e", 0, 0) + a := NewNode("a", 0, 0) + b := NewNode("b", 0, 0) + c := NewNode("c", 0, 0) + d := NewNode("d", 0, 0) + e := NewNode("e", 0, 0) graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 11.0) - graph.LinkHelper(a, c, 12.0) - graph.LinkHelper(c, d, 1.0) - graph.LinkHelper(b, d, -1.0) - graph.LinkHelper(d, e, 1.0) + graph.Link(a, b, MaxNegOffset+1) + graph.Link(a, c, MaxNegOffset+2) + graph.Link(c, d, 1.0) + graph.Link(b, d, -1.0) + graph.Link(d, e, 1.0) actual := map[string]Result{} - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, @@ -362,12 +344,8 @@ func TestMaxNeg(t *testing.T) { }) // use prev computation as input for the next iteration - initNegativeConsumer := actual["negConsumer"].pRank - if _, ok := actual["negConsumer"]; ok == false { - initNegativeConsumer = zero - } - graph = NewGraphHelper(0.85, 0.000001, initNegativeConsumer) + graph = NewGraph(0.85, 0.000001, actual["negConsumer"].pRank) eRank := actual["e"].pRank @@ -375,24 +353,26 @@ func TestMaxNeg(t *testing.T) { b = NewNode("b", actual["b"].pRank, actual["b"].nRank) c = NewNode("c", actual["c"].pRank, actual["c"].nRank) d = NewNode("d", actual["d"].pRank, actual["d"].nRank) - e = NewNode("e", actual["e"].pRank, actual["e"].nRank) + e = NewNode("e", 0, 0) graph.AddPersonalizationNode(a) - graph.LinkHelper(a, b, 11.0) - graph.LinkHelper(a, c, 12.0) - graph.LinkHelper(c, d, 1.0) - graph.LinkHelper(b, d, -1.0) - graph.LinkHelper(d, e, 1.0) + graph.Link(a, b, MaxNegOffset+1) + graph.Link(a, c, MaxNegOffset+2) + graph.Link(c, d, 1.0) + graph.Link(b, d, -1.0) + graph.Link(d, e, 1.0) + + actual = map[string]Result{} - graph.Rank(func(id string, pRank sdk.Uint, nRank sdk.Uint) { + graph.Rank(func(id string, pRank float64, nRank float64) { actual[id] = Result{ pRank: pRank, nRank: nRank, } }) - if eRank.LT(actual["e"].pRank) { - t.Errorf("weight of neg node should decrease") + if eRank <= actual["e"].pRank { + t.Errorf("weight of neg node should decrease %f, %f", eRank, actual["e"].pRank) } } diff --git a/rep/processResults.go b/rep/processResults.go index 9dab16d..90c5e24 100644 --- a/rep/processResults.go +++ b/rep/processResults.go @@ -1,10 +1,6 @@ -package detrep +package rep -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func (graph Graph) processResults(callback func(id string, pRank sdk.Uint, nRank sdk.Uint)) { +func (graph Graph) processResults(callback func(id string, pRank float64, nRank float64)) { graph.mergeNegatives() for key, node := range graph.nodes { callback(key, node.PRank, node.NRank) @@ -17,8 +13,8 @@ func (graph Graph) mergeNegatives() { if _, ok := graph.nodes[node.ID]; ok == false { graph.nodes[node.ID] = &Node{ ID: key, - PRank: sdk.ZeroUint(), - degree: sdk.ZeroUint(), + PRank: 0, + degree: 0, nodeType: Positive, } }