408 lines
31 KiB
Python
408 lines
31 KiB
Python
#!/usr/bin/env python3
|
|
"""Audit a sequential frozen line search, separately from numerical success.
|
|
|
|
Only saved-vector arithmetic is independent here: no FEM action or physical
|
|
quadrature is repeated. A shared attempt budget and first-accept stopping rule
|
|
are checked explicitly; these frozen searches are not Newton trajectories.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
|
|
from check_nonlinear_step import EPS, close, digest, dot, layout_for, minus, norm, rows, sanitize, vector
|
|
from check_coupled_trial import inspect_physical, near_vector
|
|
|
|
P = 'damped_coupled_'
|
|
STATUS = ('not_attempted', 'original_accepted', 'budget_exhausted', 'kept', 'linear_failure',
|
|
'norm_limit', 'non_finite', 'inadmissible', 'not_improved', 'prediction_deferred')
|
|
BASE = {'metadata.txt', 'coupled_trial_physical.csv'} | {
|
|
P + name + '.csv' for name in ('summary', 'trials', 'candidates', 'evaluations', 'layout')} | {
|
|
P + name + '.txt' for name in ('base', 'base_physical', 'base_residual', 'direction', 'base_action',
|
|
'state_factors', 'residual_factors', 'restored_residual', 'restored_action')}
|
|
|
|
|
|
def same_number(a, b, scale=0.):
|
|
return (math.isnan(a) and math.isnan(b)) or (not math.isfinite(a) and a == b) or close(a, b, scale)
|
|
|
|
|
|
def merit(values):
|
|
return .5 * math.fsum(value * value for value in values)
|
|
|
|
|
|
def eligibility(base_merit, prediction_merit, original_merit, maximum_excess):
|
|
decrease = base_merit - prediction_merit
|
|
ratio = (original_merit - base_merit) / decrease if math.isfinite(decrease) and decrease > 0. else math.nan
|
|
return math.isfinite(ratio) and ratio <= maximum_excess, decrease, ratio
|
|
|
|
|
|
def correction_mechanism(prediction, before, corrected, action, rhs, beta):
|
|
remainder = minus(before, prediction)
|
|
defect = minus(action, rhs)
|
|
own = [z - y - beta * a for z, y, a in zip(corrected, before, action)]
|
|
after = minus(corrected, prediction)
|
|
reconstructed = [(1. - beta) * r + beta * d + e for r, d, e in zip(remainder, defect, own)]
|
|
return {'remainder_before_norm': norm(remainder), 'remainder_after_norm': norm(after),
|
|
'scaled_linear_defect_norm': abs(beta) * norm(defect), 'own_taylor_remainder_norm': norm(own),
|
|
'decomposition_error_norm': norm(minus(after, reconstructed))}
|
|
|
|
|
|
def inspect_case(folder):
|
|
try:
|
|
return inspect_case_artifacts(Path(folder))
|
|
except (OSError, ValueError, KeyError, TypeError, IndexError, OverflowError, ZeroDivisionError) as error:
|
|
return {'passed': False, 'observations_valid': False, 'required_artifacts': sorted(BASE),
|
|
'checks': [{'check': 'complete_readable_artifacts', 'passed': False, 'error': str(error)}],
|
|
'trials': [], 'physical_observations': []}
|
|
|
|
|
|
def inspect_case_artifacts(folder):
|
|
checks, required = [], set(BASE)
|
|
def check(name, condition):
|
|
checks.append({'check': name, 'passed': bool(condition)})
|
|
def flag(row, key, label):
|
|
value = float(row[key]); check(label + key + ':boolean', value in (0., 1.)); return value == 1.
|
|
def get(name, available=True, prefix=P):
|
|
path = folder / (prefix + name + '.txt')
|
|
if available:
|
|
required.add(path.name); return vector(path)
|
|
check(name + ':unavailable_file_absent', not path.exists()); return None
|
|
summary_rows = rows(folder / (P + 'summary.csv'))
|
|
summary = {row['metric']: row['value'] if row['metric'] == 'mode' else float(row['value']) for row in summary_rows}
|
|
check('unique_summary', len(summary) == len(summary_rows))
|
|
metadata = dict(line.split('=', 1) for line in (folder / 'metadata.txt').read_text().splitlines() if '=' in line)
|
|
mode = summary['mode']; check('mode', mode in ('none', 'full', 'damped', 'near'))
|
|
check('metadata_scope', metadata['damped_coupled_probe'] == '1' and metadata['coupled_trial_alphas_are_independent'] == '0' and
|
|
metadata['coupled_trial_admissibility'] == 'sampled_enthalpy_without_repair' and metadata['coupled_search'] == mode)
|
|
for key, source in {'outer_linear_tolerance': 'linear_tol', 'relative_tolerance': 'coupled_tolerance',
|
|
'maximum_iterations': 'coupled_iterations', 'restart': 'coupled_restart',
|
|
'maximum_correction_ratio': 'coupled_maximum_ratio', 'attempt_budget': 'coupled_attempts_per_iteration',
|
|
'beta_reduction': 'coupled_beta_reduction', 'maximum_excess_ratio': 'coupled_excess_ratio'}.items():
|
|
check('metadata:' + key, summary[key] == float(metadata[source]))
|
|
for key, expected in {'schema_version': 1, 'attempt_budget': 1, 'shared_iteration_budget': 1,
|
|
'accepted_commits': 0, 'probe_exception': 0, 'accepted_buffers_bitwise_identical': 1,
|
|
'direction_buffers_bitwise_identical': 1, 'normalization_factors_bitwise_identical': 1,
|
|
'fresh_operator_and_inverse_current': 1}.items():
|
|
check(key, summary[key] == expected)
|
|
for name in ('original_accepted', 'budget_exhausted', 'kept', 'prediction_deferred'):
|
|
check('status_mapping:' + name, summary['status_' + name] == STATUS.index(name))
|
|
for value, name in enumerate(('evaluated', 'inadmissible', 'non_finite')):
|
|
check('candidate_status:' + name, summary['candidate_status_' + name] == value)
|
|
gravity_path = flag(summary, 'gravity_path', 'summary:')
|
|
check('gravity_policy', gravity_path == metadata['strategy'].startswith('curvature-'))
|
|
maximum_trials = int(summary['maximum_trials'])
|
|
maximum_steps = int(summary['maximum_beta_steps'])
|
|
reduction = summary['beta_reduction']
|
|
check('outer_grid_control', 1 <= maximum_trials <= 30 and maximum_trials == summary['maximum_trials'])
|
|
check('beta_grid_control', maximum_steps >= 1 and maximum_steps == summary['maximum_beta_steps'] and 0 < reduction < 1)
|
|
check('full_step_policy', mode not in ('none', 'full') or maximum_steps == 1)
|
|
check('beta_steps_metadata', mode in ('none', 'full') or maximum_steps == int(metadata['coupled_beta_steps']))
|
|
check('finite_controls', all(math.isfinite(summary[key]) and summary[key] > 0. for key in
|
|
('relative_tolerance', 'maximum_correction_ratio', 'maximum_excess_ratio', 'outer_linear_tolerance')))
|
|
check('nonnegative_absolute_tolerance', math.isfinite(summary['absolute_tolerance']) and summary['absolute_tolerance'] >= 0.)
|
|
check('sufficient_decrease', 0 < summary['sufficient_decrease'] < 1)
|
|
x, physical, f, p, j, sf, rf = [get(name) for name in
|
|
('base', 'base_physical', 'base_residual', 'direction', 'base_action', 'state_factors', 'residual_factors')]
|
|
size = len(x)
|
|
check('dimensions', all(len(value) == size for value in (physical, f, p, j, sf, rf)))
|
|
check('positive_normalization', all(value > 0. for value in sf + rf))
|
|
check('base_normalization', near_vector(x, [value * factor for value, factor in zip(physical, sf)], norm(x)))
|
|
layout = rows(folder / (P + 'layout.csv'))
|
|
value_layout = layout_for(layout, 'value', size); residual_layout = layout_for(layout, 'residual', size)
|
|
gravity_values = {i for name, (offset, count) in value_layout.items() if name in ('gravity_gradient', 'gravity_potential')
|
|
for i in range(offset, offset + count)}
|
|
gravity_rows = [i for name, (offset, count) in residual_layout.items() if name in ('gravity_gradient_relation', 'poisson_balance')
|
|
for i in range(offset, offset + count)]
|
|
fn, pn, jn, m0 = norm(f), norm(p), norm(j), merit(f)
|
|
check('finite_nonzero_base', math.isfinite(fn) and fn > 0. and math.isfinite(pn) and pn > 0.)
|
|
defect = [a + b for a, b in zip(f, j)]
|
|
check('outer_true_gate', norm(defect) <= summary['outer_linear_tolerance'] * fn and summary['outer_verified_gate'] == 1)
|
|
for key, value in {'base_norm': fn, 'base_merit': m0, 'outer_relative_defect': norm(defect) / fn}.items():
|
|
check(key, close(summary[key], value, value))
|
|
for name, expected in (('residual', f), ('action', j)):
|
|
restored = get('restored_' + name)
|
|
error = norm(minus(restored, expected))
|
|
check('restored_' + name, near_vector(restored, expected, norm(expected)) and
|
|
close(summary['restored_' + name + '_error'], error, norm(expected)))
|
|
|
|
trial_rows = rows(folder / (P + 'trials.csv'))
|
|
candidate_rows = rows(folder / (P + 'candidates.csv'))
|
|
evaluation_rows = rows(folder / (P + 'evaluations.csv'))
|
|
check('sequential_trials', [int(row['trial']) for row in trial_rows] == list(range(len(trial_rows))) and
|
|
1 <= len(trial_rows) <= maximum_trials)
|
|
candidate_map = {(int(row['trial']), int(row['candidate'])): row for row in candidate_rows}
|
|
evaluation_map = {(int(row['trial']), int(row['evaluation'])): row for row in evaluation_rows}
|
|
check('unique_candidates', len(candidate_map) == len(candidate_rows))
|
|
check('unique_evaluations', len(evaluation_map) == len(evaluation_rows))
|
|
seen_candidates, seen_evaluations, outcomes = set(), set(), []
|
|
attempts, deferred, iterations, operation_seconds = 0, 0, 0, 0.
|
|
accepted_trial, physical_trials = -1, []
|
|
for row in trial_rows:
|
|
i, alpha = int(row['trial']), float(row['alpha'])
|
|
label, suffix = str(i) + ':', '_a' + str(i)
|
|
check(label + 'alpha', alpha == math.ldexp(1., -i))
|
|
flags = {key: flag(row, key, label) for key in ('raw_prepared', 'gravity_gate', 'usable', 'before_accepted',
|
|
'attempted', 'aux_gate', 'aux_reported_gate', 'aux_verification_applied', 'rhs_saved', 'correction_saved',
|
|
'verified_vectors_saved', 'selected_prepared', 'selected_accepted', 'selected_original')}
|
|
status = int(row['status']); check(label + 'status', 0 <= status < len(STATUS))
|
|
name = STATUS[status]
|
|
raw = get('raw' + suffix); prediction = get('prediction' + suffix)
|
|
raw_f = get('raw_residual' + suffix, flags['raw_prepared'])
|
|
check(label + 'raw_state', near_vector(raw, [a + alpha * b for a, b in zip(x, p)], norm(x) + alpha * pn))
|
|
check(label + 'prediction', near_vector(prediction, [a + alpha * b for a, b in zip(f, j)], fn + alpha * jn))
|
|
check(label + 'preparation_status', (int(row['raw_reason']) == -1 and int(row['raw_stage']) == -1)
|
|
if flags['raw_prepared'] else (int(row['raw_reason']) >= 0 and int(row['raw_stage']) >= 0))
|
|
check(label + 'usable_requires_preparation', not flags['usable'] or (flags['raw_prepared'] and flags['gravity_gate']))
|
|
if not gravity_path:
|
|
check(label + 'raw_path', flags['gravity_gate'] and flags['usable'] == flags['raw_prepared'])
|
|
before = get('before' + suffix, flags['usable'])
|
|
before_f = get('before_residual' + suffix, flags['usable'])
|
|
gravity_correction = get('gravity_correction' + suffix, gravity_path and flags['gravity_gate'])
|
|
if flags['usable'] and not gravity_path:
|
|
check(label + 'raw_baseline', before == raw and before_f == raw_f)
|
|
if gravity_correction is not None:
|
|
check(label + 'gravity_support', len(gravity_correction) == size and bool(gravity_values) and bool(gravity_rows) and
|
|
all(value == 0. for index, value in enumerate(gravity_correction) if index not in gravity_values))
|
|
check(label + 'gravity_displacement', near_vector(before, [a + b for a, b in zip(raw, gravity_correction)], norm(raw) + norm(gravity_correction)))
|
|
target = norm([before_f[index] - prediction[index] for index in gravity_rows])
|
|
threshold = float(row['gravity_target_threshold']); disagreement = float(row['gravity_affine_disagreement'])
|
|
check(label + 'gravity_target', close(float(row['gravity_target_defect']), target, fn + alpha * jn))
|
|
check(label + 'gravity_gate', math.isfinite(threshold) and threshold >= 0. and target <= threshold + 512 * EPS * fn and
|
|
math.isfinite(disagreement) and 0. <= disagreement <= threshold)
|
|
selected = get('selected' + suffix, flags['selected_prepared'])
|
|
selected_f = get('selected_residual' + suffix, flags['selected_prepared'])
|
|
check(label + 'selected_prepared', flags['selected_prepared'] == flags['usable'])
|
|
cutoff = (1. - summary['sufficient_decrease'] * alpha) * m0
|
|
for stage, value in (('before', before_f), ('selected', selected_f)):
|
|
if value is not None:
|
|
check(label + stage + '_norm', close(float(row[stage + '_norm']), norm(value), norm(value)))
|
|
check(label + stage + '_merit', close(float(row[stage + '_merit']), merit(value), merit(value)))
|
|
else:
|
|
check(label + stage + '_unavailable', math.isnan(float(row[stage + '_norm'])) and math.isnan(float(row[stage + '_merit'])))
|
|
before_accepted = flags['usable'] and merit(before_f) <= cutoff
|
|
check(label + 'before_armijo', flags['before_accepted'] == before_accepted)
|
|
eligible, predicted_decrease, excess = eligibility(m0, merit(prediction), merit(before_f), summary['maximum_excess_ratio']) \
|
|
if flags['usable'] else (False, math.nan, math.nan)
|
|
for key, expected in (('predicted_decrease', predicted_decrease), ('excess_ratio', excess)):
|
|
if math.isfinite(float(row[key])):
|
|
check(label + key, close(float(row[key]), expected, abs(expected)))
|
|
should_attempt = flags['usable'] and mode != 'none' and not before_accepted and attempts < summary['attempt_budget'] and \
|
|
(mode != 'near' or eligible)
|
|
check(label + 'attempt_decision', flags['attempted'] == should_attempt)
|
|
if mode == 'near' and flags['usable'] and not before_accepted and attempts < summary['attempt_budget']:
|
|
check(label + 'gate_recorded', same_number(float(row['predicted_decrease']), predicted_decrease, m0) and
|
|
same_number(float(row['excess_ratio']), excess, abs(excess)))
|
|
if not should_attempt:
|
|
expected = 'not_attempted' if not flags['usable'] or mode == 'none' else (
|
|
'original_accepted' if before_accepted else ('budget_exhausted' if attempts >= summary['attempt_budget'] else 'prediction_deferred'))
|
|
check(label + 'skip_status', name == expected)
|
|
deferred += expected == 'prediction_deferred'
|
|
if expected == 'prediction_deferred':
|
|
check(label + 'deferred_gate_recorded', same_number(float(row['predicted_decrease']), predicted_decrease, m0) and
|
|
same_number(float(row['excess_ratio']), excess, abs(excess)))
|
|
attempts += should_attempt
|
|
check(label + 'shared_budget', int(row['attempts_total']) == attempts <= summary['attempt_budget'])
|
|
rhs = get('rhs' + suffix, flags['rhs_saved']); correction = get('correction' + suffix, flags['correction_saved'])
|
|
action = get('action' + suffix, flags['verified_vectors_saved']); saved_defect = get('defect' + suffix, flags['verified_vectors_saved'])
|
|
check(label + 'save_flags', should_attempt or not any(flags[key] for key in ('rhs_saved', 'correction_saved', 'verified_vectors_saved')))
|
|
check(label + 'finite_verification_saved', not (flags['aux_verification_applied'] and math.isfinite(float(row['aux_defect']))) or flags['verified_vectors_saved'])
|
|
core = int(row['aux_core_status'])
|
|
if should_attempt:
|
|
iterations += int(row['aux_iterations'])
|
|
check(label + 'iteration_limit', 0 <= int(row['aux_iterations']) <= summary['maximum_iterations'])
|
|
check(label + 'reported_status', flags['aux_reported_gate'] == (int(row['aux_status']) == 0))
|
|
cap = summary['maximum_correction_ratio'] * alpha * pn
|
|
check(label + 'unscaled_cap', same_number(float(row['correction_cap']), cap, cap))
|
|
if rhs is not None:
|
|
check(label + 'rhs', near_vector(rhs, minus(prediction, before_f), norm(prediction) + norm(before_f)))
|
|
if math.isfinite(float(row['aux_threshold'])):
|
|
check(label + 'rhs_norm', close(float(row['aux_rhs_norm']), norm(rhs), norm(rhs)))
|
|
check(label + 'threshold', close(float(row['aux_threshold']), max(summary['absolute_tolerance'],
|
|
summary['relative_tolerance'] * norm(rhs)), float(row['aux_threshold'])))
|
|
if flags['verified_vectors_saved']:
|
|
check(label + 'verified_availability', flags['aux_verification_applied'] and rhs is not None and correction is not None)
|
|
delta = minus(action, rhs); dn, cn = norm(delta), norm(correction)
|
|
check(label + 'saved_defect', near_vector(saved_defect, delta, norm(action) + norm(rhs)))
|
|
check(label + 'true_defect', same_number(float(row['aux_defect']), dn, norm(rhs)))
|
|
check(label + 'true_gate', flags['aux_gate'] == (math.isfinite(dn) and dn <= float(row['aux_threshold'])))
|
|
check(label + 'correction_norm', same_number(float(row['correction_norm']), cn, cn))
|
|
expected_core = 3 if not math.isfinite(cn) else (1 if not flags['aux_reported_gate'] or not flags['aux_gate'] else (2 if cn > cap else 0))
|
|
check(label + 'core_decision', core == expected_core)
|
|
if core == 0:
|
|
check(label + 'admissible_requires_gates', flags['aux_reported_gate'] and flags['aux_gate'] and
|
|
flags['verified_vectors_saved'] and float(row['correction_norm']) <= cap)
|
|
else:
|
|
check(label + 'failed_core_status', name == {1: 'linear_failure', 2: 'norm_limit', 3: 'non_finite'}.get(core))
|
|
check(label + 'failed_core_no_preparation', int(row['preparations']) == 0 and int(row['restorations']) == 0)
|
|
else:
|
|
check(label + 'no_aux_certificate', core == -1 and int(row['aux_status']) == -1 and not flags['aux_gate'] and
|
|
not flags['aux_reported_gate'] and not flags['aux_verification_applied'] and int(row['aux_iterations']) == 0)
|
|
check(label + 'no_aux_preparations', int(row['preparations']) == 0 and int(row['restorations']) == 0)
|
|
|
|
count = int(row['candidate_count'])
|
|
check(label + 'candidate_budget', 0 <= count <= maximum_steps and (should_attempt and core == 0 or count == 0))
|
|
crows = [candidate_map[(i, k)] for k in range(count)]
|
|
seen_candidates.update((i, k) for k in range(count))
|
|
erows = [value for key, value in evaluation_map.items() if key[0] == i]
|
|
check(label + 'evaluation_order', [int(value['evaluation']) for value in erows] == list(range(len(erows))))
|
|
observed, mechanisms = [], []
|
|
for erow in erows:
|
|
e = int(erow['evaluation']); seen_evaluations.add((i, e)); tag = suffix + '_e' + str(e)
|
|
ex, ef = get('evaluated_state' + tag), get('evaluated_residual' + tag)
|
|
beta = float(erow['beta'])
|
|
check(label + f'e{e}:requires_correction', should_attempt and core == 0 and correction is not None)
|
|
scale = norm(before) + norm(correction)
|
|
check(label + f'e{e}:displacement', near_vector(ex, [b + beta * c for b, c in zip(before, correction)], scale))
|
|
allowed_beta = [0.] + [reduction ** k for k in range(count)]
|
|
check(label + f'e{e}:known_beta_or_restore', any(near_vector(ex,
|
|
[b + value * c for b, c in zip(before, correction)], scale) for value in allowed_beta))
|
|
check(label + f'e{e}:norm', same_number(float(erow['residual_norm']), norm(ef), norm(ef)))
|
|
check(label + f'e{e}:merit', same_number(float(erow['merit']), merit(ef), merit(ef)))
|
|
check(label + f'e{e}:armijo', flag(erow, 'accepted', label) == (merit(ef) <= cutoff))
|
|
observed.append((beta, ex, ef))
|
|
mechanisms.append({'evaluation': e, 'beta': beta, **correction_mechanism(prediction, before_f, ef, action, rhs, beta)})
|
|
evaluated = []
|
|
first_accepted = None
|
|
for k, crow in enumerate(crows):
|
|
beta = reduction ** k
|
|
check(label + f'c{k}:beta', float(crow['beta']) == beta)
|
|
cstatus = int(crow['status']); check(label + f'c{k}:status', cstatus in (0, 1, 2))
|
|
if cstatus == 0:
|
|
matches = [(ex, ef) for observed_beta, ex, ef in observed if near_vector(ex,
|
|
[b + beta * c for b, c in zip(before, correction)], norm(before) + norm(correction))]
|
|
check(label + f'c{k}:fresh_evaluation', bool(matches))
|
|
ex, ef = matches[0]
|
|
cm = merit(ef); evaluated.append((beta, ex, ef, cm))
|
|
check(label + f'c{k}:merit', close(float(crow['merit']), cm, cm))
|
|
accepted = cm <= cutoff
|
|
check(label + f'c{k}:armijo', flag(crow, 'accepted', label) == accepted)
|
|
if accepted and first_accepted is None: first_accepted = k
|
|
else:
|
|
check(label + f'c{k}:rejected', not flag(crow, 'accepted', label))
|
|
check(label + f'c{k}:time', math.isfinite(float(crow['preparation_seconds'])) and float(crow['preparation_seconds']) >= 0.)
|
|
if should_attempt and core == 0:
|
|
check(label + 'beta_stop_rule', count == (first_accepted + 1 if first_accepted is not None else maximum_steps))
|
|
selected_beta = float(row['selected_beta'])
|
|
if flags['usable']:
|
|
better_candidates = [item for item in evaluated if item[3] < merit(before_f)]
|
|
best = min(better_candidates, key=lambda item: item[3]) if better_candidates else None
|
|
if name == 'kept':
|
|
fresh = [ef for beta, ex, ef in observed if ex == selected]
|
|
check(label + 'kept_best', best is not None and selected_beta == best[0] and selected == best[1])
|
|
check(label + 'selected_fresh_residual', bool(fresh) and near_vector(selected_f, fresh[-1], norm(fresh[-1])) and merit(selected_f) < merit(before_f))
|
|
else:
|
|
check(label + 'fallback_original', math.isnan(selected_beta) and selected == before and near_vector(selected_f, before_f, norm(before_f)))
|
|
check(label + 'discarded_best_requires_failed_replay', best is None or
|
|
(name in ('inadmissible', 'non_finite', 'not_improved') and int(row['restorations']) > 0))
|
|
check(label + 'selected_original', flags['selected_original'] == (selected == before))
|
|
else:
|
|
check(label + 'unavailable_selection', math.isnan(selected_beta) and not flags['selected_original'])
|
|
accepted_now = flags['usable'] and merit(selected_f) <= cutoff
|
|
check(label + 'selected_armijo', flags['selected_accepted'] == accepted_now)
|
|
if accepted_now:
|
|
check(label + 'first_accept_stops', i == len(trial_rows) - 1 and accepted_trial == -1)
|
|
accepted_trial = i
|
|
pv = get('selected_physical' + suffix, prefix='coupled_trial_')
|
|
check(label + 'physical_normalization', near_vector(selected, [v * a for v, a in zip(pv, sf)], norm(selected)))
|
|
physical_trials.append({'sample': str(i), 'selected_prepared': '1', 'selected_accepted': '1', 'selected_norm': row['selected_norm']})
|
|
for key in ('raw_seconds', 'gravity_seconds', 'correction_seconds', 'solve_seconds', 'refresh_seconds', 'verification_seconds', 'preparation_seconds'):
|
|
check(label + key, math.isfinite(float(row[key])) and float(row[key]) >= 0.)
|
|
operation_seconds += sum(float(row[key]) for key in ('raw_seconds', 'gravity_seconds', 'correction_seconds'))
|
|
check(label + 'correction_time_scope', float(row['correction_seconds']) + 1.e-6 >=
|
|
sum(float(row[key]) for key in ('solve_seconds', 'refresh_seconds', 'verification_seconds', 'preparation_seconds')))
|
|
outcomes.append({'trial': i, 'alpha': alpha, 'status': name, 'attempted': should_attempt, 'attempts_total': attempts,
|
|
'before_accepted': before_accepted, 'selected_accepted': accepted_now, 'selected_beta': selected_beta,
|
|
'before_norm': float(row['before_norm']), 'selected_norm': float(row['selected_norm']),
|
|
'predicted_decrease': predicted_decrease, 'excess_ratio': excess, 'candidate_mechanisms': mechanisms})
|
|
check('candidate_coverage', seen_candidates == set(candidate_map))
|
|
check('evaluation_coverage', seen_evaluations == set(evaluation_map))
|
|
check('accepted_trial', summary['accepted_trial'] == accepted_trial)
|
|
if accepted_trial < 0:
|
|
check('exhausted_trial_grid', len(trial_rows) == maximum_trials)
|
|
else:
|
|
last = trial_rows[-1]
|
|
for key, value in {'accepted_alpha': float(last['alpha']), 'accepted_norm': float(last['selected_norm']),
|
|
'accepted_beta': float(last['selected_beta'])}.items(): check(key, same_number(summary[key], value, value))
|
|
for key, value in {'attempts': attempts, 'aux_iterations': iterations, 'deferred_attempts': deferred}.items():
|
|
check(key, summary[key] == value)
|
|
check('operation_time_total', close(summary['search_operation_seconds'], operation_seconds, operation_seconds))
|
|
check('wall_time_scope', math.isfinite(summary['search_wall_seconds']) and summary['search_wall_seconds'] + 1.e-6 >= operation_seconds)
|
|
physical_observations = inspect_physical(folder, physical_trials, metadata, check)
|
|
check('no_unexplained_vectors', {path.name for pattern in (P + '*.txt', 'coupled_trial_*.txt') for path in folder.glob(pattern)} ==
|
|
{name for name in required if name.endswith('.txt') and name != 'metadata.txt'})
|
|
passed = all(item['passed'] for item in checks)
|
|
return sanitize({'passed': passed, 'observations_valid': passed, 'required_artifacts': sorted(required), 'checks': checks,
|
|
'summary': summary, 'metadata': metadata, 'trials': outcomes if passed else [],
|
|
'physical_observations': physical_observations if passed else [],
|
|
'scope': 'Sequential frozen search with one shared auxiliary-solve budget. Saved-vector arithmetic, not independent FEM or Newton convergence.'})
|
|
|
|
|
|
def inspect_campaign(campaign):
|
|
campaign = Path(campaign).resolve(); checks, results = [], {}
|
|
def check(name, value): checks.append({'check': name, 'passed': bool(value)})
|
|
try:
|
|
provenance = json.loads((campaign / 'provenance.json').read_text())
|
|
cases = provenance['cases']
|
|
check('executable_hash', digest(campaign / 'plateau_diagnostic') == provenance['binary_sha256'])
|
|
check('mesh_hash', digest(Path(provenance['mesh'])) == provenance['mesh_sha256'])
|
|
check('case_manifest', json.loads((campaign / 'cases.json').read_text()) == cases)
|
|
check('unique_cases', bool(cases) and len(cases) == len({case['id'] for case in cases}))
|
|
for case in cases:
|
|
name, arguments = case['id'], case['arguments']; folder = campaign / name
|
|
record = json.loads((campaign / (name + '_run.json')).read_text())
|
|
check(name + ':completed', record.get('exit_code') == 0 and bool(record.get('finished_utc')) and not record.get('timed_out', False))
|
|
check(name + ':command', record.get('id') == name and record.get('command') ==
|
|
[str(campaign / 'plateau_diagnostic'), *arguments, '--mesh', provenance['mesh'], '--output', str(folder)])
|
|
expected_inputs = {}
|
|
for option, saved in (('--probe-state', 'base_physical'), ('--probe-direction', 'direction')):
|
|
path = Path(arguments[arguments.index(option) + 1])
|
|
path = path if path.is_absolute() else Path(provenance['source']) / path
|
|
expected_inputs[str(path.resolve())] = saved
|
|
check(name + ':required_input_hashes', expected_inputs.keys() <= record.get('input_state_hashes', {}).keys())
|
|
for path, value in record.get('input_state_hashes', {}).items(): check(name + ':input:' + path, digest(Path(path)) == value)
|
|
for filename, value in record.get('output_hashes', {}).items(): check(name + ':output:' + filename, digest(folder / filename) == value)
|
|
for path, saved in expected_inputs.items():
|
|
source, target = vector(Path(path)), vector(folder / (P + saved + '.txt'))
|
|
check(name + ':input_binding:' + saved, source == target if saved == 'direction' else near_vector(source, target, norm(source)))
|
|
result = inspect_case(folder); results[name] = result
|
|
check(name + ':required_output_hashes', set(result['required_artifacts']) <= record.get('output_hashes', {}).keys())
|
|
check(name + ':probe_scope', '--damped-coupled-probe' in arguments)
|
|
metadata = result.get('metadata', {})
|
|
for option, key in {'--index': 'n', '--angular-momentum': 'J', '--linear-tol': 'linear_tol',
|
|
'--coupled-tol': 'coupled_tolerance', '--coupled-iterations': 'coupled_iterations',
|
|
'--coupled-restart': 'coupled_restart', '--coupled-max-ratio': 'coupled_maximum_ratio',
|
|
'--coupled-beta-steps': 'coupled_beta_steps', '--coupled-beta-reduction': 'coupled_beta_reduction',
|
|
'--coupled-excess-ratio': 'coupled_excess_ratio', '--coupled-attempts': 'coupled_attempts_per_iteration',
|
|
'--gravity-tol': 'gravity_tolerance', '--gravity-iterations': 'gravity_iterations', '--gravity-restart': 'gravity_restart'}.items():
|
|
if option in arguments:
|
|
check(name + ':requested:' + key, key in metadata and float(metadata[key]) == float(arguments[arguments.index(option) + 1]))
|
|
for option, key in (('--coupled-search', 'coupled_search'), ('--strategy', 'strategy')):
|
|
if option in arguments: check(name + ':requested:' + key, metadata.get(key) == arguments[arguments.index(option) + 1])
|
|
if '--no-central' in arguments: check(name + ':central_constraint', metadata.get('central_phase') == '0')
|
|
passed = all(item['passed'] for item in checks) and all(result['passed'] for result in results.values())
|
|
if not all(item['passed'] for item in checks):
|
|
for result in results.values(): result.update(passed=False, observations_valid=False, trials=[], physical_observations=[])
|
|
return {'passed': passed, 'source_commit': provenance['commit'], 'checker_sha256': digest(Path(__file__)),
|
|
'checker_helpers_sha256': {name: digest(Path(__file__).with_name(name)) for name in
|
|
('check_coupled_trial.py', 'check_nonlinear_step.py')},
|
|
'provenance_sha256': digest(campaign / 'provenance.json'), 'provenance_checks': checks, 'cases': results}
|
|
except (OSError, ValueError, KeyError, TypeError, IndexError) as error:
|
|
for result in results.values(): result.update(passed=False, observations_valid=False, trials=[], physical_observations=[])
|
|
return {'passed': False, 'provenance_checks': checks + [{'check': 'complete_readable_campaign', 'passed': False, 'error': str(error)}], 'cases': results}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('campaign', type=Path); parser.add_argument('--output', required=True, type=Path)
|
|
args = parser.parse_args()
|
|
if args.output.resolve() == args.campaign.resolve() or args.campaign.resolve() in args.output.resolve().parents:
|
|
parser.error('Write the audit outside the immutable campaign.')
|
|
result = inspect_campaign(args.campaign)
|
|
args.output.write_text(json.dumps(result, indent=2, allow_nan=False) + '\n')
|
|
print('PASS' if result['passed'] else 'FAIL', args.output)
|
|
return 0 if result['passed'] else 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|