Completed
Push — master ( a4261d...fc5e52 )
by John
14s
created

GroupRatingCalculator   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 195
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 116
dl 0
loc 195
rs 9.6
c 0
b 0
f 0
wmc 35

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A storage() 0 19 2
A getRatingToRank() 0 12 3
A getSeed() 0 6 2
A sort() 0 3 1
A getEloWinProbability() 0 2 1
B calculate() 0 56 10
A reassignRank() 0 21 5
A getRecord() 0 20 3
B validateDeltas() 0 21 7
1
<?php
2
3
namespace App\Models\Rating;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Facades\DB;
7
use App\Models\ContestModel;
8
use Log;
9
10
class GroupRatingCalculator extends Model
11
{
12
    public $cid=0;
13
    public $contestants=[];
14
    public $totParticipants=0;
15
    public $INITIAL_RATING=1500;
16
17
    public function __construct($cid){
18
        $contestModel = new ContestModel();
19
        $this->cid = $cid;
20
        $this->gid = $contestModel->gid($cid);
0 ignored issues
show
Bug introduced by
The property gid does not seem to exist on App\Models\Rating\GroupRatingCalculator. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
21
        // get rank
22
        $this->getRecord();
23
    }
24
25
    private function getRecord(){
26
        $contestModel = new ContestModel();
27
        $contestRankRaw = $contestModel->contestRank($this->cid);
28
        $this->totParticipants = count($contestRankRaw);
29
        $members = array_column($contestRankRaw,'uid');
30
        $ratings_temp = DB::table('group_member')
31
            ->where([
32
                'gid' => $this->gid,
33
            ])->whereIn('uid',$members)
34
            ->select('uid','ranking')
35
            ->get()->all();
36
        $ratings = [];
37
        foreach ($ratings_temp as $rating) {
38
            $ratings[$rating['uid']] = $rating['ranking'];
39
        }
40
        foreach($contestRankRaw as $c){
41
            $this->contestants[]=[
42
                "uid"=>$c["uid"],
43
                "points"=>$c["score"],
44
                "rating"=>$ratings[$c['uid']],
45
            ];
46
        }
47
    }
48
49
    private function reassignRank(){
50
        $this->sort("points");
51
        $idx = 0;
52
        $points = $this->contestants[0]["points"];
53
        $i = 1;
54
        while($i < $this->totParticipants){
55
            if($this->contestants[$i]["points"] < $points){
56
                $j = $idx;
57
                while($j < $i){
58
                    $this->contestants[$j]["rank"] = $i;
59
                    $j += 1;
60
                }
61
                $idx = $i;
62
                $points = $this->contestants[$i]["points"];
63
            }
64
            $i += 1;
65
        }
66
        $j = $idx;
67
        while($j < $this->totParticipants){
68
            $this->contestants[$j]["rank"] = $this->totParticipants;
69
            $j += 1;
70
        }
71
    }
72
73
    private function getEloWinProbability($Ra, $Rb){
74
        return 1.0 / (1 + pow(10, ($Rb-$Ra)/400.0));
75
    }
76
77
    private function getSeed($rating){
78
        $result = 1.0;
79
        foreach($this->contestants as $other){
80
            $result += $this->getEloWinProbability($other["rating"], $rating);
81
        }
82
        return $result;
83
    }
84
85
    private function getRatingToRank($rank){
86
        $left=1;
87
        $right=8000;
88
        while($right - $left > 1){
89
            $mid = floor(($right + $left)/2);
90
            if($this->getSeed($mid) < $rank){
91
                $right = $mid;
92
            }else{
93
                $left = $mid;
94
            }
95
        }
96
        return $left;
97
    }
98
99
    private function sort($key){
100
        usort($this->contestants, function ($a, $b) use ($key) {
101
            return $b[$key] <=> $a[$key];
102
        });
103
    }
104
105
    public function calculate(){
106
        if(empty($this->contestants)){
107
            return;
108
        }
109
110
        // recalc rank
111
        $this->reassignRank();
112
113
        foreach($this->contestants as &$member){
114
            $member["seed"] = 1.0;
115
            foreach($this->contestants as $other){
116
                if($member["uid"] != $other["uid"]){
117
                    $member["seed"] += $this->getEloWinProbability($other["rating"], $member["rating"]);
118
                }
119
            }
120
        }
121
        unset($member);
122
123
        foreach($this->contestants as &$contestant){
124
            $midRank = sqrt($contestant["rank"] * $contestant["seed"]);
125
            $contestant["needRating"] = $this->getRatingToRank($midRank);
126
            $contestant["delta"] = floor(($contestant["needRating"] - $contestant["rating"])/2);
127
        }
128
        unset($contestant);
129
130
        $this->sort("rating");
131
132
        // DO some adjuct
133
        // Total sum should not be more than ZERO.
134
        $sum = 0;
135
136
        foreach($this->contestants as $contestant){
137
            $sum += $contestant["delta"];
138
        }
139
        $inc = -floor($sum / $this->totParticipants) - 1;
140
        foreach($this->contestants as &$contestant){
141
            $contestant["delta"] += $inc;
142
        }
143
        unset($contestant);
144
145
        // Sum of top-4*sqrt should be adjusted to ZERO.
146
147
        $sum = 0;
148
        $zeroSumCount = min(intval(4*round(sqrt($this->totParticipants))), $this->totParticipants);
149
150
        for($i=0;$i<$zeroSumCount;$i++){
151
            $sum += $this->contestants[$i]["delta"];
152
        }
153
154
        $inc = min(max(-floor($sum / $zeroSumCount), -10), 0);
155
156
        for($i=0;$i<$zeroSumCount;$i++){
157
            $this->contestants[$i]["delta"] += $inc;
158
        }
159
160
        return $this->validateDeltas();
161
    }
162
163
    public function storage(){
164
        $contestants=$this->contestants;
165
        DB::transaction(function () use ($contestants) {
166
            foreach($contestants as $contestant){
167
                $newRating=$contestant["rating"]+$contestant["delta"];
168
                DB::table("group_member")->where([
169
                    'gid' => $this->gid,
170
                    'uid' => $contestant['uid'],
171
                ])->update([
172
                    "ranking"=>$newRating
173
                ]);
174
                DB::table("group_rated_change_log")->insert([
175
                    'gid' => $this->gid,
176
                    "uid" => $contestant["uid"],
177
                    "cid" => $this->cid,
178
                    "ranking" => $newRating
179
                ]);
180
            }
181
        }, 5);
182
    }
183
184
    private function validateDeltas(){
185
        $this->sort("points");
186
187
        for($i=0;$i<$this->totParticipants;$i++){
188
            for($j=$i+1;$j<$this->totParticipants;$j++){
189
                if($this->contestants[$i]["rating"] > $this->contestants[$j]["rating"]){
190
                    if($this->contestants[$i]["rating"] + $this->contestants[$i]["delta"] < $this->contestants[$j]["rating"] + $this->contestants[$j]["delta"]){
191
                        Log::warning("First rating invariant failed: {$this->contestants[i]["uid"]} vs. {$this->contestants[j]["uid"]}.");
0 ignored issues
show
Bug introduced by
The constant App\Models\Rating\j was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant App\Models\Rating\i was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
192
                        return false;
193
                    }
194
                }
195
196
                if($this->contestants[$i]["rating"] < $this->contestants[$j]["rating"]){
197
                    if($this->contestants[$i]["delta"] < $this->contestants[$j]["delta"]){
198
                        Log::warning("Second rating invariant failed: {$this->contestants[i]["uid"]} vs.  {$this->contestants[j]["uid"]}.");
199
                        return false;
200
                    }
201
                }
202
            }
203
        }
204
        return true;
205
    }
206
207
}
208