diff --git a/experiments/check_numerical_strategies.py b/experiments/check_numerical_strategies.py index 6cf8a91..4ac8151 100644 --- a/experiments/check_numerical_strategies.py +++ b/experiments/check_numerical_strategies.py @@ -246,7 +246,8 @@ class Assessment: continue alpha, error, relative, floor = (number(row, key) for key in ("alpha", "difference_quotient_error", "relative_error", "residual_subtraction_roundoff_scale")) valid.append((alpha, error, relative, floor)) - self.check(f"reduced_fd_{alpha:g}_finite", all(nonnegative(x) for x in (error, relative, floor))) + self.check(f"reduced_fd_{alpha:g}_finite", all(nonnegative(x) for x in (error, floor)) and + (nonnegative(relative) or abs(action) <= roundoff(action))) self.check(f"reduced_fd_{alpha:g}_completed_sides", all(nonnegative(number(row, key)) and number(row, key) <= tolerance for key in ("plus_gravity_norm", "minus_gravity_norm"))) if error > 20.0 * floor: reliable.append((alpha, error)) @@ -254,7 +255,8 @@ class Assessment: if not valid: self.incomplete("reduced_central_difference_available", reason="No pair completed on both sides") return - self.check("reduced_central_difference_accuracy", min(item[2] for item in valid) <= 1e-5, + self.check("reduced_central_difference_accuracy", any( + (nonnegative(item[2]) and item[2] <= 1e-5) or item[1] <= 20.0 * item[3] for item in valid), minimum_relative_error=min(item[2] for item in valid), tolerance=1e-5) reliable.sort(reverse=True) orders = [math.log(b[1] / a[1]) / math.log(b[0] / a[0]) for a, b in zip(reliable, reliable[1:]) if a[0] != b[0]] diff --git a/experiments/check_numerical_strategies_README.md b/experiments/check_numerical_strategies_README.md index 9d00108..d46fe5d 100644 --- a/experiments/check_numerical_strategies_README.md +++ b/experiments/check_numerical_strategies_README.md @@ -33,8 +33,9 @@ can vanish. Reduced probes must satisfy the freshly prepared gravity completion tolerance, the lifted gravity tangent gate, and agreement of the Schur action with the retained part of the full lifted action. Central differences must reach relative -error at most 1e-5 and show a resolved interval with order at least 1.5, or reach -the recorded residual-subtraction scale. A plateau above that scale is incomplete +error at most 1e-5 or an absolute error within the recorded residual-subtraction +scale. They must also show a resolved interval with order at least 1.5, or reach +that scale. Relative error is not meaningful for a null action. A plateau above that scale is incomplete evidence rather than an automatic claim of a bad Jacobian: the retained-residual amplification of the remaining gravity error has not been bounded. The recorded roundoff scales are heuristics, not rigorous forward-error bounds. diff --git a/experiments/test_check_numerical_strategies.py b/experiments/test_check_numerical_strategies.py index 5ada8bf..2cda822 100644 --- a/experiments/test_check_numerical_strategies.py +++ b/experiments/test_check_numerical_strategies.py @@ -140,6 +140,18 @@ class NumericalStrategyChecks(unittest.TestCase): report, code = assess(self.folder, required=("gravity",)) self.assertEqual(code, 2, report) + def test_null_reduced_action_uses_absolute_arithmetic_floor(self): + summary, _, _, _, differences = gravity_fixture(self.folder) + summary["reduced_action_norm"] = 0.0 + summary["lifted_retained_action_difference"] = 0.0 + for row in differences: + row["difference_quotient_error"] = row["residual_subtraction_roundoff_scale"] * .1 + row["relative_error"] = float("inf") + write_metrics(self.folder, "numerical_strategy_summary.csv", summary) + write_rows(self.folder, "numerical_strategy_reduced_derivative.csv", differences) + report, code = assess(self.folder) + self.assertEqual(code, 0, report) + def test_unfinished_restoration_record_is_incomplete(self): summary, _, _, _, _ = gravity_fixture(self.folder) del summary["accepted_state_change"]