bench/ — список сценариев (порождается gen_scenarios.py, а не правится руками), драйверы под Linux и Windows, предполётная проверка и описание. Девять групп: эталоны первоисточников, цилиндр против литературы, модели стенки и субсеточность, профили крыла, сложная и множественная геометрия, границы домена, старт и время жизни, внутренние инварианты, сверхмелкие сетки до 4096×2048. Стоимость планируется в обновлениях узлов — единственной переносимой между машинами мере; в часы драйвер переводит её по фактическим MLUPS, которые замеряет на месте (--calibrate). Итого 3.76e14 обновлений, ≈90 часов на 4070 Ti. Гифки пишутся на всю длительность прогона в реальном времени, 10 кадр/с. Число кадров этим задано жёстко, поэтому размер регулируется только кадром: замерено 0.103 байта на пиксель после LZW, отсюда бюджет кадры×пиксели с нижней границей по ширине. Итог 4.4 ГБ, самый тяжёлый файл 226 МБ. preflight.py гоняет каждый сценарий на два шага. Окупился сразу: поймал, что вся группа сверхмелких сеток падала на пределе GPU, а девять прогонов группы F передавали --body-x дважды. Оба отказа проявились бы только на сервере, часов через двадцать после старта кампании. Docker: двухстадийная сборка, в образе libvulkan1 и vulkan-tools. NVIDIA Container Toolkit подкладывает Vulkan-ICD только при graphics в NVIDIA_DRIVER_CAPABILITIES — без него wgpu не увидит карту, поэтому capabilities прописаны в образе, а vulkaninfo лежит внутрь для проверки. ENTRYPOINT — драйвер кампании, CMD по умолчанию --dry-run, чтобы случайный docker run не запустил сточасовую задачу. Проверено локально: образ собирается, смоук проходит с монтированием результатов на хост, физика совпадает с хостовой до последней цифры, --backend gpu без карты отказывает явно. GPU-путь в контейнере проверяется только на машине с картой. В README поправлено разделение вкладов в продольную акустику: измерением 2×2 показано, что 74.9% → 1.1% даёт ОДНОРОДНЫЙ СТАРТ, причём при полностью выключенной губке, а губка снимает лишь остаток (1.12% → 0.83% при утроении длины). При старте из покоя губка не помогает совсем — у стоячей четвертьволновой моды там узел давления, где вязкость её не трогает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
7.1 KiB
Bash
151 lines
7.1 KiB
Bash
#!/usr/bin/env bash
|
||
# Драйвер валидационной кампании. Читает scenarios.json, гоняет прогоны по одному, кладёт
|
||
# логи, ряды, сводки и гифки каждого в собственную папку и ведёт общую таблицу.
|
||
#
|
||
# ./run_campaign.sh --dry-run смета: что и сколько будет считаться
|
||
# ./run_campaign.sh --calibrate замерить фактические MLUPS этой машины
|
||
# ./run_campaign.sh --smoke три коротких прогона: проверить обвязку
|
||
# ./run_campaign.sh --resume считать, пропуская уже готовое
|
||
# ./run_campaign.sh --group A,B только выбранные группы
|
||
# ./run_campaign.sh --only cyl_re150 по подстроке идентификатора
|
||
# ./run_campaign.sh --budget-hours 24 остановиться, когда время выйдет
|
||
#
|
||
# Прогон, который упал или развалился, помечается в сводке и НЕ останавливает кампанию:
|
||
# группы E и G специально ищут предел устойчивости.
|
||
|
||
set -u
|
||
cd "$(dirname "$0")"
|
||
|
||
BIN="${KBC2D_BIN:-../target/release/kbc2d}"
|
||
SCEN="${KBC2D_SCENARIOS:-scenarios.json}"
|
||
OUT="${KBC2D_OUT:-out}"
|
||
# ВНИМАНИЕ: имя GROUPS занято самим bash (список групп пользователя), присваивание в неё
|
||
# молча игнорируется, а "$GROUPS" под root разворачивается в 0 — и выборка съедает всю
|
||
# кампанию, не сказав ни слова. Отсюда GRP_SEL.
|
||
DRY=0; RESUME=0; CALIB=0; SMOKE=0; GRP_SEL=""; ONLY=""; BUDGET=""
|
||
GPU_MLUPS="${KBC2D_GPU_MLUPS:-1200}"
|
||
CPU_MLUPS="${KBC2D_CPU_MLUPS:-22}"
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--dry-run) DRY=1 ;;
|
||
--resume) RESUME=1 ;;
|
||
--calibrate) CALIB=1 ;;
|
||
--smoke) SMOKE=1 ;;
|
||
--group) GRP_SEL="$2"; shift ;;
|
||
--only) ONLY="$2"; shift ;;
|
||
--budget-hours) BUDGET="$2"; shift ;;
|
||
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||
*) echo "неизвестный ключ: $1" >&2; exit 2 ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
command -v python3 >/dev/null 2>&1 && PY=python3 || PY=python
|
||
[ -x "$BIN" ] || { echo "не найден бинарь решателя: $BIN (собери cargo build --release)" >&2; exit 1; }
|
||
[ -f "$SCEN" ] || { echo "не найден список сценариев: $SCEN" >&2; exit 1; }
|
||
|
||
# ── калибровка: три коротких прогона на месте, чтобы оценки в часах были не гаданием ──
|
||
if [ "$CALIB" = 1 ]; then
|
||
echo "Калибровка на этой машине (три коротких прогона)…"
|
||
for spec in "240 120 gpu" "960 480 gpu" "480 240 cpu"; do
|
||
set -- $spec
|
||
m=$("$BIN" --nx "$1" --ny "$2" --size 16 --refine 1 --steps 3000 --report-every 3000 \
|
||
--verbose full --backend "$3" 2>/dev/null | grep -o '[0-9.]* MLUPS' | tail -1)
|
||
echo " $1x$2 на $3: ${m:-не измерено}"
|
||
done
|
||
echo
|
||
echo "Подставь замеренное в переменные окружения и запусти смету:"
|
||
echo " KBC2D_GPU_MLUPS=<число> KBC2D_CPU_MLUPS=<число> ./run_campaign.sh --dry-run"
|
||
exit 0
|
||
fi
|
||
|
||
mkdir -p "$OUT"
|
||
SUMMARY="$OUT/summary.csv"
|
||
LOG="$OUT/campaign.log"
|
||
[ -f "$SUMMARY" ] || echo "id,group,backend,status,seconds,steps,cost,title" > "$SUMMARY"
|
||
|
||
# Выборку и порядок считает python: разбирать JSON башем — верный способ ошибиться.
|
||
PLAN=$("$PY" - "$SCEN" "$GRP_SEL" "$ONLY" "$SMOKE" <<'PYEOF'
|
||
import json, sys
|
||
scen, groups, only, smoke = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] == "1"
|
||
runs = json.load(open(scen, encoding="utf-8"))["runs"]
|
||
if groups:
|
||
keep = {g.strip().upper() for g in groups.split(",")}
|
||
runs = [r for r in runs if r["group"].upper() in keep]
|
||
if only:
|
||
runs = [r for r in runs if only in r["id"]]
|
||
if smoke:
|
||
# три самых дешёвых прогона: обвязку проверяем, а не физику
|
||
runs = sorted(runs, key=lambda r: r["cost"])[:3]
|
||
for r in runs:
|
||
print("\t".join([r["id"], r["group"], r["backend"], str(r["cost"]),
|
||
str(r["steps"]), r["title"], "\x1f".join(r["args"])]))
|
||
PYEOF
|
||
)
|
||
|
||
[ -n "$PLAN" ] || { echo "под выборку не попал ни один прогон"; exit 0; }
|
||
|
||
total_cost=0; n=0
|
||
while IFS=$'\t' read -r id grp bk cost steps title args; do
|
||
n=$((n + 1)); total_cost=$((total_cost + cost))
|
||
done <<< "$PLAN"
|
||
|
||
est_h=$("$PY" -c "
|
||
import sys
|
||
print('%.1f' % (float(sys.argv[1]) / (float(sys.argv[2]) * 1e6) / 3600))" "$total_cost" "$GPU_MLUPS")
|
||
|
||
echo "прогонов: $n обновлений узлов: $total_cost ≈ $est_h ч при $GPU_MLUPS MLUPS"
|
||
echo "результаты: $OUT/<id>/"
|
||
echo
|
||
|
||
if [ "$DRY" = 1 ]; then
|
||
printf '%-28s %-3s %-4s %12s %10s %s\n' id гр бэк шагов часов название
|
||
while IFS=$'\t' read -r id grp bk cost steps title args; do
|
||
m=$GPU_MLUPS; [ "$bk" = cpu ] && m=$CPU_MLUPS
|
||
h=$("$PY" -c "import sys;print('%.2f'%(float(sys.argv[1])/(float(sys.argv[2])*1e6)/3600))" "$cost" "$m")
|
||
printf '%-28s %-3s %-4s %12s %10s %s\n' "$id" "$grp" "$bk" "$steps" "$h" "$title"
|
||
done <<< "$PLAN"
|
||
exit 0
|
||
fi
|
||
|
||
started=$(date +%s)
|
||
while IFS=$'\t' read -r id grp bk cost steps title args; do
|
||
dir="$OUT/$id"
|
||
if [ "$RESUME" = 1 ] && [ -f "$dir/summary.json" ]; then
|
||
echo "· $id — уже посчитан, пропускаю"
|
||
continue
|
||
fi
|
||
if [ -n "$BUDGET" ]; then
|
||
spent=$(( ($(date +%s) - started) ))
|
||
limit=$("$PY" -c "import sys;print(int(float(sys.argv[1])*3600))" "$BUDGET")
|
||
[ "$spent" -ge "$limit" ] && { echo "бюджет $BUDGET ч исчерпан, останавливаюсь"; break; }
|
||
fi
|
||
|
||
mkdir -p "$dir"
|
||
# аргументы разделены символом 0x1f: в них есть пробелы (список тел) и запятые
|
||
IFS=$'\x1f' read -r -a ARGV <<< "$args"
|
||
ARGV+=(--summary "$dir/summary.json" --csv "$dir/series.csv" --verbose full --report-every 5000)
|
||
# относительные имена файлов из сценария кладём внутрь папки прогона
|
||
for i in "${!ARGV[@]}"; do
|
||
case "${ARGV[$((i-1))]:-}" in
|
||
--gif|--xt|--case-csv) ARGV[$i]="$dir/${ARGV[$i]}" ;;
|
||
esac
|
||
done
|
||
|
||
printf '%s\n' "$BIN ${ARGV[*]}" > "$dir/cmd.txt"
|
||
echo "▶ $id ($grp, $bk) $title"
|
||
t0=$(date +%s)
|
||
if "$BIN" "${ARGV[@]}" > "$dir/log.txt" 2>&1; then st=ok; else st=fail; fi
|
||
t1=$(date +%s); dt=$((t1 - t0))
|
||
grep -q "РАЗВАЛИЛСЯ" "$dir/log.txt" 2>/dev/null && st=blewup
|
||
sed -n '/ИТОГОВЫЙ ОТЧЁТ/,$p' "$dir/log.txt" > "$dir/report.txt" 2>/dev/null
|
||
|
||
printf '%s,%s,%s,%s,%s,%s,%s,"%s"\n' "$id" "$grp" "$bk" "$st" "$dt" "$steps" "$cost" "$title" >> "$SUMMARY"
|
||
echo "$(date -Is) $id $st ${dt}s" >> "$LOG"
|
||
echo " → $st за ${dt} с"
|
||
done <<< "$PLAN"
|
||
|
||
echo
|
||
echo "готово. сводная таблица: $SUMMARY"
|