generate.py

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

6019 bytes, as of the version from 2026-10-30 13:35 (current). Recorded here, not run.

"""Resistance of infinite triangular and honeycomb lattices -- numberdb.org/T626.

Run with SageMath (install numberdb with sage -pip install numberdb):
    sage -python generate.py             # verify every stored entry
    sage -python generate.py --fill      # write entries to this draft, key required
    sage -python checks.py               # independent quadrature and graph checks

The exact coefficient polynomial a+b*z represents a+b*sqrt(3)/pi. Axis seeds
come from integrating (1-T_{2m}(u))/(1-u^2) against 1/sqrt(4-u^2), using exact
moment recurrence 2k M_k=4(2k-1)M_{k-1}-sqrt(3), M_0=pi/6. Harmonicity then
fills the triangular wedge; honeycomb values use Cserti (2000), (77)-(78).
No conjectured recurrence in Mathar (2022) is used. See derivation.md.

The default selection is all noncoincident endpoint orbits through graph
distance 12, justified in argument-selection.md. Arithmetic is exact until
one final Arb evaluation. The 128-bit guard covers measured cancellation;
checks.py records the minimum retained accuracy and verifies serialization.
The --fill action submits numbers and attachments; it never offers or publishes
the draft. NUMBERDB_KEY_FROM_STDIN=1 supports the campaign's protected key pipe.
"""
import os
import sys
from functools import lru_cache
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.real_arb import RealBallField

P = PolynomialRing(QQ, 'z')
z = P.gen()
U = PolynomialRing(QQ, 'u')
u = U.gen()
WORKING_GUARD = 128
RADIUS = 12
STEPS = ((1,0),(-1,0),(0,1),(0,-1),(1,1),(-1,-1))


def distance(m, n):
    return max(abs(m), abs(n), abs(m-n))


def triangular_orbit(m, n):
    orbit = set()
    for j in range(6):
        orbit.add((m,n))
        orbit.add((n,m))
        m,n = m-n,m
    return orbit


def canonical(m, n):
    return next((a,b) for a,b in triangular_orbit(m,n) if a >= 2*b >= 0)


def b_orbit(m,n):
    orbit = set()
    for j in range(3):
        orbit.add((m,n))
        orbit.add((m,m-n))
        m,n = -n-1,m-n
    return orbit


def honey_distance(s,m,n):
    return 2*distance(m,n) if s == 'A' else 1+2*min(distance(m,n),distance(m+1,n),distance(m+1,n+1))


@lru_cache(None)
def chebyshev(k):
    if k == 0: return U(1)
    if k == 1: return u
    return 2*u*chebyshev(k-1)-chebyshev(k-2)


@lru_cache(None)
def moment_over_pi(k):
    if k == 0: return P(QQ(1)/6)
    return (4*(2*k-1)*moment_over_pi(k-1)-z)/QQ(2*k)


@lru_cache(None)
def axis(m):
    poly, remainder = (1-chebyshev(2*m)).quo_rem(1-u*u)
    assert remainder == 0
    assert all(poly[k] == 0 for k in range(1,poly.degree()+1,2))
    return sum((poly[2*k]*moment_over_pi(k) for k in range(m)), P(0))


@lru_cache(None)
def triangular(m,n):
    m,n = canonical(int(m),int(n))
    if n == 0: return axis(m)
    if n == 1:
        return 3*triangular(m-1,0)-triangular(m-1,1)-(triangular(m,0)+triangular(m-2,0))/QQ(2)
    return (6*triangular(m-1,n-1)-triangular(m-1,n)-triangular(m,n-1)
            -triangular(m-2,n-1)-triangular(m-1,n-2)-triangular(m-2,n-2))


def coefficients(params):
    m,n = int(params['m']),int(params['n'])
    if params['lattice'] == 'triangular':
        assert params['sublattice'] == 'A'
        return triangular(m,n)
    assert params['lattice'] == 'honeycomb'
    if params['sublattice'] == 'A': return 3*triangular(m,n)
    assert params['sublattice'] == 'B'
    return triangular(m,n)+triangular(m+1,n)+triangular(m+1,n+1)


class Resistance(numberdb.Generator):
    table = 'T626'
    parameters = ('lattice','sublattice','m','n')
    type = 'R'
    digits = 100
    rigour = 'proven'
    files = ('generate.py','checks.py','argument-selection.md','derivation.md',
             'numerical-evidence.json','skill-manifest.json','stored_checks.py')

    def enumerate(self, radius=RADIUS):
        rows = []
        for lattice,bound in [('triangular',radius),('honeycomb',radius//2)]:
            for m in range(1,bound+1):
                for n in range(m//2+1):
                    rows.append((m if lattice == 'triangular' else 2*m,lattice,'A',m,n))
        for m in range(-radius,radius+1):
            for n in range(-radius,radius+1):
                d = honey_distance('B',m,n)
                if d <= radius and (m,n) == max(b_orbit(m,n)):
                    rows.append((d,'honeycomb','B',m,n))
        rows.sort(key=lambda row:(row[0], row[1] != 'triangular', row[2],row[3],row[4]))
        for d,lattice,s,m,n in rows:
            yield dict(lattice=lattice,sublattice=s,m=str(m),n=str(n))

    def value(self,params,digits):
        poly = coefficients(params)
        assert poly.degree() <= 1
        if poly[1] == 0:
            value = poly[0]
        else:
            ball = RealBallField(numberdb.bits(digits,losing=WORKING_GUARD))
            value = ball(poly[0])+ball(poly[1])*ball(3).sqrt()/ball.pi()
            assert value.is_finite() and value > 0
        special = {
            ('triangular','A',1,0): r'Adjacent vertices: $t(1,0)=1/3$.',
            ('honeycomb','B',0,0): r'Adjacent vertices: $h_B(0,0)=2/3$.',
            ('honeycomb','A',1,0): r'Second-neighbor vertices: $h_A(1,0)=1$.'}
        key=(params['lattice'],params['sublattice'],int(params['m']),int(params['n']))
        return {'number':value, 'comment':special[key]} if key in special else value


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        key = sys.stdin.read().strip()
        if '=' in key and key.split('=',1)[0].replace('_','').isalnum():
            key = key.split('=',1)[1].strip().strip('\"\'')
        numberdb.configure(api_key=key)
    generator=Resistance()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Fill all 84 graph-distance orbits with exact recurrence and certified bounds'))
    else:
        report=generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)