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
```
-----------------------------------------------------------------