76 lines
3.9 KiB
Python
76 lines
3.9 KiB
Python
"""Audit saved numerical data and freeze provenance before archive/cleanup."""
|
|
import argparse
|
|
import csv
|
|
import hashlib
|
|
import json
|
|
import math
|
|
import os
|
|
import pathlib
|
|
import platform
|
|
import re
|
|
import shutil
|
|
import time
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument('--root', required=True, type=pathlib.Path)
|
|
p.add_argument('--analysis-code-commit', help='Commit actually used for the weight analysis; omit if unknown')
|
|
args = p.parse_args()
|
|
root = args.root.resolve()
|
|
if not __debug__:
|
|
raise RuntimeError('Validation requires Python assertions enabled')
|
|
checked = {}
|
|
for mode in ('tensor', 'block'):
|
|
with (root / 'analysis' / f'{mode}_pairs.csv').open() as f:
|
|
rows = list(csv.DictReader(f))
|
|
coverage = json.loads((root / 'analysis' / f'{mode}_coverage.json').read_text())
|
|
if len(rows) != coverage['pairs_verified']:
|
|
raise ValueError('Coverage count mismatch')
|
|
for row in rows:
|
|
for key in ('cosine', 'pearson'):
|
|
if row[key] and (not math.isfinite(float(row[key])) or abs(float(row[key])) > 1 + 1e-7):
|
|
raise ValueError(f'Invalid correlation: {mode}/{key}')
|
|
for key in ('error_member_to_rep', 'error_rep_to_member', 'symmetric_error', 'scaled_error',
|
|
'sampled_error_q99_over_member_rms', 'screen_vs_exact_min_error_absolute_difference'):
|
|
value = float(row[key])
|
|
if not math.isfinite(value) or value < 0:
|
|
raise ValueError(f'Invalid error: {mode}/{key}')
|
|
if not math.isfinite(float(row['scale'])):
|
|
raise ValueError('Invalid scaling diagnostic')
|
|
checked[mode] = {'rows': len(rows), 'finite_metrics': True, 'coverage_matches': True}
|
|
test_log = (root / 'tests.log').read_text()
|
|
match = re.search(r'Ran (\d+) tests in ([\d.]+)s', test_log)
|
|
if not match or '\nOK\n' not in test_log:
|
|
raise ValueError('Final tests did not pass')
|
|
qc = {'passed': True, 'checked_unix': time.time(), 'numerical_tables': checked,
|
|
'final_tests': int(match[1]), 'final_test_seconds': float(match[2]),
|
|
'earlier_parallel_test_timeout_seconds': 300 if (root / 'tests-during-io-timeout.txt').exists() else None}
|
|
(root / 'numerical-qc.json').write_text(json.dumps(qc, indent=2))
|
|
for src, dest in [('tests.log', 'test-results.txt'), ('analysis.log', 'analysis-run.txt'),
|
|
('plot.log', 'plot-run.txt'), ('download-ranged.log', 'download-progress.txt'),
|
|
('supervisor.log', 'supervisor-run.txt')]:
|
|
shutil.copy2(root / src, root / dest)
|
|
mem = {}
|
|
for line in pathlib.Path('/proc/meminfo').read_text().splitlines():
|
|
key, value = line.split(':', 1)
|
|
if key in ('MemTotal', 'MemAvailable', 'SwapTotal', 'SwapFree'):
|
|
mem[key + '_KiB'] = int(value.split()[0])
|
|
environment = {'recorded_unix': time.time(), 'python': platform.python_version(),
|
|
'platform': platform.platform(), 'machine': platform.machine(),
|
|
'logical_cpus': os.cpu_count(), 'memory_snapshot': mem,
|
|
'blas_threads': 1, 'assertions_enabled': __debug__,
|
|
'analysis_io_priority': 'best-effort 7, applied during block verification',
|
|
'analysis_code_commit': args.analysis_code_commit,
|
|
'note': 'Shared host; see resource-observation.txt. Memory snapshot is post-analysis.'}
|
|
(root / 'execution-environment.json').write_text(json.dumps(environment, indent=2))
|
|
project = pathlib.Path(__file__).resolve().parents[1]
|
|
files = sorted((project / 'pilot').glob('*.py')) + [project / 'requirements.txt', project / 'tests/test_pilot.py']
|
|
hashes = [f'{hashlib.sha256(file.read_bytes()).hexdigest()} {file.relative_to(project)}' for file in files]
|
|
(root / 'code_checksums.txt').write_text('\n'.join(hashes) + '\n')
|
|
print(json.dumps(qc))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|