openFOAM-RANS-to-GPU/scripts/verify_gpu_rans_solver.sh

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