Preserve all trial preparation and disposition counts in reports

This commit is contained in:
2026-09-19 12:46:04 -04:00
parent e054c7464c
commit c0a496e9d6

View File

@@ -151,6 +151,8 @@ def build_rows(audits):
for key in ("normalized_unbordered_residual", "central_border", "normalized_border_action"):
row["final_" + key] = finite(physical18[key], "final " + key, nonnegative=key != "central_border")
line_search = case["line_search"]
row["line_search_counts"] = {key: line_search[key] for key in
("total_trials", "counts_by_source", "counts_by_disposition", "source_disposition_counts")}
for key in ("pressure_force_rejections", "residual_metric_rejections"):
row[key] = finite(line_search[key], key, True)
row["minimum_accepted_alpha"] = line_search["minimum_accepted_alpha"]
@@ -178,6 +180,24 @@ def build_rows(audits):
return frozen, coupled
def trial_stage_rows(coupled):
table = []
for row in coupled:
counts = row["line_search_counts"]
total = 0
for stage in counts["source_disposition_counts"]:
count = finite(stage["count"], "stage count", True)
require(count > 0 and int(count) == count, "Invalid integer stage count")
code = finite(stage["disposition_code"], "disposition code", True)
require(int(code) == code and code <= 5, "Invalid disposition code")
total += count
table.append({key: row[key] for key in ("case", "n", "J", "density_degree", "campaign")} |
{"source": stage["source"], "disposition": stage["disposition"], "disposition_code": code,
"rejected": code != 0, "count": count})
require(total == counts["total_trials"], "Joint stage counts do not cover all recorded trials")
return table
def comparisons(frozen, coupled):
result = []
for kind, table in (("frozen", frozen), ("coupled", coupled)):
@@ -315,6 +335,20 @@ def plot_figure(frozen, coupled, output):
class Tests(unittest.TestCase):
def test_distinct_preparation_stage_counts(self):
row = {"case": "sample", "n": 3., "J": 0., "density_degree": 4, "campaign": "recorded",
"line_search_counts": {"total_trials": 4, "source_disposition_counts": [
{"source": "barotropic closure", "disposition": "inadmissible_state", "disposition_code": 1, "count": 2},
{"source": "pressure force", "disposition": "inadmissible_state", "disposition_code": 1, "count": 1},
{"source": "", "disposition": "accepted", "disposition_code": 0, "count": 1}]}}
table = trial_stage_rows([row])
self.assertEqual({r["source"]: r["count"] for r in table}, {"barotropic closure": 2, "pressure force": 1, "": 1})
self.assertTrue(table[0]["rejected"])
self.assertFalse(table[-1]["rejected"])
row["line_search_counts"]["total_trials"] = 5
with self.assertRaisesRegex(ValueError, "cover all recorded trials"):
trial_stage_rows([row])
def test_cross_audit_non_density_layout(self):
def case(density_size, gravity_size=23):
return {"n": 1.5, "J": 0., "block_sizes": {"value/density": density_size,
@@ -385,7 +419,13 @@ class Tests(unittest.TestCase):
"final_residual": .01, "threshold": 1e-8, "accepted_steps": 1, "linear_iterations": 7,
"iteration_seconds": 10., "iterations": [{"linear_seconds": 6.}]},
"observer_timings": {"observer_total": 200.}, "run": {"seconds": 215., "utc_minus_monotonic_seconds": .01},
"line_search": {"pressure_force_rejections": 4, "residual_metric_rejections": 1, "minimum_accepted_alpha": 1/32},
"line_search": {"pressure_force_rejections": 4, "residual_metric_rejections": 1, "minimum_accepted_alpha": 1/32,
"total_trials": 6, "counts_by_source": {"pressure force": 4, "residual metric": 1, "": 1},
"counts_by_disposition": {"inadmissible_state": 4, "insufficient_decrease": 1, "accepted": 1},
"source_disposition_counts": [
{"source": "pressure force", "disposition": "inadmissible_state", "disposition_code": 1, "count": 4},
{"source": "residual metric", "disposition": "insufficient_decrease", "disposition_code": 4, "count": 1},
{"source": "", "disposition": "accepted", "disposition_code": 0, "count": 1}]},
"physical": {"14": {"metrics": metrics}, "18": {"metrics": metrics}}}
audit = {"artifact_passed": True, "campaigns": {"/recorded/campaign": {
"producer_commit": "test", "mesh_sha256": "test", "cases": {"sample": case}}}}
@@ -405,6 +445,8 @@ class Tests(unittest.TestCase):
self.assertEqual(row["pressure_force_rejections"], 4)
self.assertEqual(row["residual_metric_rejections"], 1)
self.assertEqual(row["minimum_accepted_alpha"], 1/32)
self.assertEqual(row["line_search_counts"], {key: case["line_search"][key] for key in
("total_trials", "counts_by_source", "counts_by_disposition", "source_disposition_counts")})
def main():
@@ -426,6 +468,7 @@ def main():
frozen, coupled = build_rows(inputs)
require(frozen or coupled, "No cases in input audits")
relative = comparisons(frozen, coupled)
stages = trial_stage_rows(coupled)
args.output.mkdir(parents=True, exist_ok=False)
write_csv(args.output / "frozen_projection.csv", frozen, ["case", "n", "J", "density_degree", "density_dofs",
"negative_mass_over_abs_mass", "negative_volume_fraction", "negative_classification", "negative_mass_over_uncertainty_scale",
@@ -439,6 +482,8 @@ def main():
"h_energy_virial_q14", "h_force_virial_q14", "negative_mass_over_abs_mass", "negative_volume_fraction",
"negative_classification", "eos_l2_q30", "eos_rms_q30"])
write_csv(args.output / "relative_to_Q2.csv", relative)
write_csv(args.output / "trial_stages.csv", stages,
["case", "n", "J", "density_degree", "campaign", "source", "disposition", "disposition_code", "rejected", "count"])
if not args.tables_only:
plot_figure(frozen, coupled, args.output)
digest = lambda path: hashlib.sha256(path.read_bytes()).hexdigest()
@@ -452,11 +497,12 @@ def main():
"Newton seconds sum reported nonlinear-iteration times, excluding observer and setup costs; total elapsed time is kept separately.",
"Final unbordered residual, signed central border and normalized border action come from q18 physical diagnostics; bordered convergence does not imply that these vanish.",
"Pressure-force and residual-metric rejection counts retain the recorded stage distinction; residual-metric counts include any nonfinite metric failures as well as insufficient decrease.",
"All source/disposition counts, including accepted trials, are preserved in trial_stages.csv and JSON; preparation-stage counts alone do not establish where a candidate would fail if processing continued.",
"Ratios to Q2 compare recorded endpoints and do not imply equal convergence or error tolerances were attained.",
"Undefined values are null/blank; zero is kept exactly. No logarithmic plot floor is inserted.",
"Uncertain-only negative observations retain exact table values and appear as separate plot annotations; resolved trends do not connect through them. Colors are stable by (n,J).",
"P(h) virial milestones do not establish pointwise density validity; density-based virials are retained separately."],
"frozen_projection": frozen, "coupled_outcomes": coupled, "relative_to_Q2": relative,
"frozen_projection": frozen, "coupled_outcomes": coupled, "relative_to_Q2": relative, "trial_stages": stages,
"artifacts_sha256": {path.name: digest(path) for path in args.output.iterdir() if path.is_file()}}
(args.output / "summary.json").write_text(json.dumps(summary, indent=2, allow_nan=False) + "\n")
print("Wrote", args.output)