#!/usr/bin/env python3 """Audit the paired six-step live experiment without loading state vectors. Hashes bind the producer's observations. Scalar identities check those recorded observations; they do not supply a fresh FEM action, a pointwise certificate, or an unavailable corrected displacement. Nonconvergence and physical defects are reported separately from artifact/decision consistency. """ import argparse import csv import datetime import hashlib import json import math from pathlib import Path import sys EPS = sys.float_info.epsilon ARMIJO = 1.e-4 # The live driver's default; this is not a configurable CLI flag. COUNTERS = ('attempts', 'kept', 'fallbacks', 'iterations', 'preparations', 'restorations', 'solve_seconds', 'verification_seconds', 'preparation_seconds', 'refresh_seconds') REQUIRED = {'metadata.txt', 'layout.csv', 'initial_physical.csv', 'final_physical.csv', 'newton.csv', 'blocks.csv', 'trials.csv', 'coupled_trials.csv', 'coupled_search.csv', 'coupled_candidates.csv', 'trial_state_consistency.csv', 'numerical_strategies.csv', 'accepted_state.txt'} def digest(path): result = hashlib.sha256() with path.open('rb') as stream: for chunk in iter(lambda: stream.read(1024 * 1024), b''): result.update(chunk) return result.hexdigest() def rows(path): with path.open() as stream: result = list(csv.DictReader(stream)) if any(None in row or any(value is None for value in row.values()) for row in result): raise ValueError(f'Incomplete CSV row: {path}') return result def close(a, b, scale=0.): return math.isfinite(a) and math.isfinite(b) and abs(a-b) <= 1.e-10*max(abs(a), abs(b)) + 256*EPS*abs(scale) def safe(value): if isinstance(value, float) and not math.isfinite(value): return None if isinstance(value, dict): return {key: safe(item) for key, item in value.items()} if isinstance(value, (list, tuple)): return [safe(item) for item in value] return value def key(row): return int(row['iteration']), int(row['trial']) def indexed(records): result = {key(row): row for row in records} if len(result) != len(records): raise ValueError('Duplicate iteration/trial observations') return result def physical(path, metadata, check): data = rows(path) values = {(int(row['quadrature']), row['metric']): float(row['value']) for row in data} check(path.name+':unique_metrics', len(values) == len(data)) check(path.name+':quadratures', {q for q, _ in values} == {14, 18}) result = {} for q in (14, 18): m = {metric: value for (order, metric), value in values.items() if order == q} label = f'{path.name}:{q}:' check(label+'model', m['polytropic_index'] == float(metadata['n']) and m['polytropic_constant'] == float(metadata['K'])) complete = m['complete_volume_measurement'] == 1 if complete: check(label+'mass_error', close(m['mass_absolute_error'], abs(m['mass']-float(metadata['M'])), abs(m['mass']))) check(label+'angular_momentum_error', close(m['angular_momentum_absolute_error'], abs(m['angular_momentum_z']-float(metadata['J'])), 1.)) virials = {} for pressure_prefix, defined_key, pressure_key in ( ('', 'pressure_from_density_defined_everywhere', 'pressure_integral'), ('enthalpy_', 'pressure_from_enthalpy_defined_everywhere', 'enthalpy_pressure_integral')): defined = m[defined_key] == 1 and complete and math.isfinite(m['binding_energy']) and m['binding_energy'] < 0 for force, binding_key in (('', 'binding_energy'), ('force_', 'force_binding_energy')): name = pressure_prefix+force+'virial_' signed, error = m[name+'signed'], m[name+'error'] if defined: numerator = 2*m['kinetic_energy']+m[binding_key]+3*m[pressure_key] scale = (2*abs(m['kinetic_energy'])+abs(m[binding_key])+3*abs(m[pressure_key]))/abs(m['binding_energy']) check(label+name+'identity', close(signed, numerator/abs(m['binding_energy']), scale) and close(error, abs(signed), scale)) else: check(label+name+'undefined', math.isnan(signed) and math.isnan(error)) virials[name+'within_1e5'] = error <= 1.e-5 if defined and math.isfinite(error) else None result[str(q)] = {'metrics': m, 'virial_milestones': virials, 'pressure_from_density_defined': m['pressure_from_density_defined_everywhere'] == 1, 'sampled_negative_density': m['negative_density_samples'] > 0, 'sampled_negative_enthalpy': m['negative_enthalpy_samples'] > 0, 'global_enthalpy_certificate': 'not_measured'} return result def inspect_case(folder, record, check): metadata = dict(line.split('=', 1) for line in (folder/'metadata.txt').read_text().splitlines() if '=' in line) history, trials = rows(folder/'newton.csv'), rows(folder/'trials.csv') coupled, searches = indexed(rows(folder/'coupled_trials.csv')), indexed(rows(folder/'coupled_search.csv')) consistency = indexed(rows(folder/'trial_state_consistency.csv')) trial_map = indexed(trials) check('trial_joins', set(trial_map) == set(coupled) == set(searches) == set(consistency)) candidates = {} for row in rows(folder/'coupled_candidates.csv'): candidates.setdefault(key(row), []).append(row) check('candidate_trial_coverage', set(candidates) <= set(trial_map)) strategy_rows = rows(folder/'numerical_strategies.csv') strategy = {row['metric']: float(row['value']) for row in strategy_rows} check('unique_strategy_summary', len(strategy) == len(strategy_rows)) initial = physical(folder/'initial_physical.csv', metadata, check) endpoint = physical(folder/'final_physical.csv', metadata, check) before = initial['18']['metrics']['normalized_bordered_residual'] initial_residual = before check('six_step_control', metadata['max_newton'] == '6' and float(metadata['linear_tol']) == .03) check('single_auxiliary_budget', metadata['coupled_attempts_per_iteration'] == '1') check('outer_iteration_sequence', [int(row['iteration']) for row in history] == list(range(len(history))) and len(history) <= 6) block_data = rows(folder/'blocks.csv') residual_blocks = {row['block'] for row in rows(folder/'layout.csv') if row['kind'] == 'residual'} blocks = {(int(row['iteration']), row['stage'], row['block']): float(row['normalized_norm']) for row in block_data} check('unique_block_observations', len(blocks) == len(block_data)) expected_blocks = {(int(row['iteration']), stage, block) for row in history for stage in ('before', 'after') for block in residual_blocks} check('block_coverage', set(blocks) == expected_blocks) previous = dict.fromkeys(COUNTERS, 0.) previous_deferred = 0. accepted_count = 0 cumulative_seconds = 0. cumulative_linear_iterations = 0 step_results = [] used_trials = set() for row in history: iteration = int(row['iteration']) iteration_trials = [trial for trial in trials if int(trial['iteration']) == iteration] check(f'{iteration}:trial_sequence', [int(trial['trial']) for trial in iteration_trials] == list(range(len(iteration_trials)))) check(f'{iteration}:trial_count', int(row['trials']) == len(iteration_trials)) for stage, value in (('before', before), ('after', float(row['F']))): block_norm = math.hypot(*(blocks[(iteration, stage, block)] for block in residual_blocks)) check(f'{iteration}:{stage}:block_norm', close(block_norm, value)) check(f'{iteration}:linear_rhs_matches_base', close(float(row['linear_rhs_norm']), before)) outer_gate = math.isfinite(float(row['linear_true'])) and float(row['linear_true']) <= .03 step_accepted = row['accepted'] == '1' check(f'{iteration}:accepted_boolean', row['accepted'] in ('0', '1')) accepted_trials = [trial for trial in iteration_trials if trial['disposition'] == '0'] check(f'{iteration}:accepted_trial_count', len(accepted_trials) == int(step_accepted)) if step_accepted: accepted_count += 1 check(f'{iteration}:accepted_linear_gate', row['linear_status'] == '0' and outer_gate) check(f'{iteration}:strict_residual_decrease', 0 <= float(row['F']) < before) check(f'{iteration}:accepted_trial_binding', accepted_trials[0] == iteration_trials[-1] and close(float(accepted_trials[0]['F']), float(row['F'])) and accepted_trials[0]['alpha'] == row['alpha']) else: check(f'{iteration}:failed_state_unchanged', close(float(row['F']), before)) increments = dict.fromkeys(COUNTERS, 0.) deferred_increment = 0. observations = [] for trial in iteration_trials: address = key(trial) used_trials.add(address) label = f'{iteration}:{address[1]}:' c, search, roundtrip = coupled[address], searches[address], consistency[address] alpha = float(trial['alpha']) check(label+'alpha_binding', 0 < alpha <= 1 and float(c['alpha']) == alpha and float(search['alpha']) == alpha) check(label+'diagnostics_available', c['diagnostics_available'] == '1' and c['status'] == search['status']) check(label+'observer_roundtrip', math.isfinite(float(roundtrip['relative_error'])) and 0 <= float(roundtrip['relative_error']) <= 64*EPS and math.isfinite(float(roundtrip['normalized_physical_roundtrip_error'])) and float(roundtrip['normalized_physical_roundtrip_error']) >= 0) change = {} for counter in COUNTERS: current = float(c[counter+'_total']) check(label+counter+':monotone', math.isfinite(current) and current >= previous[counter]) change[counter] = current-previous[counter] increments[counter] += change[counter] previous[counter] = current deferred = float(search['deferred_total']) check(label+'deferred_monotone', math.isfinite(deferred) and deferred >= previous_deferred) deferred_change = deferred-previous_deferred deferred_increment += deferred_change previous_deferred = deferred has_solve = c['has_solve'] == '1' status = int(c['status']) check(label+'known_status', 0 <= status <= 9 and c['has_solve'] in ('0', '1')) check(label+'attempt_accounting', change['attempts'] == int(has_solve) and change['iterations'] == int(c['linear_iterations'])) check(label+'selection_accounting', change['kept'] == int(status == 3) and change['fallbacks'] == int(has_solve and status != 3) and deferred_change == int(status == 9)) proposals = candidates.get(address, []) check(label+'candidate_count', len(proposals) == int(search['candidate_count']) and len(proposals) <= int(metadata['coupled_beta_steps'])) check(label+'candidate_sequence', [int(item['candidate']) for item in proposals] == list(range(len(proposals)))) merit_limit = (1-ARMIJO*alpha)*.5*before*before if trial['disposition'] == '0': check(label+'ordinary_merit_acceptance', math.isfinite(float(trial['F'])) and .5*float(trial['F'])**2 <= merit_limit) for index, proposal in enumerate(proposals): beta, merit = float(proposal['beta']), float(proposal['merit']) check(label+f'beta{index}:grid', close(beta, float(metadata['coupled_beta_reduction'])**index)) check(label+f'beta{index}:status', proposal['status'] in ('0', '1', '2') and proposal['accepted'] in ('0', '1')) valid = proposal['status'] == '0' check(label+f'beta{index}:metric', math.isfinite(merit) and merit >= 0 if valid else math.isnan(merit)) accepted = valid and merit < float(c['original_merit']) and merit <= merit_limit check(label+f'beta{index}:ordinary_acceptance', (proposal['accepted'] == '1') == accepted) if accepted: check(label+'first_accepted_beta_stops_search', index == len(proposals)-1) check(label+f'beta{index}:time', math.isfinite(float(proposal['preparation_seconds'])) and float(proposal['preparation_seconds']) >= 0) if has_solve: rhs, threshold, verified = float(c['rhs_norm']), float(c['threshold']), float(c['verified_true_norm']) check(label+'auxiliary_work_bound', 0 <= int(c['linear_iterations']) <= int(metadata['coupled_iterations'])) if math.isfinite(rhs) and math.isfinite(threshold): check(label+'auxiliary_threshold', close(threshold, max(1.e-14, float(metadata['coupled_tolerance'])*rhs))) if math.isfinite(verified) and math.isfinite(threshold): check(label+'auxiliary_true_gate', (c['verified_gate'] == '1') == (verified <= threshold)) if status == 3: selected_beta = float(search['selected_beta']) chosen = [item for item in proposals if float(item['beta']) == selected_beta] check(label+'retained_auxiliary_certificate', has_solve and c['linear_status'] == '0' and c['verified_gate'] == '1' and math.isfinite(float(c['verified_true_norm'])) and float(c['verified_true_norm']) <= float(c['threshold']) and 0 <= float(c['correction_norm']) <= float(c['maximum_correction_norm'])) check(label+'retained_beta_observed', len(chosen) == 1 and chosen[0]['status'] == '0') if len(chosen) == 1: check(label+'retained_metric_replay', close(float(chosen[0]['merit']), float(c['corrected_merit']))) check(label+'retained_merit_improves', 0 <= float(c['corrected_merit']) < float(c['original_merit'])) check(label+'retained_residual_binding', close(.5*float(trial['F'])**2, float(c['corrected_merit']))) else: check(label+'no_stale_selection', math.isnan(float(search['selected_beta']))) if not has_solve: check(label+'no_stale_auxiliary_report', c['linear_status'] == '-1' and c['verified_gate'] == '0' and not proposals) if status == 9 or has_solve: if metadata['strategy'] == 'curvature-backtracked-coupled': base_merit, predicted = float(search['base_merit']), float(search['predicted_merit']) drop = float(search['predicted_decrease']) check(label+'prediction_base_binding', close(base_merit, .5*before*before)) check(label+'predicted_decrease_identity', close(drop, base_merit-predicted, base_merit)) if drop > 0: ratio = (float(c['original_merit'])-base_merit)/drop check(label+'excess_ratio_identity', close(float(search['excess_ratio']), ratio, 1.)) check(label+'near_gate_decision', (ratio <= float(metadata['coupled_excess_ratio'])) == has_solve) else: check(label+'nonpositive_prediction_defers', status == 9 and not has_solve) observations.append({'trial': address[1], 'alpha': alpha, 'disposition': int(trial['disposition']), 'rejection_source': trial['source'], 'F': float(trial['F']), 'coupled_status': status, 'selected_beta': float(search['selected_beta']), 'auxiliary_changes': change, 'deferrals': deferred_change, 'candidates': proposals}) check(f'{iteration}:auxiliary_attempt_budget', increments['attempts'] <= 1) for field in ('seconds', 'linear_seconds', 'prepare_seconds', 'refresh_seconds'): check(f'{iteration}:{field}', math.isfinite(float(row[field])) and float(row[field]) >= 0) cumulative_seconds += float(row['seconds']) cumulative_linear_iterations += int(row['linear_iterations']) step_results.append({'iteration': iteration, 'accepted': step_accepted, 'F_before': before, 'F': float(row['F']), 'alpha': float(row['alpha']), 'outer_true_relative': float(row['linear_true']), 'outer_true_gate': outer_gate, 'outer_linear_iterations': int(row['linear_iterations']), 'iteration_seconds': float(row['seconds']), 'cumulative_iteration_seconds': cumulative_seconds, 'cumulative_outer_linear_iterations': cumulative_linear_iterations, 'linear_seconds': float(row['linear_seconds']), 'reported_trial_preparation_seconds': float(row['prepare_seconds']), 'outer_refresh_seconds': float(row['refresh_seconds']), 'auxiliary_changes': increments, 'auxiliary_deferrals': deferred_increment, 'trials': observations}) before = float(row['F']) check('all_trials_accounted', used_trials == set(trial_map)) check('accepted_steps_metadata', accepted_count == int(metadata['accepted_steps'])) converged = metadata['converged'] == '1' check('termination_record', metadata['converged'] in ('0', '1') and record['exit_code'] == (0 if converged else 3) and (converged or bool(metadata.get('failure')))) check('bounded_outcome', accepted_count == 6 or converged or bool(metadata.get('failure'))) for counter, total in previous.items(): check('final_auxiliary_'+counter, close(strategy['coupled_'+counter], total)) check('final_deferred_count', strategy['coupled_deferred_attempts'] == previous_deferred) for q in ('14', '18'): check('endpoint_residual_'+q, close(endpoint[q]['metrics']['normalized_bordered_residual'], before)) return {'metadata': metadata, 'converged': converged, 'failure': metadata.get('failure'), 'accepted_steps': accepted_count, 'initial_residual': initial_residual, 'final_residual': before, 'steps': step_results, 'summed_iteration_seconds': math.fsum(float(row['seconds']) for row in history), 'summed_outer_linear_iterations': sum(int(row['linear_iterations']) for row in history), 'auxiliary_totals': previous, 'auxiliary_deferrals_total': previous_deferred, 'initial_physical': initial, 'endpoint_physical': endpoint, 'actual_corrected_displacement': 'not_logged; alpha and beta do not determine its norm', 'first_accepted_record': next((row for row in history if row['accepted'] == '1'), None), 'first_after_blocks': [row for row in block_data if row['iteration'] == '0' and row['stage'] == 'after']} def audit(campaign): campaign = campaign.resolve() checks, cases = [], {} def check(name, value): checks.append({'check': name, 'passed': bool(value)}) try: provenance = json.loads((campaign/'provenance.json').read_text()) specifications = provenance['cases'] check('paired_manifest', len(specifications) == 2 and len({case['id'] for case in specifications}) == 2 and specifications == json.loads((campaign/'cases.json').read_text())) check('immutable_executable', digest(campaign/'plateau_diagnostic') == provenance['binary_sha256']) check('mesh_identity', digest(Path(provenance['mesh'])) == provenance['mesh_sha256']) for specification in specifications: name, arguments = specification['id'], specification['arguments'] folder = campaign/name record_path = campaign/(name+'_run.json') record = json.loads(record_path.read_text()) case_check = lambda label, value: check(name+':'+label, value) case_check('completed_observation', bool(record.get('finished_utc')) and not record.get('timed_out', False) and record.get('exit_code') in (0, 3)) case_check('command_identity', record['id'] == name and record['command'] == [str(campaign/'plateau_diagnostic'), *arguments, '--mesh', provenance['mesh'], '--output', str(folder)]) for path, expected in record['input_state_hashes'].items(): case_check('input_hash:'+path, digest(Path(path)) == expected) outputs = record['output_hashes'] case_check('required_hashes', REQUIRED <= outputs.keys()) observed_files = {str(path.relative_to(folder)) for path in folder.rglob('*') if path.is_file() and path.suffix in ('.txt', '.csv', '.json')} case_check('complete_hash_manifest', observed_files == set(outputs)) for path, expected in outputs.items(): case_check('output_hash:'+path, digest(folder/path) == expected) result = inspect_case(folder, record, case_check) metadata = result['metadata'] for option, field in {'--index': 'n', '--angular-momentum': 'J', '--max-newton': 'max_newton', '--restart': 'restart', '--max-linear': 'max_linear', '--linear-tol': 'linear_tol', '--rtol': 'rtol', '--atol': 'atol', '--schur-sweeps': 'schur_correction_sweeps', '--schur-relaxation': 'schur_relaxation'}.items(): if option in arguments: case_check('requested_'+field, float(metadata[field]) == float(arguments[arguments.index(option)+1])) for option, field in (('--strategy', 'strategy'), ('--preconditioner-variant', 'preconditioner_variant')): case_check('requested_'+field, metadata[field] == arguments[arguments.index(option)+1]) case_check('no_central', '--no-central' in arguments and metadata['central_phase'] == '0') elapsed = (datetime.datetime.fromisoformat(record['finished_utc'])-datetime.datetime.fromisoformat(record['started_utc'])).total_seconds() case_check('utc_elapsed_identity', close(record['utc_elapsed_seconds'], elapsed)) case_check('clock_difference_identity', close(record['utc_minus_monotonic_seconds'], elapsed-record['seconds'], elapsed)) case_check('child_cpu_identity', close(record['child_cpu_seconds'], record['child_user_cpu_seconds']+record['child_system_cpu_seconds'])) for field in ('seconds', 'utc_elapsed_seconds', 'child_user_cpu_seconds', 'child_system_cpu_seconds', 'child_cpu_seconds'): case_check('timing_'+field, math.isfinite(record[field]) and record[field] >= 0) result['timing'] = {field: record[field] for field in ('started_utc', 'finished_utc', 'seconds', 'utc_elapsed_seconds', 'utc_minus_monotonic_seconds', 'child_user_cpu_seconds', 'child_system_cpu_seconds', 'child_cpu_seconds')} result['timing']['clock_disagreement_observed'] = abs(record['utc_minus_monotonic_seconds']) > max(.5, .01*record['seconds']) result['run_record_sha256'] = digest(record_path) result['output_hashes'] = outputs cases[name] = result first, second = specifications def without_strategy(arguments): values = list(arguments) position = values.index('--strategy') del values[position:position+2] return values check('pair:only_strategy_differs', without_strategy(first['arguments']) == without_strategy(second['arguments'])) a, b = cases[first['id']], cases[second['id']] check('pair:strategies', {a['metadata']['strategy'], b['metadata']['strategy']} == {'curvature', 'curvature-backtracked-coupled'}) excluded = {'strategy', 'converged', 'accepted_steps', 'failure'} check('pair:matching_metadata', {k: v for k, v in a['metadata'].items() if k not in excluded} == {k: v for k, v in b['metadata'].items() if k not in excluded}) for path in ('initial_physical.csv', 'layout.csv'): check('pair:exact_'+path, a['output_hashes'][path] == b['output_hashes'][path]) both_first = a['first_accepted_record'] is not None and b['first_accepted_record'] is not None if both_first: fields = ('iteration', 'F', 'alpha', 'trials', 'linear_status', 'linear_iterations', 'linear_true', 'central_border', 'unbordered_F', 'linear_initial_residual', 'linear_rhs_norm', 'linear_initial_over_rhs') check('pair:first_accepted_record_exact', all(a['first_accepted_record'][k] == b['first_accepted_record'][k] for k in fields)) check('pair:first_accepted_blocks_exact', a['first_after_blocks'] == b['first_after_blocks']) passed = all(item['passed'] for item in checks) return safe({'passed': passed, 'observations_bound_to_valid_artifacts': passed, 'checks': checks, 'cases': cases, 'source_commit': provenance['commit'], 'provenance_sha256': digest(campaign/'provenance.json'), 'analysis_script_sha256': digest(Path(__file__)), 'both_first_accepted_steps_observed': both_first, 'scope': 'Recorded decision/scalar-identity audit, with streaming artifact hashes. Outer and auxiliary true defects are producer observations, not recomputed FEM actions. Matching first-step diagnostics are not bitwise state equality. Numerical and physical outcomes are separate; no speedup-to-convergence claim. Auxiliary phase counters are differenced, not summed repeatedly; nested phase timings must not be added to total iteration time.'}) except (OSError, ValueError, KeyError, TypeError, IndexError, OverflowError) as error: check('complete_readable_campaign', False) return safe({'passed': False, 'checks': checks, 'error': str(error), 'cases': cases, 'interpretation_valid': False}) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('campaign', type=Path) parser.add_argument('--output', type=Path, required=True) args = parser.parse_args() if args.output.resolve() == args.campaign.resolve() or args.campaign.resolve() in args.output.resolve().parents: parser.error('Write analysis outside the immutable campaign.') result = audit(args.campaign) args.output.write_text(json.dumps(result, indent=2, allow_nan=False)+'\n') print('PASS' if result['passed'] else 'FAIL', args.output) for name, case in result['cases'].items(): print(name, 'accepted=', case['accepted_steps'], 'converged=', case['converged'], 'final F=', case['final_residual']) return 0 if result['passed'] else 1 if __name__ == '__main__': raise SystemExit(main())