summaryrefslogtreecommitdiff
path: root/src/eval_tune/compare_ecm_nodes.py
blob: 73408ef53a9403f87cf96060a20f77f0f7456d9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/usr/bin/env python3
"""Pair up per-problem node counts from two ECM sd=N stdout logs (same
input file, same order) and report a median/geomean ratio plus win/loss
counts -- robust to the handful of huge forced-mate outliers that would
otherwise dominate a raw sum. Mate-hunt problems (last iterated depth
line containing MATE) are reported separately since their node count is
chaotic w.r.t. move-ordering and not representative of typical
branching-factor efficiency.
"""
import re
import statistics
import sys

SEARCHED_RE = re.compile(r"Searched for\s+[\d.]+ seconds, saw (\d+) nodes")
DEPTH_LINE_RE = re.compile(r"^\s*\d+[+]?\s+(\S+)\s+[\d:.]+\s+(\d+)\s")


def parse(path):
    """Return list of (node_count, is_mate) per completed problem, in order."""
    results = []
    last_score = None
    with open(path) as f:
        for line in f:
            m = DEPTH_LINE_RE.match(line)
            if m:
                last_score = m.group(1)
                continue
            m = SEARCHED_RE.search(line)
            if m:
                is_mate = bool(last_score and "MATE" in last_score)
                results.append((int(m.group(1)), is_mate))
                last_score = None
    return results


def main():
    base_path, cand_path = sys.argv[1], sys.argv[2]
    base = parse(base_path)
    cand = parse(cand_path)
    n = min(len(base), len(cand))
    base, cand = base[:n], cand[:n]

    normal_ratios = []
    mate_ratios = []
    wins = losses = ties = 0
    mate_wins = mate_losses = mate_ties = 0

    for (bn, bmate), (cn, cmate) in zip(base, cand):
        ratio = cn / bn if bn else 1.0
        is_mate = bmate or cmate
        bucket = mate_ratios if is_mate else normal_ratios
        bucket.append(ratio)
        if is_mate:
            if cn < bn: mate_wins += 1
            elif cn > bn: mate_losses += 1
            else: mate_ties += 1
        else:
            if cn < bn: wins += 1
            elif cn > bn: losses += 1
            else: ties += 1

    print(f"Paired problems: {n}")
    print()
    print(f"=== Non-mate problems (n={len(normal_ratios)}) ===")
    if normal_ratios:
        print(f"  median candidate/baseline node ratio: {statistics.median(normal_ratios):.4f}")
        print(f"  geomean ratio: {statistics.geometric_mean(normal_ratios):.4f}")
        print(f"  wins (candidate fewer nodes): {wins}")
        print(f"  losses (candidate more nodes): {losses}")
        print(f"  ties: {ties}")
    print()
    print(f"=== Mate-hunt problems (n={len(mate_ratios)}) -- reported separately, chaotic ===")
    if mate_ratios:
        print(f"  median ratio: {statistics.median(mate_ratios):.4f}")
        print(f"  wins: {mate_wins}  losses: {mate_losses}  ties: {mate_ties}")


if __name__ == "__main__":
    main()