存档求解器回归基线与当前改动
纳管 AMESim 对齐基线、发布锁、回归测试及当前物理门禁调整。 更新日志仅记录已完成成果,并注明当前 HEAD 尚待真实 production 复跑与远端 workflow 验证。
This commit is contained in:
1 parent
53f8601fec
commit
a8c733883c
32 files changed
+47241
-108
No files matched your search
@@ -31,6 +31,14 @@ from time import monotonic, perf_counter, process_time
|
||||
from typing import Any
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from app.simulation.physical_state_v21 import (
|
||||
PhysicalStateV21Error,
|
||||
evaluate_physical_state_v21,
|
||||
load_approved_golden as load_physical_state_v21_golden,
|
||||
physical_state_v21_applicable,
|
||||
project_physical_state_v21,
|
||||
)
|
||||
|
||||
try: # resource is unavailable on native Windows Python.
|
||||
import resource
|
||||
except ImportError: # pragma: no cover - Windows regression job
|
||||
@@ -456,6 +464,50 @@ def load_regression_manifest(path: Path | str) -> dict[str, object]:
|
||||
"Authoritative XML byte count does not match manifest source.bytes."
|
||||
)
|
||||
|
||||
reference_archive = source.get("referenceArchive")
|
||||
reference_archive_path: Path | None = None
|
||||
reference_archive_sha256: str | None = None
|
||||
if reference_archive is not None:
|
||||
if not isinstance(reference_archive, Mapping):
|
||||
raise RegressionManifestError(
|
||||
"Manifest source.referenceArchive must be an object when declared."
|
||||
)
|
||||
archive_path_value = reference_archive.get("path")
|
||||
archive_sha256 = reference_archive.get("sha256")
|
||||
archive_bytes = reference_archive.get("bytes")
|
||||
if (
|
||||
not isinstance(archive_path_value, str)
|
||||
or not archive_path_value
|
||||
or not _valid_sha256(archive_sha256)
|
||||
or not isinstance(archive_bytes, int)
|
||||
or archive_bytes <= 0
|
||||
or reference_archive.get("role") != "authoritativePhysicalBaseline"
|
||||
):
|
||||
raise RegressionManifestError(
|
||||
"Manifest AMESim reference archive identity or role is incomplete."
|
||||
)
|
||||
reference_archive_path = (repository_root / archive_path_value).resolve()
|
||||
if not reference_archive_path.is_relative_to(repository_root):
|
||||
raise RegressionManifestError(
|
||||
"AMESim reference archive must remain inside the repository."
|
||||
)
|
||||
try:
|
||||
archive_payload = reference_archive_path.read_bytes()
|
||||
except OSError as exc:
|
||||
raise RegressionManifestError(
|
||||
f"Could not read AMESim reference archive {reference_archive_path}: {exc}"
|
||||
) from exc
|
||||
if len(archive_payload) != archive_bytes:
|
||||
raise RegressionManifestError(
|
||||
"AMESim reference archive byte count mismatch."
|
||||
)
|
||||
actual_archive_sha256 = _sha256(archive_payload)
|
||||
if actual_archive_sha256 != archive_sha256:
|
||||
raise RegressionManifestError(
|
||||
"AMESim reference archive hash mismatch."
|
||||
)
|
||||
reference_archive_sha256 = str(archive_sha256)
|
||||
|
||||
companion = source.get("companionProject")
|
||||
companion_path: Path | None = None
|
||||
if companion is not None:
|
||||
@@ -627,6 +679,28 @@ def load_regression_manifest(path: Path | str) -> dict[str, object]:
|
||||
correctness = manifest.get("correctness")
|
||||
if not isinstance(correctness, dict):
|
||||
raise RegressionManifestError("Manifest correctness must be an object.")
|
||||
python_golden_role = correctness.get("pythonGoldenRole", "acceptanceGate")
|
||||
if python_golden_role not in {
|
||||
"acceptanceGate",
|
||||
"determinismDiagnosticOnly",
|
||||
}:
|
||||
raise RegressionManifestError(
|
||||
"correctness.pythonGoldenRole must be acceptanceGate or "
|
||||
"determinismDiagnosticOnly."
|
||||
)
|
||||
physical_baseline_authority = correctness.get("physicalBaselineAuthority")
|
||||
compare_amesim_on_every_run = correctness.get(
|
||||
"compareAmesimOnEveryRun", False
|
||||
)
|
||||
if physical_baseline_authority is not None and (
|
||||
physical_baseline_authority != "amesim"
|
||||
or compare_amesim_on_every_run is not True
|
||||
or reference_archive_path is None
|
||||
):
|
||||
raise RegressionManifestError(
|
||||
"AMESim physical authority requires a validated reference archive "
|
||||
"and compareAmesimOnEveryRun=true."
|
||||
)
|
||||
state_relative_tolerance = _finite_nonnegative(
|
||||
correctness.get("stateRelativeTolerance"),
|
||||
field="correctness.stateRelativeTolerance",
|
||||
@@ -641,6 +715,9 @@ def load_regression_manifest(path: Path | str) -> dict[str, object]:
|
||||
)
|
||||
|
||||
loaded_goldens: dict[str, dict[str, dict[str, object]]] = {}
|
||||
loaded_physical_state_v21_goldens: dict[
|
||||
str, dict[str, dict[str, object]]
|
||||
] = {}
|
||||
for case_id in sequence:
|
||||
variant = variants[case_id]
|
||||
assert isinstance(variant, dict)
|
||||
@@ -654,6 +731,14 @@ def load_regression_manifest(path: Path | str) -> dict[str, object]:
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} has an invalid golden lane reference."
|
||||
)
|
||||
if python_golden_role == "determinismDiagnosticOnly" and (
|
||||
reference.get("role") != "pythonDeterminismRegression"
|
||||
or reference.get("affectsPhysicalCorrectness") is not False
|
||||
):
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} Python golden must be marked as a "
|
||||
"non-physical determinism regression."
|
||||
)
|
||||
golden_path_value = reference.get("path")
|
||||
golden_sha256 = reference.get("sha256")
|
||||
if (
|
||||
@@ -704,6 +789,95 @@ def load_regression_manifest(path: Path | str) -> dict[str, object]:
|
||||
)
|
||||
loaded_goldens.setdefault(case_id, {})[lane_name] = golden
|
||||
|
||||
raw_physical_state_v21_goldens = variant.get(
|
||||
"physicalStateV21Goldens", {}
|
||||
)
|
||||
if not isinstance(raw_physical_state_v21_goldens, dict):
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} physicalStateV21Goldens must be an object "
|
||||
"keyed by lane."
|
||||
)
|
||||
for lane_name, reference in raw_physical_state_v21_goldens.items():
|
||||
if lane_name not in lanes or not isinstance(reference, Mapping):
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} has an invalid physical-state-v2.1 "
|
||||
"golden lane reference."
|
||||
)
|
||||
if physical_baseline_authority == "amesim" and (
|
||||
reference.get("role") != "amesimPhysicalBaseline"
|
||||
or reference.get("compareOnEveryRun") is not True
|
||||
):
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} physical-state reference must be marked "
|
||||
"as the per-run AMESim physical baseline."
|
||||
)
|
||||
golden_path_value = reference.get("path")
|
||||
golden_sha256 = reference.get("sha256")
|
||||
if (
|
||||
not isinstance(golden_path_value, str)
|
||||
or not golden_path_value
|
||||
or not _valid_sha256(golden_sha256)
|
||||
):
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} physical-state-v2.1 golden reference "
|
||||
"is incomplete."
|
||||
)
|
||||
golden_path = (repository_root / golden_path_value).resolve()
|
||||
if not golden_path.is_relative_to(repository_root):
|
||||
raise RegressionManifestError(
|
||||
"Physical-state-v2.1 golden must remain inside the repository."
|
||||
)
|
||||
try:
|
||||
physical_state_golden = load_physical_state_v21_golden(golden_path)
|
||||
except (OSError, PhysicalStateV21Error) as exc:
|
||||
raise RegressionManifestError(
|
||||
f"Could not load physical-state-v2.1 golden {golden_path}: {exc}"
|
||||
) from exc
|
||||
if physical_state_golden.get("_sha256") != golden_sha256:
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} physical-state-v2.1 golden hash mismatch."
|
||||
)
|
||||
if (
|
||||
physical_state_golden.get("caseId") != case_id
|
||||
or physical_state_golden.get("lane") != lane_name
|
||||
or physical_state_golden.get("sourceXmlSha256") != expected_sha256
|
||||
):
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} physical-state-v2.1 golden identity "
|
||||
"does not match the manifest."
|
||||
)
|
||||
physical_provenance = physical_state_golden.get("provenance")
|
||||
physical_amesim = (
|
||||
physical_provenance.get("amesim")
|
||||
if isinstance(physical_provenance, Mapping)
|
||||
else None
|
||||
)
|
||||
if physical_baseline_authority == "amesim" and (
|
||||
not isinstance(physical_amesim, Mapping)
|
||||
or physical_amesim.get("archiveSha256")
|
||||
!= reference_archive_sha256
|
||||
):
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} AMESim baseline provenance does not "
|
||||
"match source.referenceArchive."
|
||||
)
|
||||
expected_times = [float(value) for value in variant["checkpointTimes"]]
|
||||
physical_state_checkpoints = physical_state_golden.get("checkpoints")
|
||||
assert isinstance(physical_state_checkpoints, list)
|
||||
golden_times = [
|
||||
float(checkpoint["requestedTime"])
|
||||
for checkpoint in physical_state_checkpoints
|
||||
if isinstance(checkpoint, Mapping)
|
||||
]
|
||||
if expected_times != golden_times:
|
||||
raise RegressionManifestError(
|
||||
f"Variant {case_id!r} physical-state-v2.1 golden checkpoint "
|
||||
"times differ from manifest."
|
||||
)
|
||||
loaded_physical_state_v21_goldens.setdefault(case_id, {})[
|
||||
lane_name
|
||||
] = physical_state_golden
|
||||
|
||||
_finite_positive(
|
||||
execution.get("predictionSafetyFactor", 1.0),
|
||||
field="execution.predictionSafetyFactor",
|
||||
@@ -720,10 +894,14 @@ def load_regression_manifest(path: Path | str) -> dict[str, object]:
|
||||
manifest["_manifestPath"] = str(manifest_path)
|
||||
manifest["_repositoryRoot"] = str(repository_root)
|
||||
manifest["_sourcePath"] = str(source_path)
|
||||
manifest["_referenceArchivePath"] = (
|
||||
str(reference_archive_path) if reference_archive_path is not None else None
|
||||
)
|
||||
manifest["_companionPath"] = (
|
||||
str(companion_path) if companion_path is not None else None
|
||||
)
|
||||
manifest["_goldens"] = loaded_goldens
|
||||
manifest["_physicalStateV21Goldens"] = loaded_physical_state_v21_goldens
|
||||
return manifest
|
||||
|
||||
|
||||
@@ -1105,7 +1283,7 @@ def summarize_simulation_result(
|
||||
"eventTrace": event_trace,
|
||||
"comparisonMode": "numericTolerance",
|
||||
}
|
||||
return {
|
||||
summary = {
|
||||
"success": bool(result.get("success")),
|
||||
"status": result.get("status"),
|
||||
"partial": bool(result.get("partial")),
|
||||
@@ -1126,6 +1304,13 @@ def summarize_simulation_result(
|
||||
"diagnostics": dict(diagnostic_mapping),
|
||||
"eventTrace": event_trace,
|
||||
}
|
||||
if physical_state_v21_applicable(result):
|
||||
summary["physicalStateV21"] = project_physical_state_v21(
|
||||
result,
|
||||
checkpoint_times=checkpoint_times,
|
||||
sample_step=sample_step,
|
||||
)
|
||||
return summary
|
||||
|
||||
|
||||
def _worker_control_listener(cancel_event: threading.Event) -> None:
|
||||
@@ -1666,6 +1851,77 @@ def evaluate_regression_golden(
|
||||
return audit
|
||||
|
||||
|
||||
def evaluate_physical_state_v21_golden(
|
||||
summary: Mapping[str, object] | None,
|
||||
golden: Mapping[str, object] | None,
|
||||
) -> dict[str, object]:
|
||||
"""Evaluate the algebraic/discrete v2.1 contract when a manifest pins it."""
|
||||
|
||||
if golden is None:
|
||||
return {
|
||||
"configured": False,
|
||||
"evaluated": False,
|
||||
"passed": None,
|
||||
"issues": [],
|
||||
}
|
||||
provenance = golden.get("provenance")
|
||||
amesim_provenance = (
|
||||
provenance.get("amesim") if isinstance(provenance, Mapping) else None
|
||||
)
|
||||
alignment = golden.get("amesimAlignmentAtGeneration")
|
||||
audit: dict[str, object] = {
|
||||
"configured": True,
|
||||
"evaluated": False,
|
||||
"passed": False,
|
||||
"goldenId": golden.get("id"),
|
||||
"goldenPath": golden.get("_path"),
|
||||
"goldenSha256": golden.get("_sha256"),
|
||||
"amesimArchiveSha256": (
|
||||
amesim_provenance.get("archiveSha256")
|
||||
if isinstance(amesim_provenance, Mapping)
|
||||
else None
|
||||
),
|
||||
"amesimAlignmentAtGenerationPassed": (
|
||||
alignment.get("passed") if isinstance(alignment, Mapping) else None
|
||||
),
|
||||
"issues": [],
|
||||
"metrics": [],
|
||||
}
|
||||
if summary is None:
|
||||
audit["issues"] = ["missingSummaryForPhysicalStateV21Golden"]
|
||||
return audit
|
||||
contract = summary.get("physicalStateV21")
|
||||
if not isinstance(contract, Mapping):
|
||||
audit["issues"] = ["missingPhysicalStateV21Contract"]
|
||||
return audit
|
||||
audit["evaluated"] = True
|
||||
try:
|
||||
evaluation = evaluate_physical_state_v21(contract, golden)
|
||||
except PhysicalStateV21Error as exc:
|
||||
audit["issues"] = ["invalidPhysicalStateV21Contract"]
|
||||
audit["error"] = str(exc)
|
||||
return audit
|
||||
audit.update(evaluation)
|
||||
audit.update(
|
||||
{
|
||||
"configured": True,
|
||||
"evaluated": True,
|
||||
"goldenId": golden.get("id"),
|
||||
"goldenPath": golden.get("_path"),
|
||||
"goldenSha256": golden.get("_sha256"),
|
||||
"amesimArchiveSha256": (
|
||||
amesim_provenance.get("archiveSha256")
|
||||
if isinstance(amesim_provenance, Mapping)
|
||||
else None
|
||||
),
|
||||
"amesimAlignmentAtGenerationPassed": (
|
||||
alignment.get("passed") if isinstance(alignment, Mapping) else None
|
||||
),
|
||||
}
|
||||
)
|
||||
return audit
|
||||
|
||||
|
||||
def _case_correctness_issues(
|
||||
result: Mapping[str, object],
|
||||
*,
|
||||
@@ -1827,8 +2083,20 @@ def _case_correctness_issues(
|
||||
if isinstance(golden_evaluation, Mapping):
|
||||
raw_golden_issues = golden_evaluation.get("issues")
|
||||
if isinstance(raw_golden_issues, list):
|
||||
python_golden_role = correctness.get(
|
||||
"pythonGoldenRole", "acceptanceGate"
|
||||
)
|
||||
accepted_issues = (
|
||||
raw_golden_issues
|
||||
if python_golden_role == "acceptanceGate"
|
||||
else [
|
||||
issue
|
||||
for issue in raw_golden_issues
|
||||
if issue in {"missingOutputContract", "outputContractMismatch"}
|
||||
]
|
||||
)
|
||||
issues.extend(
|
||||
str(issue) for issue in raw_golden_issues if isinstance(issue, str)
|
||||
str(issue) for issue in accepted_issues if isinstance(issue, str)
|
||||
)
|
||||
return tuple(dict.fromkeys(issues))
|
||||
|
||||
@@ -1912,6 +2180,10 @@ def run_regression_suite(
|
||||
assert isinstance(correctness, dict)
|
||||
loaded_goldens = manifest.get("_goldens", {})
|
||||
assert isinstance(loaded_goldens, dict)
|
||||
loaded_physical_state_v21_goldens = manifest.get(
|
||||
"_physicalStateV21Goldens", {}
|
||||
)
|
||||
assert isinstance(loaded_physical_state_v21_goldens, dict)
|
||||
|
||||
case_reports: list[dict[str, object]] = []
|
||||
predecessor_completed = True
|
||||
@@ -1988,6 +2260,45 @@ def run_regression_suite(
|
||||
summary if isinstance(summary, Mapping) else None,
|
||||
golden if isinstance(golden, Mapping) else None,
|
||||
)
|
||||
golden_evaluation["role"] = correctness.get(
|
||||
"pythonGoldenRole", "acceptanceGate"
|
||||
)
|
||||
golden_evaluation["affectsPhysicalCorrectness"] = (
|
||||
correctness.get("pythonGoldenRole", "acceptanceGate")
|
||||
== "acceptanceGate"
|
||||
)
|
||||
raw_python_golden_issues = golden_evaluation.get("issues")
|
||||
golden_evaluation["acceptanceIssues"] = (
|
||||
list(raw_python_golden_issues)
|
||||
if correctness.get("pythonGoldenRole", "acceptanceGate")
|
||||
== "acceptanceGate"
|
||||
and isinstance(raw_python_golden_issues, list)
|
||||
else [
|
||||
issue
|
||||
for issue in (
|
||||
raw_python_golden_issues
|
||||
if isinstance(raw_python_golden_issues, list)
|
||||
else []
|
||||
)
|
||||
if issue in {"missingOutputContract", "outputContractMismatch"}
|
||||
]
|
||||
)
|
||||
case_physical_state_v21_goldens = loaded_physical_state_v21_goldens.get(
|
||||
case_id, {}
|
||||
)
|
||||
physical_state_v21_golden = (
|
||||
case_physical_state_v21_goldens.get(lane)
|
||||
if isinstance(case_physical_state_v21_goldens, Mapping)
|
||||
else None
|
||||
)
|
||||
physical_state_v21_evaluation = evaluate_physical_state_v21_golden(
|
||||
summary if isinstance(summary, Mapping) else None,
|
||||
(
|
||||
physical_state_v21_golden
|
||||
if isinstance(physical_state_v21_golden, Mapping)
|
||||
else None
|
||||
),
|
||||
)
|
||||
correctness_issues = (
|
||||
_case_correctness_issues(
|
||||
result,
|
||||
@@ -1998,6 +2309,21 @@ def run_regression_suite(
|
||||
if solver_completed
|
||||
else ()
|
||||
)
|
||||
if solver_completed:
|
||||
raw_v21_issues = physical_state_v21_evaluation.get("issues")
|
||||
if isinstance(raw_v21_issues, list):
|
||||
correctness_issues = tuple(
|
||||
dict.fromkeys(
|
||||
(
|
||||
*correctness_issues,
|
||||
*(
|
||||
str(issue)
|
||||
for issue in raw_v21_issues
|
||||
if isinstance(issue, str)
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
report = {
|
||||
"caseId": case_id,
|
||||
"stopTime": stop_time,
|
||||
@@ -2016,6 +2342,7 @@ def run_regression_suite(
|
||||
"passed": solver_completed and not correctness_issues,
|
||||
"issues": list(correctness_issues),
|
||||
"regressionGolden": golden_evaluation,
|
||||
"physicalStateV21Golden": physical_state_v21_evaluation,
|
||||
},
|
||||
}
|
||||
if solver_completed and correctness_issues:
|
||||
|
||||
Reference in new issue
Block a user