Direkter Discord Link:
https://discord.com/channels/425419482568196106/539960268982059008/1103213528531337246Text:
<@311675903480692737> the version 1.0
```idealized_UHO_Elo --help``` prints
```
usage: idealized_UHO_Elo [-h] [-v] [-s [SYNONYM ...]] [-p [PLAYER ...]]
[-m MAX_PAIRS] [-V]
F [F ...]
Calculate the idealized UHO Elo from pair of games
positional arguments:
F PGN file name to process
options:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-s [SYNONYM ...], --synonym [SYNONYM ...]
equivalent player names (ex: 'SF'='SF 15')
-p [PLAYER ...], --player [PLAYER ...]
restrict the processing to players in list
-m MAX_PAIRS, --max-pairs MAX_PAIRS
restrict the processing to the MAX_PAIRS first pairs
of games for each couple of players (default:
1000000000)
-V, --verbose output detailled statistics
Games must be ordered by pairs, i.e. after finding a game between player1 and
player2 the next game found with the same players must be the revenge game with
color reversed.
Formulas:
wins = WD + DW + 2 * WW
losses = LD + DL + 2 * LL
draws = WL + LW + DD
Elo = 100 * log_10(wins / losses)
2 sigma error bar = 200 / ln(10) * sqrt(1 / wins + 1 / losses))
Bugs:
Skip undefined results
Example:
idealized_UHO_Elo --synonym 'SF'='SF 15' 'Kasparov'='Kasparov, Garry' --player lc0.net.808335 SF -- foo.pgn bar.pgn
```
and a run example produced by ```idealized_UHO_Elo match-t81-swa-11061K_TC_240_4.pgn match-808335_TC_240_4.pgn``` prints
```
-----------------------------------------------------------------
# NAME | 1 2 3
-----------------------------------------------------------------
1 lc0.net.784968 | 0+-0 -14+-24 3+-35
2 lc0.net.t81-swa-11061K | 14+-24 0+-0 nan+-nan
3 lc0.net.808335 | -3+-35 nan+-nan 0+-0
-----------------------------------------------------------------
```
and a run example produced by ```idealized_UHO_Elo match-t81-swa-11061K_TC_240_4.pgn
Python script:
match from collections import Counter
from math import log10 as log_10, log as ln, sqrt
from numpy import nan, inf
MAX_PAIRS = 10**9
W, D, L, UNDEFINED = 0, 1, 2, 3
RESULTS = ["1-0", "1/2-1/2", "0-1", "*"]
RES_NAME = "WDL*"
players = dict()
player_ids = []
def add_player(player_name):
if not player_name in players:
player_id = len(players)
players[player_name] = player_id
player_ids.append(player_id)
return player_id
player_id = find(players[player_name])
players[player_name] = player_id
return player_id
def find(player_id):
father_id = player_ids[player_id]
if father_id != player_id:
player_ids[player_id] = find(father_id)
return player_ids[player_id]
def union(id1, id2):
player_ids[id2] = find(id1)
def player_id(player_name):
return find(players[player_name])
def result(string):
return RESULTS.index(string)
def is_tag(line):
return len(line) > 0 and line[0] == '['
def tag_value(line):
tag, value, _ = line.split('"')
return tag.strip('[ '), value
def warning(file, nb_games, description=""):
print("Issue after reading", nb_games, "games in", file_name)
print(description, "\n")
def init_results():
return [[0 for _ in range(3)] for _ in range(3)]
def add_result(pairing, pair_results, nb_pairs, pending_games, res, file, nb_games):
if pairing in pending_games:
warning(file, nb_games, "Games are not organised in pairs")
return
rev_pairing = tuple(reversed(pairing))
if not rev_pairing in pending_games:
pending_games[pairing] = res
return
reversed_res = pending_games.pop(rev_pairing)
if nb_pairs[pairing] == 0:
pair_results[pairing] = init_results()
pair_results[rev_pairing] = init_results()
pair_results[pairing][res][2 - reversed_res] += 1
pair_results[rev_pairing][reversed_res][2 - res] += 1
nb_pairs[pairing] += 1
nb_pairs[rev_pairing] += 1
def parse_file(file_name, pair_results, nb_pairs, max_pairs=MAX_PAIRS):
try:
with open(file_name, "r") as pgn_file:
pending_games = dict()
nb_games = 0
for line in pgn_file:
line = line.strip()
if is_tag(line):
tag, value = tag_value(line)
if tag == "White":
white = add_player(value)
elif tag == "Black":
black = add_player(value)
elif tag == "Result":
res = result(value)
pairing = (white, black)
if res != UNDEFINED and nb_pairs[pairing] < max_pairs:
add_result(pairing, pair_results, nb_pairs, pending_games, res, file_name, nb_games)
nb_games += 1
except:
if 'nb_games' not in locals():
print("Can not open", file_name, "\n")
else:
warning(file_name, nb_games)
return
if pending_games:
warning(file_name, nb_games, "{} reversed game(s) not found".format(len(pending_games)))
def idealized_UHO_Elo(pairing, pair_results, player_names, verbose=True):
(player1, player2) = pairing
if player1 == player2:
return 0, 0
if not pairing in pair_results:
return nan, nan
results = pair_results[pairing]
wins = results[W][D] + results[D][W] + 2 * results[W][W]
losses = results[L][D] + results[D][L] + 2 * results[L][L]
draws = results[L][W] + results[W][L] + results[D][D]
if wins == 0 and losses == 0:
elo = 0
error = nan
elif wins == 0:
elo = -inf
error = nan
elif losses == 0:
elo = +inf
error = nan
else:
elo = round(100 * log_10(wins / losses))
error = round(200 / ln(10) * sqrt(1 / wins + 1 / losses))
if verbose:
LIMIT = "-" * 40
print(LIMIT)
print(player_names[player1], "-", player_names[player2])
print()
for first, name1 in zip((W, D, L), RES_NAME):
for second, name2 in zip((W, D, L), RES_NAME):
print("{}{}: {}".format(name1, name2, results[first][second]))
print()
print("total:", sum(sum(line) for line in results))
print()
print("idealized UHO Elo:", elo)
print("2 sigma error bar:", error)
print(LIMIT)
return elo, error
if __name__ == "__main__":
from os.path import basename, splitext
from argparse import ArgumentParser, RawDescriptionHelpFormatter
VERSION = "1.0"
script_name, *_ = splitext(basename(__file__))
DESCRIPTION = "Calculate the idealized UHO Elo from pair of games"
EPILOG = """\
Games must be ordered by pairs, i.e. after finding a game between player1 and
player2 the next game found with the same players must be the revenge game with
color reversed.
Formulas:
wins = WD + DW + 2 * WW
losses = LD + DL + 2 * LL
draws = WL + LW + DD
Elo = 100 * log_10(wins / losses)
2 sigma error bar = 200 / ln(10) * sqrt(1 / wins + 1 / losses))
Bugs:
Skip undefined results
Example:
{} --synonym 'SF'='SF 15' 'Kasparov'='Kasparov, Garry' --player lc0.net.808335 SF -- foo.pgn bar.pgn\
""".format(script_name)
HELP_MAX_PAIRS = """
restrict the processing to the MAX_PAIRS first pairs of games for each couple
of players (default: {})
""".format(MAX_PAIRS)
try:
with open("{}.cfg".format(script_name), "r") as file:
args = file.readlines()
args = list(map(str.strip, args))
file.close()
except:
args = None
parser = ArgumentParser(script_name, description=DESCRIPTION, epilog=EPILOG,
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument("pgn_files", metavar="F", nargs="+", type=str,
help="PGN file name to process")
parser.add_argument("-v", "--version", action="version",
version="%(prog)s {}\n".format(VERSION))
parser.add_argument("-s", "--synonym", type=str, nargs="*",
help="equivalent player names (ex: 'SF'='SF 15')")
parser.add_argument("-p", "--player", type=str, nargs="*",
help="restrict the processing to players in list")
parser.add_argument("-m", "--max-pairs", type=int,
default=MAX_PAIRS, help=HELP_MAX_PAIRS)
parser.add_argument("-V", "--verbose", action='store_const', const=True,
default=False, help="output detailled statistics")
options = parser.parse_args(args)
if options.player:
for player_name in options.player:
add_player(player_name)
if options.synonym:
for synonyms in options.synonym:
name1, name2 = synonyms.split('=')
union(add_player(name1), add_player(name2))
pair_results = dict()
nb_pairs = Counter()
for file_name in options.pgn_files:
parse_file(file_name, pair_results, nb_pairs, options.max_pairs)
player_names = list(players)
elo_table = [[None for _ in range(len(players))] for _ in range(len(players))]
for player1 in range(len(players)):
for player2 in range(len(players)):
pairing = (player1, player2)
elo = idealized_UHO_Elo(pairing, pair_results, player_names, options.verbose)
elo_table[player1][player2] = elo
first_col_len = max(map(len, player_names))
sep = " |"
limit = "-" * (5 + len(sep) + first_col_len + 12 * len(players))
print()
print(limit)
print(" # NAME" + " " * (first_col_len - 4) + sep, end="")
for nb, _ in enumerate(players, 1):
print(f"{nb:6d}", end=" " * 6)
print()
print(limit)
for nb, (name, line) in enumerate(zip(player_names, elo_table), 1):
name_format = f"{nb:3d}" + " {:" + str(first_col_len) + "s}"
print(name_format.format(name), end=sep)
for elo, error in line:
print(f'{elo:>6}+-{error:<3}', end=" ")
print()
print(limit)
-808335_TC_240_4.pgn``` prints
```
-----------------------------------------------------------------