checks.py

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

11073 bytes, as of the version from 2026-11-01 21:37. Recorded here, not run.

"""Independent checks for character-degree power sums -- numberdb.org/T661.

Run: sage -python checks.py > check-results.json
For stored values: NUMBERDB_API_KEY=... sage -python checks.py --stored
The API key can instead arrive on stdin with NUMBERDB_KEY_FROM_STDIN=1.

No acceptance sidecar is produced: the campaign requires a separate critique.
This checker independently constructs colored Young diagrams by adding one
box and counts paths, rather than evaluating hook products. GAP character
tables check small cyclic bases and a noncyclic abelian base. Fourth roots
are enclosed by integer square roots at a scale of 10^140, independently of
Arb; this gives exact rational intervals for the complete positive sum.
"""
import numberdb.sage as numberdb
from numberdb._write import Entries
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.version import version as sage_version
from collections import Counter, defaultdict
from decimal import Decimal
from fractions import Fraction
from math import factorial, isqrt
from pathlib import Path
import ast
import hashlib
import importlib.metadata
import json
import os
import subprocess
import sys
import time

from generate import CharacterDegreePowerSum, selected_pairs, degree_histogram
from check import exactness, measure, agrees_with, names_its_rings

SKILL_SHA256 = 'a2b28d9953264d7b0ca62663785c2952190c5be6d6d3597fd695d9a54d606278'
SKILL_MANIFEST = 'agents/runs/20261101T212519Z-build-skills/manifest.json'


def children(partition):
    for row in range(len(partition)):
        if row == 0 or partition[row-1] > partition[row]:
            out = list(partition)
            out[row] += 1
            yield tuple(out)
    yield partition + (1,)


def branching_histograms(q, maximum):
    level = {((),)*q: 1}
    result = {0: {1: 1}}
    for n in range(1, maximum+1):
        nxt = defaultdict(int)
        for multipartition, paths in level.items():
            for color in range(q):
                for child in children(multipartition[color]):
                    changed = list(multipartition)
                    changed[color] = child
                    nxt[tuple(changed)] += paths
        level = nxt
        result[n] = dict(Counter(level.values()))
    return result


def colored_partition_count(q, n):
    coeff = [1] + [0]*n
    for part in range(1, n+1):
        for color in range(q):
            for size in range(part, n+1):
                coeff[size] += coeff[size-part]
    return coeff[n]


def root_bounds(d, scale=140):
    t = 10**scale
    f = isqrt(isqrt(int(d)*t**4))
    assert f**4 <= d*t**4 < (f+1)**4
    lo = Fraction(f,t)
    hi = lo if f**4 == d*t**4 else Fraction(f+1,t)
    return lo, hi


def independent_moment(spectrum, exponent):
    if exponent == '3':
        val = sum(count*d**3 for d,count in spectrum.items())
        return Fraction(val),Fraction(val)
    assert exponent == '9/4'
    low = high = Fraction(0)
    for d, count in spectrum.items():
        lo, hi = root_bounds(d)
        low += count*d*d*lo
        high += count*d*d*hi
    return low,high


def written_bounds(text):
    text = str(text).strip()
    if '+/-' in text:
        mid,rad = map(Fraction,text.split('+/-'))
        return mid-rad,mid+rad
    if text.startswith('['):
        lo,hi = text[1:-1].split(',')
        return Fraction(lo),Fraction(hi)
    if '.' not in text and 'e' not in text.lower():
        val = Fraction(text)
        return val,val
    dec = Decimal(text)
    mid = Fraction(dec)
    ulp = Fraction(10)**dec.as_tuple().exponent
    return mid-ulp,mid+ulp


def gap_checks():
    # Controls first: an abelian group, then S3 with degrees 1,1,2.
    specs = [('control-C2','CyclicGroup(2)',{1:2}),
             ('control-S3','SymmetricGroup(3)',{1:2,2:1})]
    pairs = [(1,n) for n in range(3,9)] + [(2,n) for n in range(2,5)] + [(3,n) for n in range(2,5)] + [(8,2)]
    for q,n in pairs:
        expr = 'SymmetricGroup(%d)'%n if q==1 else 'WreathProduct(CyclicGroup(%d),SymmetricGroup(%d))'%(q,n)
        specs.append(('%d,%d'%(q,n),expr,degree_histogram(q,n)))
    specs.append(('noncyclic-8,2','WreathProduct(AbelianGroup([2,2,2]),SymmetricGroup(2))',degree_histogram(8,2)))
    lines=['SizeScreen([100000,100000]);; Print("VERSION:",GAPInfo.Version,"\\n");']
    for label,expr,_ in specs:
        lines.append('g:=%s;; Print("%s:",Collected(List(Irr(g),DegreeOfCharacter)),"\\n");'%(expr,label))
    lines.append('QUIT;')
    run = subprocess.run(['gap','-q'],input='\n'.join(lines)+'\n',text=True,capture_output=True,timeout=180)
    assert run.returncode == 0 and not run.stderr.strip(), (run.returncode,run.stderr)
    observed={}
    for line in run.stdout.splitlines():
        key,raw=line.split(':',1)
        observed[key] = raw if key=='VERSION' else dict(ast.literal_eval(raw))
    for label,_,expected in specs:
        assert observed[label] == expected, label
    return {'version':observed.pop('VERSION'),'controls':2,'group_comparisons':len(specs)-2,'spectra':observed}


def companion_checks(client):
    doc=numberdb.table('T257',client=client)
    ring=PolynomialRing(ZZ,'t')
    assert ring('t^2+2*t+1')(1)==4
    checked=[]
    for q,n in selected_pairs():
        label='A%d'%(n-1) if q==1 else 'B%d'%n if q==2 else None
        if label and label in doc['Numbers']:
            text=doc['Numbers'][label]
            if isinstance(text,dict): text=text['number']
            assert ring(text)(1)==q**n*factorial(n),label
            checked.append(label)
    ps=numberdb.table('T119',client=client)
    ps_cases=[]
    for q,n,degrees in [(1,3,[1,1,2]),(1,4,[1,1,2,3,3]),(2,2,[1,1,1,1,2])]:
        k=len(degrees)
        r=PolynomialRing(ZZ,['x%d'%(i+1) for i in range(k)])
        text=ps['Numbers'][str(k)]['3']
        if isinstance(text,dict): text=text['number']
        assert r(text)(*degrees)==sum(d**3 for d in degrees)
        ps_cases.append([q,n])
    return {'coxeter_order_rows':checked,'power_sum_specializations':ps_cases}


def run_checks(stored=False):
    start=time.monotonic()
    assert root_bounds(16)==(Fraction(2),Fraction(2))
    a,b=root_bounds(2)
    assert a**4 < 2 < b**4 and b-a==Fraction(1,10**140)
    assert branching_histograms(1,3)[3]=={1:2,2:1}
    # These histograms share no partition enumeration or hook-product code.
    independent={}
    for q,maximum in [(1,20),(2,12),(3,10)]:
        for n,hist in branching_histograms(q,maximum).items():
            if n>=2: independent[q,n]=hist
    pairs=list(selected_pairs())
    assert len(pairs)==77 and len(set(pairs))==77
    checks={'branching_spectra':0,'sum_of_squares':0,'linear_characters':0,
            'colored_partition_counts':0,'two_factor_spectra':0}
    for q,n in pairs:
        spectrum=degree_histogram(q,n)
        if (q,n) in independent:
            assert spectrum==independent[q,n],(q,n)
            checks['branching_spectra']+=1
            assert sum(spectrum.values())==colored_partition_count(q,n)
            checks['colored_partition_counts']+=1
        else:
            # For n=2, an abelian normal subgroup has index 2, so degrees
            # are 1 or 2. Abelianization gives 2q linear characters, and
            # the regular representation dimension 2q^2 fixes the rest.
            assert n==2
            independent[q,n]={1:2*q,2:(2*q*q-2*q)//4}
            assert spectrum==independent[q,n]
        assert spectrum.get(1)==2*q
        checks['linear_characters']+=1
        assert sum(count*d*d for d,count in spectrum.items())==q**n*factorial(n)
        checks['sum_of_squares']+=1
        if n==2:
            assert spectrum=={1:2*q,2:q*(q-1)//2}
            checks['two_factor_spectra']+=1
    generator=CharacterDegreePowerSum()
    values={','.join(p[k] for k in generator.parameters):generator.value(p,100) for p in generator.enumerate()}
    assert not names_its_rings(str(Path(__file__).with_name('generate.py')))
    assert not exactness(values)
    exact_values={k:v for k,v in values.items() if k.endswith(',3')}
    def exact_other(key):
        q,n,s=key.split(',')
        value = independent_moment(independent[int(q),int(n)],s)[0]
        assert value.denominator == 1
        return ZZ(value.numerator)
    disagreements = agrees_with(exact_values,exact_other)
    assert not disagreements, disagreements[:3]
    entries=Entries(*generator.parameters)
    for p in generator.enumerate():
        key=','.join(p[k] for k in generator.parameters)
        entries.add(number=values[key],digits=100,**p)
    records=entries.as_list()
    client=None
    if stored:
        client=numberdb.Client(as_sage=True)
        document=numberdb.table('T661',client=client)
        records=document['Numbers']
        assert isinstance(records,list)
        expected=[p for p in generator.enumerate()]
        assert [r['params'] for r in records]==expected
    accuracy=[]
    for record in records:
        p=record['params']; key=','.join(p[k] for k in generator.parameters)
        low,high=independent_moment(independent[int(p['q']),int(p['n'])],p['s'])
        left,right=written_bounds(record['number'])
        assert left<=low<=high<=right,(key,'stored enclosure does not contain independently isolated interval')
        if p['s']=='3':
            assert left==right==low and '.' not in record['number']
        else:
            assert high-low<Fraction(1,10**130)*low
            assert right-left<Fraction(3,10**99)*low
            assert values[key].is_finite() and values[key].accuracy()>350
            accuracy.append(int(values[key].accuracy()))
    # All displayed two-factor closed forms are also verified on these values.
    for r in records:
        p=r['params']; q,n=int(p['q']),int(p['n'])
        if n==2 and p['s']=='3': assert int(r['number'])==4*q*q-2*q
    gap=gap_checks()
    companions=companion_checks(client)
    evidence={'table':'T661','run':'20261101T212519Z','stored_values_checked':stored,
      'skill_manifest':SKILL_MANIFEST,'local_skill_sha256':SKILL_SHA256,'live_skill_sha256':SKILL_SHA256,
      'counts':{'group_pairs':77,'values':len(records),'exact_integer_values':77,'certified_real_values':77},
      'structural_checks':checks,'independent_enclosures':len(records),
      'minimum_arb_accuracy_bits':min(accuracy),'working_guard_bits':64,
      'size':measure({','.join(r['params'][k] for k in generator.parameters):r['number'] for r in records}),
      'gap':gap,'companions':companions,
      'versions':{'python':sys.version,'sage':sage_version,'numberdb':importlib.metadata.version('numberdb')},
      'elapsed_seconds':round(time.monotonic()-start,3)}
    return {'evidence':evidence,'records':records,
      'degree_histograms':{str(q)+','+str(n):{str(k):v for k,v in degree_histogram(q,n).items()} for q,n in pairs}}


if __name__=='__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
        os.environ['NUMBERDB_API_KEY']=sys.stdin.read().strip()
    print(json.dumps(run_checks('--stored' in sys.argv),indent=2))