independent.py

back to table · edit · history · where entries came from · files · download

3699 bytes, as of the version from 2026-10-27 20:42 (current). Recorded here, not run.

"""Independent exact checks for T560; Python rational arithmetic, no generator data.

CMSZ section 3.1: B(u)=(u/2)/sinh(u/2), F(u)=sum (j-1)! B_j u^j,
A(t)=1/Q^{-1}(t), Q(u)=u exp(F(u)). Lagrange inversion gives
[t^n](t A(t)) = [u^n]exp((1-n)F(u))/(1-n), n >= 2.

For the signed spin series use G(u)=-sum B_(2j)u^(2j)/(2j*2^j)
and Q_spin(u)=u exp(-G(u)); see CMSZ section 6.3. The conversion is
2*(-1)^g*2^(2g)/((2g-1)*(2g-1)!) in both cases. This includes the
factor 2 from the EMZ volume convention, also documented in the independent
surface-dynamics implementation. Hyperelliptic spin is floor((g+1)/2) mod 2.

The code uses coefficient recurrences and Lagrange inversion, not Sage's
series inverse or surface-dynamics' executable implementation.
"""
from fractions import Fraction as F
from math import factorial


def inverse_series(a):
    b = [1 / a[0]]
    for n in range(1, len(a)):
        b.append(-sum(a[k] * b[n-k] for k in range(1, n+1)) / a[0])
    return b


def exp_series(a):
    assert a[0] == 0
    b = [F(1)]
    for n in range(1, len(a)):
        b.append(sum(k * a[k] * b[n-k] for k in range(1, n+1)) / n)
    return b


def controls():
    assert exp_series([F(0), F(1)] + [F(0)]*7) == [F(1, factorial(n)) for n in range(9)]
    assert inverse_series([F(1), F(-1)] + [F(0)]*7) == [F(1)]*9
    assert minimal_total(1) == F(1, 3)
    assert spin_difference(1) == F(-1, 3)
    assert hyperelliptic(2, 1) == F(1, 120)
    assert hyperelliptic(2, 2) == F(1, 135)


def minimal_total(g):
    n = 2*g
    sinh_over = [F(0)]*(n+1)
    for j in range(g+1):
        sinh_over[2*j] = F(1, 2**(2*j)*factorial(2*j+1))
    B = inverse_series(sinh_over)
    log_q = [F(0)] + [factorial(j-1)*B[j] for j in range(1,n+1)]
    tA_n = exp_series([(1-n)*x for x in log_q])[n] / (1-n)
    return F(2 * (-1)**g * 2**n, (n-1)*factorial(n-1)) * tA_n


def spin_difference(g):
    n = 2*g
    # t/(exp(t)-1): coefficient B_j/j!.
    b = inverse_series([F(1, factorial(j+1)) for j in range(n+1)])
    log_p = [F(0)]*(n+1)
    for j in range(1,g+1):
        log_p[2*j] = -b[2*j]*factorial(2*j) / (2*j*2**j)
    a_n = exp_series([(n-1)*x for x in log_p])[n] / (1-n)
    return F(2 * (-1)**g * 2**n, (n-1)*factorial(n-1)) * a_n


def double_factorial(n):
    product = 1
    for j in range(n,0,-2):
        product *= j
    return product


def hyperelliptic(g, zeros):
    if zeros == 1:
        return F(2*double_factorial(2*g-3), factorial(2*g+1)*double_factorial(2*g-2))
    if zeros == 2:
        return F(8*double_factorial(2*g-2), factorial(2*g+2)*double_factorial(2*g-1))
    raise ValueError('Hyperelliptic components have one or two zeros')


def partitions(n, maximum=None):
    if n == 0:
        yield ()
    else:
        for first in range(min(n, n if maximum is None else maximum),0,-1):
            for rest in partitions(n-first, first):
                yield (first,) + rest


def components(g, mu):
    if g == 2:
        return ('hyp',)
    if g == 3:
        return ('hyp','odd') if mu in ((4,),(2,2)) else ('connected',)
    if mu == (2*g-2,):
        return ('hyp','even','odd')
    if mu == (g-1,g-1):
        return ('hyp','nonhyp') if g % 2 == 0 else ('hyp','even','odd')
    if all(m % 2 == 0 for m in mu):
        return ('even','odd')
    return ('connected',)


def selection():
    return [(g, mu, c) for g in range(2,5)
            for mu in partitions(2*g-2) for c in components(g,mu)]


if __name__ == '__main__':
    controls()
    for g in range(1,5):
        print(g, 'total/pi^(2g)', minimal_total(g), 'spin difference/pi^(2g)', spin_difference(g))
    print('Profiles', [len(list(partitions(2*g-2))) for g in range(2,5)])
    print('Components', len(selection()))