136 lines
5.2 KiB
Bash
Executable file
136 lines
5.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
JOBS="${JOBS:-$(nproc)}"
|
|
PYTHON_BIN="${PYTHON_BIN:-${ROOT_DIR}/.venv/bin/python}"
|
|
REPORT=""
|
|
PASSTHRU=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--report)
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "--report requires a path" >&2
|
|
exit 2
|
|
fi
|
|
REPORT="$2"
|
|
PASSTHRU+=("$1" "$2")
|
|
shift 2
|
|
;;
|
|
--report=*)
|
|
REPORT="${1#--report=}"
|
|
PASSTHRU+=("$1")
|
|
shift
|
|
;;
|
|
--backend|--backend=*)
|
|
echo "verify_gpu_rans_solver.sh always requests --backend gpu; do not pass --backend" >&2
|
|
exit 2
|
|
;;
|
|
*)
|
|
PASSTHRU+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "${REPORT}" ]]; then
|
|
echo "--report is required so the full GPU RANS guard can inspect verifier evidence" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ ! -x "${PYTHON_BIN}" ]]; then
|
|
uv sync --dev
|
|
fi
|
|
|
|
if ! PYTHONPATH= "${PYTHON_BIN}" -c 'import pybind11, numpy, quadrants' >/dev/null 2>&1; then
|
|
uv sync --dev
|
|
fi
|
|
|
|
"${ROOT_DIR}/scripts/build_openfoam_airfrans_subset.sh" >/dev/null
|
|
|
|
set +u
|
|
source "${ROOT_DIR}/OpenFOAM-14/etc/bashrc" \
|
|
WM_MPLIB=Dummy \
|
|
ParaView_TYPE=none \
|
|
SCOTCH_TYPE=none \
|
|
ZOLTAN_TYPE=none
|
|
set -u
|
|
unset FOAM_SIGFPE
|
|
|
|
"${ROOT_DIR}/scripts/build_python_stepper.sh" >/dev/null
|
|
|
|
set +e
|
|
PYTHONPATH= "${PYTHON_BIN}" "${ROOT_DIR}/scripts/verify_airfrans_stepper.py" --backend gpu "${PASSTHRU[@]}"
|
|
VERIFY_STATUS=$?
|
|
set -e
|
|
|
|
PYTHONPATH= "${PYTHON_BIN}" - "${REPORT}" "${VERIFY_STATUS}" <<'PY'
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
report_path = Path(sys.argv[1])
|
|
verify_status = int(sys.argv[2])
|
|
required_fields = ("U", "p", "phi", "nut", "k", "omega")
|
|
required_modes = ("run_one", "split")
|
|
failures: list[str] = []
|
|
|
|
if not report_path.exists():
|
|
failures.append(f"report missing: {report_path}")
|
|
report = {}
|
|
else:
|
|
try:
|
|
report = json.loads(report_path.read_text())
|
|
except Exception as exc: # pragma: no cover - shell guard diagnostic
|
|
failures.append(f"report is not valid JSON: {exc}")
|
|
report = {}
|
|
|
|
|
|
def require(condition: bool, message: str) -> None:
|
|
if not condition:
|
|
failures.append(message)
|
|
|
|
|
|
backend = report.get("backend") if isinstance(report.get("backend"), dict) else {}
|
|
verifier_evidence = report.get("verifier_evidence") if isinstance(report.get("verifier_evidence"), dict) else {}
|
|
modes = report.get("modes") if isinstance(report.get("modes"), dict) else {}
|
|
provider = str(backend.get("provider") or "").lower()
|
|
device = str(backend.get("device") or "").lower()
|
|
|
|
require(verify_status == 0, f"underlying AirfRANS verifier exited {verify_status}")
|
|
require(report.get("status") == "passed", f"report.status is {report.get('status')!r}, expected 'passed'")
|
|
require(backend.get("requested") == "gpu", f"backend.requested is {backend.get('requested')!r}, expected 'gpu'")
|
|
require(backend.get("selected") == "gpu", f"backend.selected is {backend.get('selected')!r}, expected 'gpu'")
|
|
require("cpu" not in provider and provider not in {"foam_stepper_cpu", "openfoam"}, f"backend.provider looks CPU-backed: {backend.get('provider')!r}")
|
|
require(device not in {"host", "cpu"}, f"backend.device looks CPU-backed: {backend.get('device')!r}")
|
|
require(backend.get("counts_as_gpu_algorithm_progress") is not False, "backend explicitly says it does not count as GPU progress")
|
|
require(verifier_evidence.get("passed") is True, "verifier_evidence.passed is not true")
|
|
|
|
for mode_name in required_modes:
|
|
mode = modes.get(mode_name) if isinstance(modes.get(mode_name), dict) else {}
|
|
require(mode.get("enabled") is True, f"modes.{mode_name}.enabled is not true")
|
|
mode_backend = mode.get("backend") if isinstance(mode.get("backend"), dict) else backend
|
|
require(mode_backend.get("selected") == "gpu", f"modes.{mode_name}.backend.selected is not 'gpu'")
|
|
execution_path = str(mode.get("execution_path") or "").lower()
|
|
require("gpu" in execution_path, f"modes.{mode_name}.execution_path does not identify a GPU path: {mode.get('execution_path')!r}")
|
|
comparisons = mode.get("comparisons") if isinstance(mode.get("comparisons"), dict) else {}
|
|
for field in required_fields:
|
|
comparison = comparisons.get(field) if isinstance(comparisons.get(field), dict) else {}
|
|
require(comparison.get("allclose") is True, f"modes.{mode_name}.comparisons.{field}.allclose is not true")
|
|
require("actual_shape" in comparison or "shape" in comparison, f"modes.{mode_name}.comparisons.{field} has no actual shape evidence")
|
|
require("expected_shape" in comparison or "shape" in comparison, f"modes.{mode_name}.comparisons.{field} has no expected shape evidence")
|
|
require("max_abs" in comparison or "max_abs_error" in comparison, f"modes.{mode_name}.comparisons.{field} has no max error evidence")
|
|
|
|
if failures:
|
|
print("full GPU RANS solver guard failed:", file=sys.stderr)
|
|
for failure in failures:
|
|
print(f"- {failure}", file=sys.stderr)
|
|
print(f"report={report_path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print("full GPU RANS solver guard passed")
|
|
print(f"report={report_path}")
|
|
PY
|