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>
109 lines
5.1 KiB
PowerShell
109 lines
5.1 KiB
PowerShell
# Драйвер валидационной кампании для Windows. Делает ровно то же, что run_campaign.sh, и
|
||
# читает тот же scenarios.json — целевая машина Linux, а этот вариант нужен для локальной
|
||
# отладки обвязки.
|
||
#
|
||
# .\run_campaign.ps1 -DryRun
|
||
# .\run_campaign.ps1 -Calibrate
|
||
# .\run_campaign.ps1 -Smoke
|
||
# .\run_campaign.ps1 -Resume -Group A,B
|
||
# .\run_campaign.ps1 -Only cyl_re150 -BudgetHours 6
|
||
|
||
[CmdletBinding()]
|
||
param(
|
||
[switch]$DryRun,
|
||
[switch]$Resume,
|
||
[switch]$Calibrate,
|
||
[switch]$Smoke,
|
||
[string[]]$Group,
|
||
[string]$Only,
|
||
[double]$BudgetHours = 0,
|
||
[string]$Bin = "..\target\release\kbc2d.exe",
|
||
[string]$Scenarios = "scenarios.json",
|
||
[string]$Out = "out",
|
||
[double]$GpuMlups = 1200,
|
||
[double]$CpuMlups = 22
|
||
)
|
||
|
||
Set-Location $PSScriptRoot
|
||
if (-not (Test-Path $Bin)) { throw "не найден бинарь решателя: $Bin (собери cargo build --release)" }
|
||
if (-not (Test-Path $Scenarios)) { throw "не найден список сценариев: $Scenarios" }
|
||
|
||
if ($Calibrate) {
|
||
"Калибровка на этой машине (три коротких прогона)…"
|
||
foreach ($c in @(@(240,120,'gpu'), @(960,480,'gpu'), @(480,240,'cpu'))) {
|
||
$o = & $Bin --nx $c[0] --ny $c[1] --size 16 --refine 1 --steps 3000 `
|
||
--report-every 3000 --verbose full --backend $c[2] 2>$null
|
||
$m = ($o | Select-String 'MLUPS' | Select-Object -Last 1)
|
||
" $($c[0])x$($c[1]) на $($c[2]): $(if ($m) { ($m -replace '.*?([0-9.]+) MLUPS.*','$1') } else { 'не измерено' })"
|
||
}
|
||
""
|
||
"Подставь замеренное: .\run_campaign.ps1 -DryRun -GpuMlups <число> -CpuMlups <число>"
|
||
return
|
||
}
|
||
|
||
$runs = (Get-Content $Scenarios -Raw -Encoding UTF8 | ConvertFrom-Json).runs
|
||
if ($Group) { $g = $Group | ForEach-Object { $_.ToUpper() }; $runs = $runs | Where-Object { $g -contains $_.group.ToUpper() } }
|
||
if ($Only) { $runs = $runs | Where-Object { $_.id -like "*$Only*" } }
|
||
if ($Smoke) { $runs = $runs | Sort-Object cost | Select-Object -First 3 }
|
||
if (-not $runs) { "под выборку не попал ни один прогон"; return }
|
||
|
||
$totalCost = ($runs | Measure-Object -Property cost -Sum).Sum
|
||
$estH = $totalCost / ($GpuMlups * 1e6) / 3600
|
||
"прогонов: $($runs.Count) обновлений узлов: {0:e3} ≈ {1:F1} ч при $GpuMlups MLUPS" -f $totalCost, $estH
|
||
"результаты: $Out\<id>\"
|
||
""
|
||
|
||
if ($DryRun) {
|
||
"{0,-28} {1,-3} {2,-4} {3,12} {4,8} {5}" -f 'id','гр','бэк','шагов','часов','название'
|
||
foreach ($r in $runs) {
|
||
$m = if ($r.backend -eq 'cpu') { $CpuMlups } else { $GpuMlups }
|
||
"{0,-28} {1,-3} {2,-4} {3,12} {4,8:F2} {5}" -f $r.id, $r.group, $r.backend, $r.steps, ($r.cost/($m*1e6)/3600), $r.title
|
||
}
|
||
return
|
||
}
|
||
|
||
New-Item -ItemType Directory -Force $Out | Out-Null
|
||
$summary = Join-Path $Out 'summary.csv'
|
||
$log = Join-Path $Out 'campaign.log'
|
||
if (-not (Test-Path $summary)) { 'id,group,backend,status,seconds,steps,cost,title' | Out-File $summary -Encoding utf8 }
|
||
|
||
$started = Get-Date
|
||
foreach ($r in $runs) {
|
||
$dir = Join-Path $Out $r.id
|
||
if ($Resume -and (Test-Path (Join-Path $dir 'summary.json'))) { "· $($r.id) — уже посчитан, пропускаю"; continue }
|
||
if ($BudgetHours -gt 0 -and ((Get-Date) - $started).TotalHours -ge $BudgetHours) {
|
||
"бюджет $BudgetHours ч исчерпан, останавливаюсь"; break
|
||
}
|
||
New-Item -ItemType Directory -Force $dir | Out-Null
|
||
|
||
# относительные имена файлов из сценария кладём внутрь папки прогона
|
||
$argv = @()
|
||
for ($i = 0; $i -lt $r.args.Count; $i++) {
|
||
$v = $r.args[$i]
|
||
if ($i -gt 0 -and @('--gif','--xt','--case-csv') -contains $r.args[$i-1]) { $v = Join-Path $dir $v }
|
||
$argv += $v
|
||
}
|
||
$argv += @('--summary', (Join-Path $dir 'summary.json'),
|
||
'--csv', (Join-Path $dir 'series.csv'),
|
||
'--verbose','full','--report-every','5000')
|
||
|
||
"$Bin $($argv -join ' ')" | Out-File (Join-Path $dir 'cmd.txt') -Encoding utf8
|
||
"▶ $($r.id) ($($r.group), $($r.backend)) $($r.title)"
|
||
$t0 = Get-Date
|
||
& $Bin @argv 2>&1 | Out-File (Join-Path $dir 'log.txt') -Encoding utf8
|
||
$st = if ($LASTEXITCODE -eq 0) { 'ok' } else { 'fail' }
|
||
$dt = [int]((Get-Date) - $t0).TotalSeconds
|
||
if (Select-String -Path (Join-Path $dir 'log.txt') -Pattern 'РАЗВАЛИЛСЯ' -Quiet) { $st = 'blewup' }
|
||
$rep = Get-Content (Join-Path $dir 'log.txt') -Encoding utf8
|
||
$k = ($rep | Select-String 'ИТОГОВЫЙ ОТЧЁТ' | Select-Object -First 1).LineNumber
|
||
if ($k) { $rep[($k-1)..($rep.Count-1)] | Out-File (Join-Path $dir 'report.txt') -Encoding utf8 }
|
||
|
||
'{0},{1},{2},{3},{4},{5},{6},"{7}"' -f $r.id,$r.group,$r.backend,$st,$dt,$r.steps,$r.cost,$r.title |
|
||
Out-File $summary -Append -Encoding utf8
|
||
"$(Get-Date -Format o) $($r.id) $st ${dt}s" | Out-File $log -Append -Encoding utf8
|
||
" → $st за $dt с"
|
||
}
|
||
|
||
""
|
||
"готово. сводная таблица: $summary"
|