generate.py

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

3914 bytes, as of the version from 2026-10-29 19:59 (current). Recorded here, not run.

"""Volumes of right-angled hyperbolic antiprisms and Löbell polyhedra -- numberdb.org/T611.

Run with SageMath or passagemath and the numberdb client installed:
    sage -pip install numberdb
    sage -python generate.py             # verify all stored entries
    sage -python generate.py --fill      # send entries to the existing draft
Use NUMBERDB_API_KEY for authenticated access to the private draft.

A_n is ideal, n >= 3; L_n is compact, n >= 5. Curvature is -1 and
n counts base sides. Default selection: every A_3,...,A_30 and L_5,...,L_30.
See argument-selection.md for both parameter justifications.

The antiprism formula is Thurston's, as quoted by Repovs--Vesnin (2024),
equation (1); the Lobell formula is Inoue (2008), Theorem 4.1.
The equivalent positive argument pi/2-2*theta uses oddness of Lambda.
Lambda(x) = Im(Li_2(exp(2*i*x)))/2, evaluated entirely with Arb balls.
All arguments here are strictly between 0 and pi/2; the principal Li_2
branch is therefore unambiguous. No decimal approximation defines an angle.

80 extra bits allow cancellation and propagation through asin and Li_2.
The checks measure the worst retained accuracy and compare independent
integrals. Generator outputs are enclosures; the client writes 100 significant
decimal digits with its one-unit-last-place interval convention.
"""
import os
import sys
import numberdb.sage as numberdb
from sage.rings.real_arb import RealBallField
from sage.rings.complex_arb import ComplexBallField

WORKING_GUARD = 80


def lobachevsky(angle, complex_field):
    z = (complex_field(0, 2) * complex_field(angle)).exp()
    result = z.polylog(2).imag() / 2
    assert result.is_finite()
    return result


def volume(family, n, digits=100):
    n = int(n)
    bits = numberdb.bits(digits, losing=WORKING_GUARD)
    real = RealBallField(bits)
    comp = ComplexBallField(bits)
    pi = real.pi()
    if family == 'antiprism' and n >= 3:
        args = [pi / 4 + pi / (2*n), pi / 4 - pi / (2*n)]
        assert all(0 < a < pi / 2 for a in args)
        answer = 2*n*sum(lobachevsky(a, comp) for a in args)
    elif family == 'lobell' and n >= 5:
        theta = (1 / (2 * (pi / n).cos())).arcsin()
        args = [theta, theta + pi/n, theta - pi/n, pi/2 - 2*theta]
        assert all(0 < a < pi / 2 for a in args)
        answer = real(n)/2 * (2*lobachevsky(args[0], comp)
                              + sum(lobachevsky(a, comp) for a in args[1:]))
    else:
        raise ValueError('Use antiprism with n >= 3 or lobell with n >= 5')
    assert answer.is_finite() and answer > 0
    assert real(answer.rad()) < abs(answer) * real(10)**(-digits-10)
    return answer


class RightAngledVolumes(numberdb.Generator):
    table = 'T611'
    parameters = ('family', 'n')
    type = 'R'
    digits = 100
    rigour = 'proven'
    files = ('generate.py', 'checks.py', 'argument-selection.md')

    def enumerate(self, maximum=30):
        for family, minimum in [('antiprism', 3), ('lobell', 5)]:
            for n in range(minimum, maximum+1):
                yield {'family': family, 'n': n}

    def value(self, params, digits):
        family, n = str(params['family']), int(params['n'])
        result = {'number': volume(family, n, digits)}
        if family == 'antiprism' and n == 3:
            result['comment'] = 'The regular ideal right-angled octahedron.'
        elif family == 'lobell' and n == 5:
            result['comment'] = 'The compact regular right-angled dodecahedron.'
        return result


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        os.environ['NUMBERDB_API_KEY'] = sys.stdin.read().strip()
    generator = RightAngledVolumes()
    if '--fill' in sys.argv:
        print(generator.publish(message='Fill 54 volumes from certified Arb balls'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)