root/trunk/common/rating.php

Revision 884, 8.3 KB (checked in by bogdanpasoi@…, 3 years ago)

Better rating graph.

  • Property svn:eol-style set to native
Line 
1<?php
2
3// infoarena rating system
4//
5// In laymen terms, rating systems provide a way to rank and differentiate
6// contestants in multiple-round competitions. Ratings are computed with
7// black math & statistics voodoo magic :)
8//
9// infoarena uses home-baked rating system combining features from
10// glicko, TrueSkill, ELO and possibly others.
11//
12// See development documentation on ratings:
13// http://hackers.devnet.ro/wiki/Rating
14
15// Glicko parameters
16// These may be tweaked until they yield decent ratings
17
18// Initial rating assumed for a new contestant.
19// Note that we're using standard ELO ratings (spanning 3000 and beyond)
20define("IA_RATING_INITIAL", 1500);
21// Initial deviation
22define("IA_RATING_DEVIATION", 99);
23// Deviation boundaries and median
24define("IA_RATING_MAX_DEVIATION", 360);
25define("IA_RATING_MIN_DEVIATION", 15);
26define("IA_RATING_MED_DEVIATION", 45);
27// threshold value to normalize results
28// FIXME: Ratings don't change more than this
29define("IA_RATING_MAX_DIFF", 300);
30// number of seconds in a time period
31define("IA_RATING_TIME_PERIOD", 2628000);
32// number of months before you reach maximum unreliability
33// 4 years now
34define("IA_RATING_CHAOS", 48);
35// don't ask.
36// FIXME: But I do! What do these mean?
37define("IA_RATING_C", sqrt((IA_RATING_MAX_DEVIATION * IA_RATING_MAX_DEVIATION - IA_RATING_MED_DEVIATION * IA_RATING_MED_DEVIATION) / IA_RATING_CHAOS));
38define("IA_RATING_Q", log(10.0) / 400.0);
39// we're feeding games into chunks and periodically updating ratings
40define("IA_RATING_MAX_CHUNK", 10);
41// tweak rating increases to avoid unusual behavior for huge contents
42define("IA_RATING_TWEAK_PERIOD", 3);
43define("IA_MIN_SCALED_RATING", 0);
44define("IA_MAX_SCALED_RATING", 1500);
45
46// number square
47function sqr($number) {
48    return $number * $number;
49}
50
51// init user array where we store and update ratings
52// $whole_user_list array format is (list of usernames):
53//  array(
54//      username,
55//      ...
56//  );
57// $ratings array is as returned by rating_last_scores()
58// Output array format is:
59//  array(
60//      username => array(
61//                      rating => (int),
62//                      deviation => (int),
63//                      timestamp => (int)
64//                  )
65//      ...
66//  );
67function rating_init($whole_user_list, $last_scores) {
68    $users = $last_scores;
69    foreach ($whole_user_list as $username) {
70        if (isset($users[$username])) {
71            continue;
72        }
73
74        $user = array(
75            'rating' => IA_RATING_INITIAL,
76            'deviation' => IA_RATING_DEVIATION,
77            'timestamp' => 0,
78        );
79        $users[$username] = $user;
80    }
81    return $users;
82}
83
84// update user deviation. consider now is $timestamp
85// deviations must be update before updating ratings
86//
87// $timestamp is UNIX timestamp (seconds)
88function rating_update_deviation(&$users, $username, $timestamp) {
89    log_assert(isset($users[$username]));
90
91    $user =& $users[$username];
92    $old_deviation = $user['deviation'];
93    if (!$user['timestamp']) {
94        // user never was rated, leave deviation as it is
95        log_assert(IA_RATING_DEVIATION == $user['deviation']);
96        $elapsed = 0;
97    }
98    else {
99        // compute elapsed time from the last rating to current timestamp
100        $elapsed = (int)ceil(($timestamp - $user['timestamp']) / IA_RATING_TIME_PERIOD);
101    }
102    $user['deviation'] = min(IA_RATING_MAX_DEVIATION,
103                             sqrt(sqr($old_deviation) + sqr(IA_RATING_C) * $elapsed));
104    $user['timestamp'] = $timestamp;
105}
106
107// FIXME: Throw in some comments
108function rating_gspot($deviation) {
109    $deviation = 1.0 / sqrt(1.0 + 3.0 * sqr(IA_RATING_Q) * sqr($deviation) / sqr(M_PI));
110    return $deviation;
111}
112
113// Expected score for a pair of contestants
114function rating_expected_score($rating1, $rating2, $deviation2) {
115    $diff = max(-IA_RATING_MAX_DIFF, min(IA_RATING_MAX_DIFF, $rating1 - $rating2));
116    $score = 1.0 / (1.0 + pow(10, -rating_gspot($deviation2) * $diff / 400.0));
117    return $score;
118}
119
120// FIXME: Throw in some comments
121function rating_score($score1, $score2, $variance) {
122    if ($score1 > $score2) {
123        if (!$score2) {
124           return 1.0;
125        }
126        return 0.5 + min(0.5, ($score1 - $score2) / $variance * 0.25);
127    }
128
129    if ($score1 < $score2) {
130        if (!$score1) {
131            return 0.0;
132        }
133        return 0.5 - min(0.5, ($score2 - $score1) / $variance * 0.25);
134    }
135    return 0.5;
136}
137
138// Update ratings considering user_scores. Assume now is $timestamp.
139// $timestamp is UNIX timestamp
140// FIXME: Throw in some comments
141//
142// $user_scores array format is:
143//  array(
144//      username => score,
145//      ...
146//  )
147function rating_update(&$users, $user_scores, $timestamp) {
148    if (count($user_scores) < 1) {
149        log_warn("Need more than 1 contestant to update ratings! Nothing to do!");
150        return;
151    }
152    log_print("Updating ratings for ".count($user_scores)." users");
153
154    $new_rating = array();
155    $new_deviation = array();
156    $user_count = count($user_scores);
157
158    // update deviations and compute score mean
159    $score_mean = 0;
160    foreach ($user_scores as $username => $score) {
161        rating_update_deviation($users, $username, $timestamp);
162        $score_mean += $score;
163    }
164    $score_mean /= $user_count;
165
166    // compute score variance
167    $score_variance = 0;
168    foreach ($user_scores as $username => $score) {
169        $score_variance += sqr($score - $score_mean);
170    }
171    $score_variance = sqrt($score_variance / $user_count);
172
173    // Voodoo Magic
174    // FIXME: Throw in some comments if you understand anything about this
175    $chunk_count = ceil(($user_count-1) / IA_RATING_MAX_CHUNK);
176    foreach ($user_scores as $username => $score) {
177        log_assert(isset($users[$username]));
178        $user =& $users[$username];
179
180        log_assert(isset($user['rating']) && isset($user['deviation'])
181                   && isset($user['timestamp']));
182
183        $rating1 = $user['rating'];
184        $deviation1 = $user['deviation'];
185        $pos = 0;
186        $keys = array_keys($user_scores);
187        $tweak = $chunk_count / IA_RATING_TWEAK_PERIOD;
188
189        for ($chunk = 0; $chunk < $chunk_count; $chunk++, $pos += IA_RATING_MAX_CHUNK) {
190            $D = 0.0;
191            for ($i = $pos; $i < $pos+IA_RATING_MAX_CHUNK && $i < $user_count; $i++) {
192                $username2 = $keys[$i];
193                $score2 = $user_scores[$username2];
194                log_assert(isset($users[$username2]));
195                $user2 = $users[$username2];
196                if ($username2 == $username) {
197                    continue;
198                }
199                $rating2 = $user2['rating'];
200                $deviation2 = $user2['deviation'];
201
202                $temp = rating_expected_score($rating1, $rating2, $deviation2);
203                $D = $D + sqr(rating_gspot($deviation2)) * $temp * (1.0 - $temp);
204            }
205            $D = $D * sqr(IA_RATING_Q);
206
207            $R = 0.0;
208            for ($i = $pos; $i < $pos+IA_RATING_MAX_CHUNK && $i < $user_count; $i++) {
209                $username2 = $keys[$i];
210                $score2 = $user_scores[$username2];
211                log_assert(isset($users[$username2]));
212                $user2 = $users[$username2]; 
213                if ($username2 == $username) {
214                    continue;
215                }
216                $rating2 = $user2['rating'];
217                $deviation2 = $user2['deviation'];
218
219                $temp = rating_expected_score($rating1, $rating2, $deviation2);
220                $weight = max(IA_RATING_Q * rating_gspot($deviation2) * sqr(IA_RATING_MIN_DEVIATION),
221                              IA_RATING_Q * rating_gspot($deviation2) / (1.0 / sqr($deviation1) + $D));
222                $R = $R + $weight * (rating_score($score, $score2, $score_variance) - $temp);
223            }
224
225            $rating1 = max(0, $rating1 + $R / $tweak);
226            $deviation1 = max(IA_RATING_MIN_DEVIATION,
227                              sqrt(1.0 / (1.0/sqr($deviation1)+$D)));
228        }
229
230        // update rating and deviation
231        $user['rating'] = $rating1;
232        $user['deviation'] = $deviation1;
233    }
234}
235
236// Represent rating in a human-friendly scale from 0 to 1000
237// NOTE: This is used only when displaying ratings to users.
238// NOTE: Currently used by www/format/* and scripts/send-newsletter.
239function rating_scale($absolute_rating) {
240    return min(max(IA_MIN_SCALED_RATING, round($absolute_rating / 3.0)), 
241                   IA_MAX_SCALED_RATING);
242}
243
244?>
Note: See TracBrowser for help on using the browser.