-
Notifications
You must be signed in to change notification settings - Fork 8
/
skill_factors.go
153 lines (127 loc) · 5.57 KB
/
skill_factors.go
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
package trueskill
import (
"github.com/mafredri/go-trueskill/collection"
"github.com/mafredri/go-trueskill/factor"
"github.com/mafredri/go-trueskill/schedule"
)
type skillFactors struct {
skillPriorFactors []factor.Factor
playerPerformances []int
skillToPerformanceFactors []factor.Factor
playerPerformanceDifferences []int
performanceToPerformanceDifferencFactors []factor.Factor
greatherThanOrWithinFactors []factor.Factor
}
func buildSkillFactors(ts Config, players []Player, draws []bool, varBag *collection.DistributionBag) (skillFactors, []int, factor.List) {
gf := factor.NewGaussianFactors()
var sf skillFactors
var factorList factor.List
numPlayers := len(players)
var skillIndex []int
for i := 0; i < numPlayers; i++ {
skillIndex = append(skillIndex, varBag.NextIndex())
}
for i := 0; i < numPlayers; i++ {
priorSkill := players[i]
gpf := gf.GaussianPrior(priorSkill.Mean(), priorSkill.Variance()+(ts.tau*ts.tau), skillIndex[i], varBag)
sf.skillPriorFactors = append(sf.skillPriorFactors, gpf)
factorList.Add(gpf)
}
for i := 0; i < numPlayers; i++ {
sf.playerPerformances = append(sf.playerPerformances, varBag.NextIndex())
}
for i := 0; i < numPlayers; i++ {
glf := gf.GaussianLikeliehood(ts.beta*ts.beta, sf.playerPerformances[i], skillIndex[i], varBag, varBag)
sf.skillToPerformanceFactors = append(sf.skillToPerformanceFactors, glf)
factorList.Add(glf)
}
for i := 0; i < numPlayers-1; i++ {
sf.playerPerformanceDifferences = append(sf.playerPerformanceDifferences, varBag.NextIndex())
}
for i := 0; i < numPlayers-1; i++ {
gws := gf.GaussianWeightedSum(1.0, -1.0, sf.playerPerformanceDifferences[i], sf.playerPerformances[i],
sf.playerPerformances[i+1], varBag, varBag, varBag)
sf.performanceToPerformanceDifferencFactors = append(sf.performanceToPerformanceDifferencFactors, gws)
factorList.Add(gws)
}
for i, draw := range draws {
// TODO: Change hard-coded 2 to len(teamA) + len(teamB)
// (when teams are supported).
epsilon := drawMargin(ts.beta, ts.drawProbability, 2)
var f factor.Factor
if draw {
f = gf.GaussianWithin(epsilon, sf.playerPerformanceDifferences[i], varBag)
} else {
f = gf.GaussianGreaterThan(epsilon, sf.playerPerformanceDifferences[i], varBag)
}
sf.greatherThanOrWithinFactors = append(sf.greatherThanOrWithinFactors, f)
factorList.Add(f)
}
return sf, skillIndex, factorList
}
func skillFactorListToScheduleStep(facs []factor.Factor, idx int) []schedule.Runner {
var steps []schedule.Runner
for _, f := range facs {
step := schedule.NewStep(f.UpdateMessage, idx)
steps = append(steps, step)
}
return steps
}
// buildSkillFactorSchedule builds a full schedule that represents all the steps
// in a factor graph.
func buildSkillFactorSchedule(numPlayers int, sf skillFactors, loopMaxDelta float64) schedule.Runner {
// Prior schedule initializes the skill priors for all players and updates
// the performance
priorSchedule := schedule.NewSequence(
schedule.NewSequence(skillFactorListToScheduleStep(sf.skillPriorFactors, 0)...),
schedule.NewSequence(skillFactorListToScheduleStep(sf.skillToPerformanceFactors, 0)...),
)
// Loop schedule iterates until desired accuracy is reached
var loopSchedule schedule.Runner
if numPlayers == 2 {
// In two player mode there is no loop, just send the performance
// difference and the greater-than.
loopSchedule = schedule.NewSequence(
schedule.NewStep(sf.performanceToPerformanceDifferencFactors[0].UpdateMessage, 0),
schedule.NewStep(sf.greatherThanOrWithinFactors[0].UpdateMessage, 0),
)
} else {
// Forward schedule updates the factor graph in one direction
var forwardSchedule []schedule.Runner
// ... and the backward schedule in the other direction
var backwardSchedule []schedule.Runner
for i := 0; i < numPlayers-2; i++ {
forwardSteps := []schedule.Runner{
schedule.NewStep(sf.performanceToPerformanceDifferencFactors[i].UpdateMessage, 0),
schedule.NewStep(sf.greatherThanOrWithinFactors[i].UpdateMessage, 0),
schedule.NewStep(sf.performanceToPerformanceDifferencFactors[i].UpdateMessage, 2),
}
forwardSchedule = append(forwardSchedule, forwardSteps...)
backwardSteps := []schedule.Runner{
schedule.NewStep(sf.performanceToPerformanceDifferencFactors[numPlayers-2-i].UpdateMessage, 0),
schedule.NewStep(sf.greatherThanOrWithinFactors[numPlayers-2-i].UpdateMessage, 0),
schedule.NewStep(sf.performanceToPerformanceDifferencFactors[numPlayers-2-i].UpdateMessage, 1),
}
backwardSchedule = append(backwardSchedule, backwardSteps...)
}
// Combine the backward and forward schedule so that they are run in
// said order
combinedForwardBackwardSchedule := schedule.NewSequence(
schedule.NewSequence(forwardSchedule...),
schedule.NewSequence(backwardSchedule...),
)
// Loop through the forward and backward schedule until the delta stops
// changing by more than loopMaxDelta
loopSchedule = schedule.NewLoop(combinedForwardBackwardSchedule, loopMaxDelta)
}
innerSchedule := schedule.NewSequence(
loopSchedule,
schedule.NewStep(sf.performanceToPerformanceDifferencFactors[0].UpdateMessage, 1),
schedule.NewStep(sf.performanceToPerformanceDifferencFactors[numPlayers-2].UpdateMessage, 2),
)
// Finally send the skill performances of all players
posteriorSchedule := schedule.NewSequence(skillFactorListToScheduleStep(sf.skillToPerformanceFactors, 1)...)
// Combine all schedules into one runnable sequence
fullSchedule := schedule.NewSequence(priorSchedule, innerSchedule, posteriorSchedule)
return fullSchedule
}