-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathtrustnet.js
194 lines (178 loc) · 8.55 KB
/
trustnet.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
const stats = require("simple-statistics")
const appleseed = require("appleseed-metric")
const Graph = require("trustnet-graph")
const debug = require("debug")("trustnet")
const util = require("./util.js")
module.exports = TrustNet
function TrustNet (opts) {
opts = opts || {}
if (!(this instanceof TrustNet)) return new TrustNet(opts)
// the source to compute trust graph from
this.rootid = null
// the computed rankings, as seen from this.rootid
this.rankings = null
// the graph of trust assignments
this.graph = null
// true if the trust graph only consists of direct assignments, i.e. it is without transitive trust
this.isFirstOrder = true
// used for highOnlyStrategy. if there are no direct trust assignments > threshold -> return empty list in getMostTrusted
// rationale: if none of the trust source's direct trust assignments are greater than the threshold then the
// resulting rankings will be misrepresentative; too much energy will be distributed to nodes which would not
// necessarily be trusted
this.trustThreshold = opts.threshold || 0.50
}
TrustNet.prototype.load = async function (rootid, assignments, distrusted=[]) {
this.rootid = `${rootid}` // force to string
// filter out distrusted nodes
let trustAssignments = assignments.filter((t) => !distrusted.includes(t.src) && !distrusted.includes(t.dst))
this.isFirstOrder = this._isFirstOrderGraph(this.rootid, trustAssignments)
if (this.isFirstOrder) {
debug("only first order graph. skip")
this.graph = Graph(trustAssignments)
this.rankings = this._firstOrderRankings(this.rootid, trustAssignments)
return
}
debug("trustAssignments %O", assignments)
debug("distrusted ids %O", distrusted)
let result = await appleseed(this.rootid, trustAssignments, 200, 0.85, 0.01)
this.rankings = result.rankings
debug("rankings %O", this.rankings)
this.graph = result.graph
}
TrustNet.prototype.getMostTrusted = function () {
if (this.rootid) {
debug("most trusted for", this.rootid)
}
if (this.rankings === null) return []
return Object.keys(this._highOnlyStrategy())
}
TrustNet.prototype.getRankings = function () {
if (this.rankings === null) return {}
const nonzeroRankings = Object.keys(this.rankings).reduce((obj, key) => {
const rank = parseFloat(this.rankings[key])
if (rank > 0) obj[key] = rank
return obj
}, {})
debug("get rankings %O", nonzeroRankings)
return nonzeroRankings
}
TrustNet.prototype.getAllTrusted = function () {
// return all entries with some non-zero measure of trust (ranking > 0), and add the rootid as well
return Object.entries(this.rankings).filter((e) => e[1] > 0).map((e) => e[0]).concat(this.rootid)
}
function getBreaks (rankings, breaks) {
let scores = Object.values(rankings)
if (scores.length < breaks) {
breaks = scores.length
}
let trustGroups = stats.ckmeans(scores, breaks)
return { groups: trustGroups, breaks }
}
function naiveHighTrustStrategy (rankings) {
const tweakedRankings = Object.assign({}, rankings)
// insert a sentinel with a ranking of 0, that way we always have at least one value in the cluster of lowest values.
// helps to avoid sorting away useful values when total # of ranks is low
const sentinel = `sentinel-${+(new Date())}`
tweakedRankings[sentinel] = 0
debug("rankings", rankings)
let { groups, breaks } = getBreaks(tweakedRankings, 3)
// filter out sentinel, and participants with no trust
groups[0] = groups[0].filter((n) => n > 0)
debug("groups", groups)
debug("breaks", breaks)
// exclude lowest trusted group; merge the other two clusters and use as result
// rationale: we exclude the lowest trusted group as that is the least relevant part of the ranked trust graph.
// the other two clusters contain direct trust assignments issued from the trust source, as well as nodes which are
// highly regarded in their personal trust network
let highestGroup = groups[groups.length-1].concat(groups[groups.length-2])
debug("highest group %O", highestGroup)
let result = {}
// now we need to remap the values we used for clustering to their actual ids
// make a copy of the rankings as we'll be deleting the seen rank and id couples
// to prevent matching the same id twice if the rank ends up being the same
let rankingsCopy = Object.assign({}, rankings)
for (let rank of highestGroup) {
let id = util.rank2id(rank, rankingsCopy)
delete rankingsCopy[id]
result[id] = rank
}
debug("high trust strategy result: %O", result)
return result
}
// non-naive version accounts for initial context of the source node's trust assignments
// if source hasn't issued any high-trust assignments, appleseed's top rankings will be
// misleading
TrustNet.prototype._highOnlyStrategy = function () {
debug("entering high only strategy")
let hasHighTrust = false
const highTrustEdges = []
for (let edge of this.graph.outEdges(this.rootid)) {
if (edge.weight >= this.trustThreshold) {
highTrustEdges.push(edge)
}
}
// collect the first order edges with a non-zero positive trust weight
const firstOrderEdges = this.graph.outEdges(this.rootid).filter((n) => n.weight > 0)
// transform first order edges into nodes
const firstOrderNodes = firstOrderEdges.map((n) => n.dst)
// merge an array of objects with another object
function merge (obj, arrayOfObj) {
const newObj = Object.assign({}, obj)
arrayOfObj.forEach((o) => { obj = Object.assign(newObj, o) })
return newObj
}
let result = {}
// there were no high trust assignments originating from the source, or we were operating on a first order (no transitive edges) graph
// => the highest rankings from appleseed aren't trustworthy enough. only return first order edges
if (highTrustEdges.length === 0) {
debug("no high trust edges, returning missing rankings")
return merge(result, this._getMissingRankingsList(firstOrderNodes))
} else if (this.isFirstOrder) {
debug("first order graph, returning edges to ranked form")
return util.edgesToRankedForm(firstOrderEdges)
}
result = naiveHighTrustStrategy(this.rankings)
// now we need to do some extra processing to ensure that *all* the first order edges are returned.
// rationale: they have been trusted firsthand by this.rootid => i.e. they are one of the most trusted (it's just
// they might be low trusted, due to their poor judgement - they themselves *are* trusted, though)
// i *might* have read a small article on functional programming before writing the following lines..
const appleseedNodes = Object.keys(result)
const missingFirstOrderNodes = firstOrderNodes.filter((n) => !appleseedNodes.includes(n))
// extend result with all of the missing first order nodes, carrying over their rankings
const missingEdgesRanked = this._getMissingRankingsList(missingFirstOrderNodes)
debug("fully computed, returning augmented appleseed edges")
return merge(result, missingEdgesRanked)
}
TrustNet.prototype._isFirstOrderGraph = function (root, assignments) {
const g = Graph(assignments)
debug("isFirstOrder: root's outgoing edges %O", g.outEdges(root))
const outgoingNodes = g.outEdges(root).filter((e) => e.weight > 0).map((e) => e.dst)
debug("isFirstOrder: root's filtered outgoing nodes %O", outgoingNodes)
// traverse the root's outgoing edges. for each destination node, see if *it* has any outgoing edges.
// if a node does have outgoing edges, our graph has a depth greater than 1
for (const node of outgoingNodes) {
const edges = g.outEdges(node)
debug("isFirstOrder edges %O", edges)
if (edges.length > 0 && edges.filter((e) => e.weight > 0 && e.dst !== root).length > 0) return false
}
// otherwise, if we can't find any such nodes, return true
return true
}
TrustNet.prototype._getMissingRankingsList = function (nodes) {
return nodes.map((n) => {
let o = {}
o[n] = this.rankings[n] || -1
return o
})
}
TrustNet.prototype._getFirstOrderEdges = function (rootid, assignments) {
let g = Graph(assignments)
return g.outEdges(rootid)
}
TrustNet.prototype._getFirstOrderNodes = function (g, rootid) {
return g.outEdges(rootid).map((e) => e.dst)
}
TrustNet.prototype._firstOrderRankings = function (rootid, assignments) {
const edges = this._getFirstOrderEdges(rootid, assignments)
return util.edgesToRankedForm(edges)
}