#!/usr/bin/env python3
"""Simulate a finite-exponent, finite-precision evaluator (like Magma/MPFR) on the n=12 trees, using the
certified class logs from tables/. Goal: see how a count like 2919 can arise and which values merge."""
import sys, math
import mpmath as mp
mp.mp.dps = 60
N = 12
cls, clsL = {}, {}
for n in range(1, N + 1):
    for line in open(f"tables/classes_n{n:02d}.tsv"):
        if line.startswith("#"): continue
        cid, re_s, im_s, _ = line.rstrip("\n").split("\t")
        if re_s.startswith("Re Log v"):
            clsL[(n, int(cid))] = None; continue
        mid = lambda s: mp.mpf(0) if s.startswith("[+/-") else mp.mpf(s.strip("[").split(" +/-")[0].rstrip("]"))
        clsL[(n, int(cid))] = mp.mpc(mid(re_s), mid(im_s))
    for line in open(f"tables/trees_n{n:02d}.tsv"):
        if line.startswith("#"): continue
        idx, cid, e = line.rstrip("\n").split("\t")
        cls[e] = (n, int(cid))

def split(s):
    if s == "i": return None
    if s == "i^i": return "i", "i"
    d = 0
    for j, ch in enumerate(s):
        d += ch == "("; d -= ch == ")"
        if d == 0: return s[1:j], s[j + 3:-1]

def run(EMAX_BITS, DIG):
    lim = EMAX_BITS * math.log(2)
    memo = {}
    def status(e):              # 'ok', 'inf', 'zero', 'nan'
        if e in memo: return memo[e]
        L = clsL[cls[e]]
        sp = split(e)
        if sp is None: r = "ok"
        else:
            sa, sb = status(sp[0]), status(sp[1])
            if sa == "nan" or sb == "nan" or sb == "inf" or sa == "inf": r = "nan"
            elif sa == "zero": r = "nan" if sb == "ok" else "nan"
            elif sb == "zero": r = "ok"
            elif L is None or mp.re(L) > lim: r = "inf" if L is not None else "nan"
            elif mp.re(L) < -lim: r = "zero"
            else: r = "ok"
        memo[e] = r
        return r
    bad, zero, fin = set(), set(), {}
    nbad = 0
    for e, (n, c) in cls.items():
        if n != N: continue
        s = status(e)
        if s in ("inf", "nan"): bad.add(c); nbad += 1
        elif s == "zero": zero.add(c)
        else: fin[c] = clsL[(n, c)]
    # finite values: merge if equal to DIG significant digits (relative distance of v ~ |dL| mod 2 pi i)
    vals = sorted(fin.items(), key=lambda kv: (mp.re(kv[1]), mp.im(kv[1])))
    tol = mp.mpf(10) ** (-DIG)
    groups = []
    for c, L in vals:
        for g in groups[-30:]:
            d = abs(L - g[0][1]); d = min(d, abs(d - 2j * mp.pi))
            if d < tol: g.append((c, L)); break
        else: groups.append([(c, L)])
    merged = [g for g in groups if len(g) > 1]
    return nbad, bad, zero, len(groups), merged

for eb in (2**30, 2**31, 2**32, 2**62):
    for dig in (15, 20, 30, 50):
        nbad, bad, zero, ng, merged = run(eb, dig)
        print(f"emax=2^{int(math.log2(eb))} digits={dig}: Inf/NaN trees={nbad} ({len(bad)} true classes), "
              f"zero classes={len(zero)}, finite distinct={ng}, merged groups={len(merged)} -> "
              f"finite+zero(1 if any)={ng + (1 if zero else 0)}")

print("\n--- details at emax=2^30 (MPFR default) ---")
for dig in (30, 50):
    nbad, bad, zero, ng, merged = run(2**30, dig)
    print(f"digits={dig}:")
    for c in sorted(bad):
        L = clsL[(N, c)]
        print(f"   Inf/NaN class {c}: Re Log v = {mp.nstr(mp.re(L), 6) if L is not None else 'level-2 (-3.2e(4.1e34))'}")
    for c in zero: print(f"   zero class {c}: Re Log v = {mp.nstr(mp.re(clsL[(N, c)]), 6)}")
    for g in merged:
        print("   merged by rounding:", [(c, mp.nstr(L, 5)) for c, L in g])
