prologin2014_ranking

Scripts to compute the final ranking of Prologin 2014
git clone https://esimon.eu/repos/prologin2014_ranking
Log | Files | Refs | README

rank.py (6389B)


      1 #!/usr/bin/env python3
      2 
      3 import os
      4 import sys
      5 import time
      6 from math import log, erf, sqrt
      7 from random import shuffle
      8 import pickle
      9 from trueskill import Rating, quality, rate
     10 
     11 os.environ['DJANGO_SETTINGS_MODULE'] = 'prologin.concours.settings'
     12 sys.path.insert(0, '/root/sadm/python-lib')
     13 from django.contrib.auth.models import User
     14 from prologin.concours.stechec.models import *
     15 
     16 # Number of players per game
     17 party_size = 4
     18 
     19 # Quality of sampling new members for a party
     20 sample_size = 1
     21 
     22 # The conservative score (aka "at least this good") is : mean - conservativeness * stddev
     23 conservativeness = 3
     24 
     25 # Number of matches running at the same time
     26 max_concurrent_matches = 250
     27 
     28 # Maximum number of simultaneous matches per player
     29 concurrent_match_per_player = 50
     30 
     31 # Maximum difference in number of concurrent matches between the player who is playing the most and the one who is playing the least
     32 min_ppp_margin = 2
     33 
     34 # Frequency (in number of launched games) at which game results are gathered
     35 update_score_frequency = 100
     36 
     37 tournament_name='Tournoi final'
     38 
     39 filepath=sys.argv[1]
     40 with open(filepath, 'rb') as f:
     41     ratings = pickle.load(f)
     42 
     43 current_matches = []
     44 nb_match_per_player = {}
     45 champions = {}
     46 users = {}
     47 for user in ratings.keys():
     48     # The last non deleted champion of a user
     49     champion = Champion.objects.filter(author__username=user).order_by('-ts').exclude(deleted=True)[0].pk
     50     nb_match_per_player[user] = 0
     51     champions[user] = champion
     52     users[champion] = user
     53 
     54 def launch_match(cs):
     55     match = Match()
     56     match.author = User.objects.get(username='epsilon012')
     57     match.tournament = Tournament.objects.get(name=tournament_name)
     58     match.save()
     59 
     60     for c in cs:
     61         mc = MatchPlayer()
     62         mc.champion = Champion.objects.get(pk=c)
     63         mc.match = match
     64         mc.save()
     65 
     66     match.status = 'new'
     67     match.save()
     68 
     69     return match.pk
     70 
     71 def is_match_complete(match):
     72     m = Match.objects.filter(pk=match)
     73     return m.status == 'done' 
     74 
     75 def get_match_results(match):
     76     players = MatchPlayer.objects.filter(match__pk=match)
     77     scores = dict((p.champion.pk, p.score) for p in players)
     78     return scores
     79 
     80 def do_match(party):
     81     for member in party:
     82         nb_match_per_player[member] += 1
     83     party_champ = [ champions[member] for member in party ]
     84     pk=launch_match(party_champ)
     85     current_matches.append(pk)
     86     return pk
     87 
     88 def conservative_estimate(key):
     89     return ratings[key].mu - conservativeness * ratings[key].sigma
     90 
     91 def can_enter_match(user):
     92     return nb_match_per_player[user] < concurrent_match_per_player
     93 
     94 def match_making():
     95     # Number of game of the player who is playing the least
     96     min_ppp = min(nb_match_per_player.values())
     97     pick_from_me = [ user for user in ratings.keys() if nb_match_per_player[user] <= min_ppp + min_ppp_margin]
     98 
     99     ready = list(filter(can_enter_match, ratings.keys()))
    100 
    101     # The player with the most uncertain rating, he'll be in the party no matter what
    102     fuzziest = max(pick_from_me, key=lambda x: ratings[x].sigma)
    103     ready.remove(fuzziest)
    104 
    105     # Fill sample_me with available ("ready") players, the remaining (party_size-1) players to be picked will be consecutive players in this array
    106     # The //x*x is done so that sample_me is extend with arrays of size multiple of (party_size-1), this mean we can't sample a party from two different shuffled ready arrays, since doing so might result in a match with the same player appearing twice
    107     sample_me = []
    108     for _ in range(sample_size):
    109         shuffle(ready)
    110         sample_me += ready[:len(ready)//(party_size-1)*(party_size-1)]
    111 
    112     # We pick the remaining (party_size-1) players from sample_me to maximize the match quality
    113     max_qual = -1
    114     max_party = None
    115     for i in range(len(sample_me)//(party_size-1)):
    116         party_minus_1 = sample_me[(party_size-1)*i:(party_size-1)*(i+1)]
    117         party = party_minus_1 + [ fuzziest ]
    118         gr = [[ratings[member]] for member in party]
    119         qual = quality(gr)
    120 
    121         if qual > max_qual:
    122             max_qual = qual
    123             max_party = party
    124 
    125     return max_party
    126 
    127 # update ratings given the result of a match in a dictionary {user: score}
    128 def update(scores):
    129     for key in scores:
    130         nb_match_per_player[key] -= 1
    131         scores[key] = -scores[key]
    132 
    133     if not any(scores.values()):
    134         return
    135 
    136     new_rating = rate([(ratings[key],) for key in scores], scores.values())
    137     for key, value in zip(scores.keys(), new_rating):
    138         ratings[key] = value[0]
    139 
    140     with open(filepath, 'wb') as f:
    141         pickle.dump(ratings, f)
    142 
    143 # Check if any matches finished and update ratings accordingly
    144 def update_scores():
    145     global current_matches
    146     remove_me = []
    147     for match_pk in current_matches:
    148         match = Match.objects.filter(pk=match_pk)[0]
    149         if match.status == 'done':
    150             print("Match {0:<5} done".format(match_pk))
    151             remove_me.append(match_pk)
    152             scores_champ = get_match_results(match_pk)
    153             update(dict((users[key], scores_champ[key]) for key in scores_champ))
    154     current_matches = [ item for item in current_matches if item not in remove_me ]
    155 
    156 if(len(sys.argv)>2):
    157     N = int(sys.argv[2])
    158 else:
    159     n = len(ratings)
    160     N = int(round(n*log(n, 2)))
    161 
    162 for epoch in range(N):
    163     party = match_making()
    164     if party is None:
    165         print("Epoch {0:<5} No match".format(epoch))
    166         time.sleep(3)
    167     else:
    168         id_match = do_match(party)
    169         print("Epoch {0:<5} id {1:<5} party {2}".format(epoch, id_match, party))
    170 
    171     while len(current_matches) >= max_concurrent_matches:
    172         update_scores()
    173         time.sleep(3)
    174 
    175     if (epoch+1) % update_score_frequency == 0:
    176         update_scores()
    177 
    178 while len(current_matches) > 0:
    179     time.sleep(5)
    180     update_scores()
    181 
    182 leaderboard = list(ratings.keys())
    183 #leaderboard.sort(key=conservative_estimate, reverse=True)
    184 leaderboard.sort(key=lambda x: ratings[x].mu, reverse=True)
    185 for (position, champion) in enumerate(leaderboard):
    186     if position!=len(leaderboard)-1:
    187         std = (ratings[leaderboard[position]].mu - ratings[leaderboard[position+1]].mu) / (ratings[leaderboard[position]].sigma)
    188         std = erf(std/sqrt(2))
    189     else:
    190         std = '-'
    191     print("{0:<2} champion {1:<15} estimate {2:<20} mu {3:<20} sigma {4:<20} prob {5:<20}".format(position+1, champion, conservative_estimate(champion), ratings[champion].mu, ratings[champion].sigma, std))