From a1486cb9b2fd27d9ccaa125896e2f7d54ff948ac Mon Sep 17 00:00:00 2001 From: "Duncan P. N. Exon Smith" Date: Mon, 11 Dec 2023 16:45:26 -0800 Subject: [PATCH] Add komi integration to ratings math Add komi integration to the ratings math. This path can be turned on using: - `--compute-handicap-via-komi-small` for 9x9 and 13x13 boards - `--compute-handicap-via-komi-19x19` for 19x19 boards For now, none of the scripts pass the necessary extra arguments (they assert out if you pass those arguments). Relates to #45 --- analysis/util/RatingMath.py | 75 +++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/analysis/util/RatingMath.py b/analysis/util/RatingMath.py index 7f2ac62..b1b510d 100644 --- a/analysis/util/RatingMath.py +++ b/analysis/util/RatingMath.py @@ -33,6 +33,8 @@ P = 1 HALF_STONE_HANDICAP = False HALF_STONE_HANDICAP_FOR_ALL_RANKS = False +COMPUTE_HANDICAP_VIA_KOMI_SMALL = False +COMPUTE_HANDICAP_VIA_KOMI_19X19 = False cli.add_argument( "--half-stone-handicap", dest="half_stone_handicap", const=1, default=False, action="store_const", help="Use a 0.5 rank adjustment for hc1", @@ -40,6 +42,16 @@ cli.add_argument( "--half-stone-handicap-for-all-ranks", dest="half_stone_handicap_for_all_ranks", const=1, default=False, action="store_const", help="use rankdiff -0.5 for handicap", ) +cli.add_argument( + "--compute-handicap-via-komi-small", + dest="compute_handicap_via_komi_small", const=1, default=False, action="store_const", + help="compute effective handicap from komi for small boards", +) +cli.add_argument( + "--compute-handicap-via-komi-19x19", + dest="compute_handicap_via_komi_19x19", const=1, default=False, action="store_const", + help="compute effective handicap from komi for 19x19 boards", +) logarithmic = cli.add_argument_group( "logarithmic ranking variables", "rating to ranks converted with `(log(rating / a) ** p) * c + d`", @@ -77,14 +89,67 @@ def set_exhaustive_log_parameters(a: float, c:float, d:float, p:float = 1.0) -> D = d P = p -def get_handicap_adjustment(rating: float, handicap: int) -> float: +def get_handicap_adjustment(rating: float, handicap: int, size: int | None = None, komi: float | None = None, rules: str | None = None) -> float: global HALF_STONE_HANDICAP global HALF_STONE_HANDICAP_FOR_ALL_RANKS + global COMPUTE_HANDICAP_VIA_KOMI_SMALL + global COMPUTE_HANDICAP_VIA_KOMI_19X19 + + if COMPUTE_HANDICAP_VIA_KOMI_19X19 or COMPUTE_HANDICAP_VIA_KOMI_SMALL: + assert size is not None + assert (size == 9 or + size == 13 or + size == 19) + assert komi is not None + assert rules is not None + assert (rules == "chinese" or + rules == "aga" or + rules == "japanese" or + rules == "korean" or + rules == "ing" or + rules == "nz") + if (COMPUTE_HANDICAP_VIA_KOMI_19X19 and size == 19) or (COMPUTE_HANDICAP_VIA_KOMI_SMALL and size != 19): + # Use a "even game komi" that allows for draws for computing ratings. + # It's okay to expect a draw. + stone_value = 12 + if rules == "japanese" or rules == "korean": + # Territory scoring. + area_bonus = 0 + komi_bonus = 0 + else: + # Bonus for the area value of a stone in area scoring. + area_bonus = 1 + + # Chinese and AGA rules add extra komi when there's a handicap but + # don't store it in the 'komi' field. + if rules == "chinese": + komi_bonus = 1 * handicap + elif rules == "aga": + komi_bonus = 1 * (handicap - 1) if handicap else 0 + else: + komi_bonus = 0 + + # Convert the handicap into an equivalent komi, and subtract it from + # the komi for an even game. + handicap_as_komi = ((stone_value / 2 + area_bonus) - (komi + komi_bonus) + + ((stone_value + area_bonus) * handicap if handicap > 1 else 0)) + + # Convert back to a fractional handicap, using scaling factors of 3x + # and 6x for 9x9 and 13x13. + if size == 9: + adjusted_handicap = handicap_as_komi * 6 / stone_value + elif size == 13: + adjusted_handicap = handicap_as_komi * 3 / stone_value + else: + adjusted_handicap = handicap_as_komi / stone_value + + return rank_to_rating(rating_to_rank(rating) + adjusted_handicap) - rating + if HALF_STONE_HANDICAP_FOR_ALL_RANKS: return rank_to_rating(rating_to_rank(rating) + (handicap - 0.5 if handicap > 0 else 0)) - rating if HALF_STONE_HANDICAP: return rank_to_rating(rating_to_rank(rating) + (0.5 if handicap == 1 else handicap)) - rating - return rank_to_rating(rating_to_rank(rating) + handicap) - rating + return rank_to_rating(rating_to_rank(rating)) - rating def set_optimizer_rating_points(points: List[float]) -> None: global optimizer_rating_control_points @@ -99,9 +164,13 @@ def configure_rating_to_rank(args: argparse.Namespace) -> None: global D global HALF_STONE_HANDICAP global HALF_STONE_HANDICAP_FOR_ALL_RANKS + global COMPUTE_HANDICAP_VIA_KOMI_SMALL + global COMPUTE_HANDICAP_VIA_KOMI_19X19 HALF_STONE_HANDICAP = args.half_stone_handicap HALF_STONE_HANDICAP_FOR_ALL_RANKS = args.half_stone_handicap_for_all_ranks + COMPUTE_HANDICAP_VIA_KOMI_SMALL = args.compute_handicap_via_komi_small + COMPUTE_HANDICAP_VIA_KOMI_19X19 = args.compute_handicap_via_komi_19x19 system: str = args.ranks a: float = args.a c: float = args.c @@ -260,7 +329,7 @@ def __rating_to_rank(rating: float) -> float: else: raise NotImplementedError - assert round(get_handicap_adjustment(1000.0, 0), 8) == 0 + assert round(get_handicap_adjustment(1000.0, 0, size=19, rules="japanese",komi="0.5"), 8) == 0 def lerp(x:float, y:float, a:float):