back to table · edit · history · where entries came from · files · download
9661 bytes, as of the version from 2026-09-27 17:11 (current). Recorded here, not run.
"""Reproduce T415 without changing the original Falkner-Skan computation.
Install numberdb, python-flint and mpmath; keep falkner_skan_core.py beside
this file. The dependency is an unchanged, SHA-256-checked source attachment.
sage -pip install numberdb python-flint mpmath
sage -python generate.py --verify
sage -python generate.py --offline entries.json --profiles results \
--checks numerical-checks.json
sage -python generate.py --offline entries.json --recompute
The last command is an expensive full recomputation. The cached command
validates the original manifest and checks displacement convergence itself;
the old core's check_pair deliberately is not used as displacement evidence.
No command in this adapter publishes. Verification reads the website only.
"""
import argparse
import hashlib
import importlib.util
import json
from decimal import Decimal, localcontext
from fractions import Fraction
from functools import lru_cache
from pathlib import Path
import numberdb
CORE_SHA256 = 'a3c603ef1d0e13e8303a514fe53a4ffb63a11066e314ce593766117a7d179753'
CHECKS_SHA256 = '93bedf2d85e91790587887ad6c110f4814f07296569d7dbdc8517bda64199169'
BETAS = ('-1/6', '-1/8', '0', '1/4', '1/3', '1/2', '2/3', '3/4', '4/5', '1')
SETTINGS = ((160, 140, '1/8', '32'), (210, 180, '1/10', '36'))
THRESHOLD = Decimal('1e-110')
def sha256(content):
return hashlib.sha256(content).hexdigest()
def core_path():
attached = Path(__file__).with_name('falkner_skan_core.py')
if attached.exists():
return attached
return Path(__file__).resolve().parent.parent / 'precision-repair/falkner_skan_generator.py'
def check_core(path):
if sha256(Path(path).read_bytes()) != CORE_SHA256:
raise ValueError('The immutable Falkner-Skan core has changed')
@lru_cache(maxsize=1)
def load_core():
path = core_path()
check_core(path)
specification = importlib.util.spec_from_file_location('falkner_skan_core', path)
module = importlib.util.module_from_spec(specification)
specification.loader.exec_module(module)
return module
def decimal_rational(value):
fraction = Fraction(value)
return Decimal(fraction.numerator) / Decimal(fraction.denominator)
def finite(value, positive=False):
value = Decimal(value)
if not value.is_finite() or (positive and value <= 0):
raise ArithmeticError('Nonfinite or nonpositive numerical quantity')
return value
def displacement(result, normalisation):
if normalisation not in ('hartree', 'wedge'):
raise ValueError('Unknown normalisation')
with localcontext() as context:
context.prec = 240
value = finite(result['displacement_hartree'], positive=True)
if normalisation == 'wedge':
beta = decimal_rational(result['beta'])
if beta >= 2:
raise ValueError('The wedge coordinate is singular at beta=2')
value *= (2 - beta).sqrt()
return value
def check_displacement_pair(first, second):
beta = first['beta']
if beta not in BETAS or second['beta'] != beta:
raise ValueError('Mismatched or unsupported pressure gradient')
with localcontext() as context:
context.prec = 240
controls = []
for result, setting in zip((first, second), SETTINGS):
if result['settings'] != list(setting):
raise ValueError('Unexpected integration settings')
parameter = decimal_rational(beta)
thickness = displacement(result, 'hartree')
momentum = finite(result['momentum_hartree'], positive=True)
shear = finite(result['shear_hartree'], positive=True)
residual = shear - parameter * thickness - (1 + parameter) * momentum
if abs(residual) >= THRESHOLD:
raise ArithmeticError('Recomputed momentum identity failed')
for field in ('endpoint_velocity_residual', 'endpoint_acceleration',
'momentum_identity_residual'):
if abs(finite(result[field])) >= THRESHOLD:
raise ArithmeticError('Failed profile control: ' + field)
if finite(result['minimum_sampled_acceleration']) < -THRESHOLD:
raise ArithmeticError('Not the monotone upper branch')
controls.append(str(residual))
differences = {}
for normalisation in ('hartree', 'wedge'):
relative = abs(displacement(first, normalisation) /
displacement(second, normalisation) - 1)
if relative >= THRESHOLD:
raise ArithmeticError('Insufficient displacement convergence')
differences[normalisation] = str(relative)
return {'relative_differences': differences,
'recomputed_momentum_identity_residuals': controls}
def load_checked_profiles(directory, checks_path, source=None):
check_core(source or core_path())
directory = Path(directory)
checks_bytes = Path(checks_path).read_bytes()
if sha256(checks_bytes) != CHECKS_SHA256:
raise ValueError('The original numerical-check manifest has changed')
checks = json.loads(checks_bytes)
if checks['generator_sha256'] != CORE_SHA256:
raise ValueError('The original manifest names another computation')
if (directory / 'source-sha256.txt').read_text().strip() != CORE_SHA256:
raise ValueError('Cached source hash differs')
required = {beta.replace('/', '_') + '-' + str(index) + '.json'
for beta in BETAS for index in range(2)}
required |= {'T417-entries.json', 'T418-entries.json', 'T419-entries.json', 'diagnostics.json'}
if set(checks['artifact_hashes']) != required:
raise ValueError('Incomplete original artifact manifest')
snapshots = {}
for name, expected in checks['artifact_hashes'].items():
content = (directory / name).read_bytes()
if sha256(content) != expected:
raise ValueError('Hash-bound artifact changed: ' + name)
snapshots[name] = json.loads(content)
profiles, convergence = {}, {}
for beta in BETAS:
pair = [snapshots[beta.replace('/', '_') + '-' + str(index) + '.json']
for index in range(2)]
convergence[beta] = check_displacement_pair(*pair)
profiles[beta] = pair[1]
expected_params = list(Displacement().enumerate())
for table in ('T418', 'T419'):
if [row['params'] for row in snapshots[table + '-entries.json']] != expected_params:
raise ValueError('Parameter selection differs from ' + table)
return profiles, convergence, checks['artifact_hashes']
@lru_cache(maxsize=10)
def profile(beta):
if beta not in BETAS:
raise ValueError('Unsupported pressure gradient')
core = load_core()
pair = [core.solve(beta, setting) for setting in SETTINGS]
check_displacement_pair(*pair)
return pair[1]
class Displacement(numberdb.Generator):
table = 'T415'
parameters = ('beta', 'branch', 'normalisation')
type = 'R'
digits = 100
rigour = 'heuristic (agreement-checked)'
def enumerate(self):
for beta in BETAS:
for normalisation in ('hartree', 'wedge'):
yield dict(beta=beta, branch='upper', normalisation=normalisation)
def validate_params(self, params, digits):
if set(params) != set(self.parameters):
raise ValueError('Expected beta, branch and normalisation')
if params['branch'] != 'upper' or params['beta'] not in BETAS:
raise ValueError('Unsupported branch or pressure gradient')
if params['normalisation'] not in ('hartree', 'wedge'):
raise ValueError('Unknown normalisation')
if not isinstance(digits, int) or not 1 <= digits <= 100:
raise ValueError('Only 1 through 100 significant digits are supported')
def from_profile(self, params, result, digits=100):
self.validate_params(params, digits)
if result['beta'] != params['beta']:
raise ValueError('Profile parameter mismatch')
return format(displacement(result, params['normalisation']), '.' + str(digits - 1) + 'e')
def value(self, params, digits):
self.validate_params(params, digits)
return self.from_profile(params, profile(params['beta']), digits)
def main():
parser = argparse.ArgumentParser(description=__doc__)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument('--verify', action='store_true')
mode.add_argument('--offline', type=Path)
parser.add_argument('--profiles', type=Path)
parser.add_argument('--checks', type=Path)
parser.add_argument('--recompute', action='store_true')
options = parser.parse_args()
generator = Displacement()
if options.verify:
report = generator.verify(sample=None)
print(report)
raise SystemExit(0 if report.ok else 1)
if options.recompute:
if options.profiles or options.checks:
parser.error('Choose cached profiles or a recomputation, not both')
profiles = {beta: profile(beta) for beta in BETAS}
else:
if not options.profiles or not options.checks:
parser.error('Provide --profiles and --checks, or explicitly --recompute')
profiles, _, _ = load_checked_profiles(options.profiles, options.checks)
entries = [{'params': params, 'number': generator.from_profile(params, profiles[params['beta']])}
for params in generator.enumerate()]
options.offline.write_text(json.dumps(entries, indent=2) + '\n')
if __name__ == '__main__':
main()