Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4ae028154 | ||
|
|
70f66176bc | ||
|
|
750e8a483e | ||
|
|
6a03f463bc | ||
|
|
46dc6ba086 | ||
|
|
e1efc18274 |
@@ -0,0 +1,49 @@
|
||||
# DFG 2D-1 (Re 20) against the official reference intervals
|
||||
|
||||
`tests/validation/run_dfg_re20.py` runs the steady DFG 2D-1 case on the refined
|
||||
channel root (D40 root, D80 wide fine patch) and on the uniform D80 grid. Geometry,
|
||||
inflow, initial state, layout and observation are those of the pinned Re100 runner
|
||||
(`run_refined_dfg.py`, `re100_helpers.py`, both unchanged); only the lattice
|
||||
viscosity is set for Re 20 (root tau 0.56, fine 0.62, uniform 0.62, lattice speed 0.01).
|
||||
|
||||
Label: "compared against the official reference bands", not a full benchmark
|
||||
certification: the outlet is pressure-NEQ (`neq_extrap`), not do-nothing, and the
|
||||
inlet is local Zou-He.
|
||||
|
||||
## Quantities
|
||||
|
||||
- Cd = 2 Fx / (rho U^2 D), Cl = 2 Fy / (rho U^2 D), interval means over each sample.
|
||||
- p_diff = p(0.15, 0.2) - p(0.25, 0.2), linear extrapolation to the exact surface
|
||||
points (`re100_helpers.pressure_probes`). The helper returns dp / (rho U^2) in
|
||||
scaled units; p_diff = dp * 0.04 for U = 0.2 m/s, rho = 1. Both are reported.
|
||||
- Official intervals (Schaefer & Turek 1996): Cd [5.57, 5.59], Cl [0.0104, 0.0110],
|
||||
p_diff [0.1172, 0.1176].
|
||||
|
||||
## Bounds and steady rule (frozen before any run)
|
||||
|
||||
- End time t* = 60 (30 s physical): 240,000 refined root steps, 480,000 uniform steps.
|
||||
- One sample every t* = 0.5 (0.25 s) at the end of a synchronized root step; Cd and
|
||||
Cl are interval means. Trailing window t* 50..60 inclusive (21 samples).
|
||||
- Steady rule (our engineering choice, not official): in the window, for each of Cd,
|
||||
Cl, p_diff, max - min <= 10 % and |least-squares slope x window| <= 2 % of its
|
||||
official interval width (range 2e-3 / 6e-5 / 4e-5; slope 4e-4 / 1.2e-5 / 8e-6).
|
||||
Absolute limits, so the small Cl is never a divisor. Never relaxed after a run.
|
||||
- The verdict uses the window mean; the distance to the nearest interval edge is
|
||||
reported for each quantity (negative outside).
|
||||
- Force sign: fluid on cylinder, x along the inflow, y up from the bottom wall; Cd > 0
|
||||
and the official Cl > 0 (`cd_positive` is reported).
|
||||
- Not steady at t* = 60: status `NOT_STEADY`, no interval verdict. Health limits
|
||||
(Mach <= 0.2, rho in [0.5, 1.5]) stop the run with `UNHEALTHY`.
|
||||
- Steady: `WITHIN_INTERVALS` or `OUTSIDE_INTERVALS`, with the per-quantity verdict.
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
python tests/validation/run_dfg_re20.py --output <new-dir> --plan
|
||||
CUDA_VISIBLE_DEVICES=<n> python tests/validation/run_dfg_re20.py --case both \
|
||||
--collision SRT --output <new-dir> --max-wall-seconds <cap>
|
||||
```
|
||||
|
||||
The uniform D80 control separates method error from refinement error. `--plan` writes `plan.json` only. A GPU run writes `plan.json` and `result.json`
|
||||
(per case: lattice parameters, every sample, window ranges, status, verdict).
|
||||
Host contract tests: `tests/unit/test_dfg_re20.py`.
|
||||
@@ -0,0 +1,76 @@
|
||||
待 Grok 独立复核:750e8a4 本轮六例物理验证
|
||||
==========================================
|
||||
|
||||
固定候选 `750e8a483ec47dcf472e528edc1cf2936f0e7e83`,工作树 `/home/frank14f/CelerisLab-release-prep-20261020` 干净。六例全部完成、退出码 0、无超时;调度器和六个子进程均已消失,最终 `nvidia-smi` 无计算进程。206 个源码/测试/文档哈希与启动 manifest 一致,各例运行前后 source map 相同,原生结果与外部摘要一致;V2 原生源码捕获及 runner 哈希也一致。V4 采用外部 manifest 绑定,没有冒称 runner 内部具备同样 provenance。没有新实验、阈值变更、源码变更、main 合并或公开发布。
|
||||
|
||||
六例状态及进程墙钟秒数:V2 zero refined `periodic` 511.409,zero uniform `periodic` 325.254;fixed refined `driven_diagnostics_complete` 530.173,fixed uniform 同状态 333.016;V4 refined `NOT_STEADY` 315.714,uniform `NOT_STEADY` 114.183。墙钟包含初始化和观测开销,仅用于执行记录,不能作正式性能比较。
|
||||
|
||||
**V2 zero:MRT、unshifted,均达到 t*=300。** 每例 3480 条 history;refined 1.2M steps,uniform 2.4M steps。各提取 14 个 t*>250 的周期,末五周期全部原生周期门槛通过,各自与冻结参考比较通过。
|
||||
|
||||
| 周期指标 | refined | uniform | 原门槛 |
|
||||
|---|---:|---:|---:|
|
||||
| 末五周期范围 | 281.663875–298.232508 | 281.590556–298.153468 | 起点>=250,5 个连续周期 |
|
||||
| period min / max | 3.311073 / 3.316868 | 3.311314 / 3.314196 | 每周期严格在 (2,5) |
|
||||
| period ptp/mean | 0.00174873 | 0.000869972 | <=0.005 |
|
||||
| 末周期距最后力采样 | 1.757492 | 1.836532 | [0,5] |
|
||||
| 连续周期最大间隙 | 0 | 0 | <1e-8 |
|
||||
| 最小 Cl peak-to-peak | 2.003137 | 1.998667 | >=0.2 |
|
||||
| Cd_min / Cd_max 跨周期 range | 8.14323e-5 / 1.39714e-4 | 2.60406e-4 / 8.66420e-5 | 每项<=0.005 |
|
||||
| Cl_min / Cl_max 跨周期 range | 1.96279e-4 / 1.15290e-4 | 1.59490e-4 / 1.82477e-4 | 每项<=0.005 |
|
||||
| dp_min / dp_max 跨周期 range | 5.50774e-4 / 5.87194e-4 | 8.12699e-4 / 7.99311e-4 | 每项<=0.005 |
|
||||
| 周期质量均值 ptp/abs(mean) | 3.61945e-8 | 8.51505e-9 | <=1e-4 |
|
||||
| 最大周期均值 flux_relative | 6.35676e-6 | 5.22752e-5 | <=0.005 |
|
||||
|
||||
末周期实际值:refined period=3.312817588、St=0.301857852、Cd=[3.160863689,3.234990541]、Cl=[-1.019161308,0.984224152]、dp=[2.404928344,2.499337133];uniform period=3.311313790、St=0.301994937、Cd=[3.161468623,3.231458726]、Cl=[-1.016677997,0.982020469]、dp=[2.420541455,2.513318511]。
|
||||
|
||||
| 末周期参考误差 | refined | uniform | 原门槛 |
|
||||
|---|---:|---:|---:|
|
||||
| Cd_min / Cd_max 相对误差 | 0.00107520 / 0.00235425 | 0.000884026 / 0.00125993 | 每项<=0.02 |
|
||||
| Cl_min / Cl_max 绝对误差 | 0.00212432 / 0.00234646 | 0.00460763 / 0.00455014 | 每项<=0.03 |
|
||||
| dp_min / dp_max 相对误差 | 0.00638037 / 0.00306172 | 0.0000703422 / 0.00251519 | 每项<=0.02 |
|
||||
| St 相对误差 | 0.0000558054 | 0.000509970 | <=0.02 |
|
||||
| Cd / Cl / dp waveform RMS | 0.00472589 / 0.00216188 / 0.0117979 | 0.00317231 / 0.00337428 / 0.00382206 | 描述指标,不是 gate |
|
||||
|
||||
`matched_case_pass=false` 原样保留:四次 V2 调用均选择单 case,runner 只有 `--case both` 的两结果原生比较分支才赋值这一字段。它不等于 zero 的各自周期或参考比较失败,也不能由执行者自行改为 true;本轮没有运行该 paired-case 原生比较。fixed 没有官方 DFG 参考 verdict,完成执行不等于物理验收。
|
||||
|
||||
**Fixed Jet 健康与 CV。** 两例达到 t*=300,五个 250–300 诊断窗均完整。原健康规则为 physical leaf 数据及 impulse 有限、rho_min>=0.5、rho_max<=1.5、Mach<=0.2。所有 3480 条 history 均有限,观测数值如下。
|
||||
|
||||
| 指标 | refined | uniform |
|
||||
|---|---:|---:|
|
||||
| 全序列 rho min / max | 0.994218661 / 1.004163150 | 0.993391573 / 1.004678726 |
|
||||
| 全序列 max Mach | 0.0422925943 | 0.0421810197 |
|
||||
| mass t*=0.5 / t*=300 | 0.8931541481 / 0.8931214909 | 0.8936680602 / 0.8936524063 |
|
||||
| 上述端点质量相对变化 | -3.65640e-5 | -1.75164e-5 |
|
||||
| t*=290–300 mass ptp/mean | 8.40337e-5 | 6.31000e-5 |
|
||||
| 290–300 Cd / Cl 均值 | 3.184020005 / -0.580990812 | 3.182612828 / -0.582235721 |
|
||||
|
||||
CV 各 501 个端点,间距 t*=0.02,积分区间 290–300(root time 40000)。排除首端点之前的 impulse,只累加后 500 个区间;root reaction impulse refined=(254.721600438,-46.479264945),uniform=(254.609026243,-46.578857642)。两条矩形轮廓 refined 为 (48,44,160,120)/(44,36,180,128),uniform 的 x 坐标均加 0.25。以原式 `J_CV=-ΔP-∫outward_momentum_flux dt`,残差为 `J_CV-J_raw`,系数除以 `duration*0.5*U_root²*D_root=80`。这里是 wall+aperture 原始流体对物体反作用,不是已修正的 nozzle thrust。
|
||||
|
||||
| 动量残差系数 (x,y) | refined | uniform |
|
||||
|---|---|---|
|
||||
| 内轮廓、stride1 | (2.24958e-4,-1.00611e-4) | (1.78190e-4,-7.24811e-5) |
|
||||
| 外轮廓、stride1 | (7.88038e-5,-4.35879e-5) | (-8.30841e-5,-1.99246e-5) |
|
||||
| 外减内 | (-1.46155e-4,5.70231e-5) | (-2.61274e-4,5.25565e-5) |
|
||||
| stride2 相对 stride1 最大分量差 | 1.18312e-5 | 2.19402e-5 |
|
||||
| stride4 相对 stride1 最大分量差 | 4.44884e-5 | 6.26044e-5 |
|
||||
|
||||
CV 未定义验收门槛,以上仅为离线积分诊断。质量记录缺少 raw `mass_into_fluid` Jet 源账本,无法扣除质量源判断闭合。`ΔM+∫outward_mass_flux dt` 仅是表观质量源:refined 内/外=-0.0172316912/-0.646617293 root mass;uniform=3.519017515/3.575029022。相对初始 CV 库存分别 (-2.37505e-6,-5.74501e-5)/(4.84891e-4,3.17573e-4)。轮廓间质量差异保留供审查,不能据此宣称质量闭合通过。
|
||||
|
||||
**V4:SRT,无 Jet,t*=60。** uniform 480k/refined 240k steps,各 120 个样本。末窗 t*=50–60 共 21 点;drift 是原 runner 的 `abs(OLS slope per t*)*10`。
|
||||
|
||||
| case / 指标 | 末窗 range | 原 range 上限 | 末窗 drift | 原 drift 上限 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| refined Cd | 9.86614e-4 | 0.002 | 4.68834e-6 | 0.0004 |
|
||||
| refined Cl | 6.95374e-5 | 6e-5 | 3.28188e-6 | 1.2e-5 |
|
||||
| refined p_diff | 1.45066e-4 | 4e-5 | 9.28999e-6 | 8e-6 |
|
||||
| uniform Cd | 1.19061e-3 | 0.002 | 1.60374e-4 | 0.0004 |
|
||||
| uniform Cl | 7.93289e-5 | 6e-5 | 5.92289e-6 | 1.2e-5 |
|
||||
| uniform p_diff | 1.27157e-4 | 4e-5 | 2.80735e-5 | 8e-6 |
|
||||
|
||||
两例均有 Cl range、p_diff range/drift 超限,保持 `steady=false,status=NOT_STEADY,verdict=null`。末窗均值 refined=(Cd 5.588876919,Cl 0.010732806,p_diff 0.117811546),uniform=(5.586439289,0.010680915,0.117402607)。原区间 Cd=[5.57,5.59]、Cl=[0.0104,0.011]、p_diff=[0.1172,0.1176];refined 压差均值在区间外,uniform 均值在区间内,但均不能越过稳态 gate。
|
||||
|
||||
120 点全序列(t*=0.5–60)存在初始瞬态和反复起伏:refined Cd/Cl/p_diff 的增量符号变化次数为 82/80/82,uniform 为 79/78/81;末窗仍为 13/13/14 和 12/13/11。末窗有符号 OLS slope per t* refined=(-4.68834e-7,-3.28188e-7,-9.28999e-7),uniform=(-1.60374e-5,+5.92289e-7,-2.80735e-6)。这只是当前采样分辨率下的非单调时序描述,不能确定振荡频率、物理/数值根因,也不是发散或瓶颈结论。完整 JSON 保留全序列及逐 10 t* 窗统计。边界偏差仍为 pressure-NEQ/neq_extrap outlet 与 local Zou-He inlet,不能称为原始 do-nothing DFG 边界。
|
||||
|
||||
**复核边界与证据。** 本轮不是发布通过:短套件仍为 767 passed/24 failed/1 skipped;V1 历史 pin 阻塞,V3 四例原 TG dry-run 均被 frozen-source guard 拒绝、尚无 CUDA 计算或 TG 结果;未接纳待修订 `56004f44`,没有修改 dtype/NumPy/数值核心。下一步由 Claude 提交修订和解释、Grok 独立复核,再由集成任务接受明确通过审查的 SHA。
|
||||
|
||||
原始证据根 `/home/frank14f/CelerisLab-release-evidence-20261020/physical-750e8a4/followup-queue`。`final-physical-digest.json` 约 99 KB,含全部六例原生小摘要、门槛、CV 抽稀结果、源/输出哈希、进程检查和限制;后处理脚本 `final_physical_digest.py` 不导入或运行模拟器。原始 history/log 只保留在 Horsea,没有上传巨量日志。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
"""Read-only postprocessing of the six existing, authorized simulation outputs."""
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import datetime
|
||||
import numpy as np
|
||||
|
||||
Q = Path('/home/frank14f/CelerisLab-release-evidence-20261020/physical-750e8a4/followup-queue')
|
||||
R = Path('/home/frank14f/CelerisLab-release-prep-20261020')
|
||||
SHA = '750e8a483ec47dcf472e528edc1cf2936f0e7e83'
|
||||
def read(p):
|
||||
return json.loads(p.read_text())
|
||||
def v4_cases(data):
|
||||
"""DFG Re20 result.json: schema 1 is a bare case list, schema 2 is {schema, provenance, cases}."""
|
||||
if isinstance(data, list): return data
|
||||
if isinstance(data, dict) and data.get('schema') == 2: return data['cases']
|
||||
raise ValueError('unknown DFG Re20 result schema')
|
||||
def sha(p):
|
||||
return hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
def cmd(*a):
|
||||
return subprocess.check_output(a, text=True).strip()
|
||||
def span(a):
|
||||
return float(np.ptp(a))
|
||||
def finite(x):
|
||||
if isinstance(x, dict): return all(finite(v) for v in x.values())
|
||||
if isinstance(x, list): return all(finite(v) for v in x)
|
||||
return not isinstance(x, (int,float)) or math.isfinite(x)
|
||||
def stats(rows, key):
|
||||
t=np.array([v['tstar'] for v in rows]); v=np.array([r[key] for r in rows])
|
||||
d=np.diff(v); nz=np.sign(d[d!=0])
|
||||
return dict(samples=len(v),first=float(v[0]),last=float(v[-1]),min=float(min(v)),max=float(max(v)),mean=float(np.mean(v)),range=span(v),ols_slope_per_tstar=float(np.polyfit(t-t[0],v,1)[0]),increment_sign_changes=int(np.count_nonzero(nz[1:]!=nz[:-1])),total_variation=float(np.abs(d).sum()))
|
||||
|
||||
state=read(Q/'state.json'); manifest=read(Q/'manifest.json'); execution=read(Q/'execution.json')
|
||||
out=dict(schema=1,utc=datetime.datetime.now(datetime.timezone.utc).isoformat(),sha=SHA,evidence_root=str(Q),postprocessor_sha256=sha(Path(__file__)),scope='Offline read-only digest; no new simulation, gate edit, acceptance promotion, or performance claim',verification={},cases={})
|
||||
v=out['verification']; v['all_six_completed']=len(state['jobs'])==6 and all(x['status']=='COMPLETED' for x in state['jobs'].values())
|
||||
v['all_exit_zero_no_timeout']=all(x['exit_code']==0 and not x['timeout'] for x in state['jobs'].values())
|
||||
v['head']=cmd('git','-C',str(R),'rev-parse','HEAD');v['git_status_porcelain']=cmd('git','-C',str(R),'status','--porcelain');v['clean_fixed_head']=v['head']==SHA and not v['git_status_porcelain']
|
||||
v['tree']=cmd('git','-C',str(R),'rev-parse','HEAD^{tree}');v['src_tree']=cmd('git','-C',str(R),'rev-parse','HEAD:src')
|
||||
v['tracked_source_map_matches_manifest']=all(sha(R/p)==h for p,h in manifest['files_sha256'].items());v['tracked_source_map_count']=len(manifest['files_sha256'])
|
||||
v['execution_final_source_matches_manifest']=all(execution['source_after'][k]==manifest[k] for k in ['sha','tree','src_tree','git_status','git_diff_sha256','files_sha256'])
|
||||
pids=[state['scheduler_pid']]+[x['pid'] for x in state['jobs'].values()]
|
||||
v['tracked_processes']={}
|
||||
for pid in pids:
|
||||
p=subprocess.run(['ps','-p',str(pid),'-o','pid=,ppid=,stat=,comm='],capture_output=True,text=True)
|
||||
v['tracked_processes'][str(pid)]=p.stdout.strip() or 'absent'
|
||||
v['no_tracked_process_running']=all(s=='absent' or ' Z' in s for s in v['tracked_processes'].values())
|
||||
v['gpu_compute_processes']=cmd('nvidia-smi','--query-compute-apps=gpu_uuid,pid,process_name,used_gpu_memory','--format=csv')
|
||||
out['hashes']={p.name:sha(p) for p in [Q/'manifest.json',Q/'execution.json',Q/'state.json',Q/'queue-plan.json',Q/'receipt-preflight.json',Q/'authorized_followup_queue.py',Q/'v4_external_timing.py']}
|
||||
for label,sj in state['jobs'].items():
|
||||
p=Q/label; r=read(p/'result.json'); c=out['cases'][label]=dict(status=sj['native_status'],exit_code=sj['exit_code'],timeout=sj['timeout'],process_wall_seconds=sj['wall_seconds'],pid=sj['pid'],gpu_uuid=sj['gpu_uuid'],source_unchanged=r['source_unchanged'])
|
||||
c['source_matches_manifest']=all(r['source'][k]==manifest[k] and r['source_after'][k]==manifest[k] for k in ['sha','tree','src_tree','git_status','git_diff_sha256','files_sha256'])
|
||||
c['external_manifest_hash_matches']=r['external_manifest_sha256']==sha(Q/'manifest.json')
|
||||
c['hashes']={str(z.relative_to(p)):sha(z) for z in [p/'command.json',p/'result.json',*sorted((p/'output').rglob('*.json')),*sorted((p/'output').rglob('*.csv')),*sorted((p/'output').rglob('*.jsonl'))]}
|
||||
if label.startswith('v4'):
|
||||
n=v4_cases(read(p/'output/result.json'))[0]; rows=n['rows']
|
||||
captured=r['native_results'][0]
|
||||
c['native_receipt_matches_external_result']=all(n[k]==z for k,z in captured.items() if k not in ['last_row','rows_count']) and captured['last_row']==rows[-1] and captured['rows_count']==len(rows)
|
||||
c['external_receipt_history_handling']='V4 external receipt stores row count and last row; full native rows are separately hashed'
|
||||
c['native_all_finite']=finite(n)
|
||||
c['native']={k:z for k,z in n.items() if k not in ['rows','lattice']}
|
||||
c['full_series']={k:stats(rows,k) for k in ['cd','cl','p_diff']}
|
||||
c['ten_tstar_windows']={str(a):{k:stats([z for z in rows if a<=z['tstar']<=a+10],k) for k in ['cd','cl','p_diff']} for a in range(0,60,10)}
|
||||
c['last_window']=dict(tstar=[50,60],metrics={k:stats(rows[-21:],k) for k in ['cd','cl','p_diff']})
|
||||
c['health']=dict(rho_min=min(z['rho_min'] for z in rows),rho_max=max(z['rho_max'] for z in rows),max_mach=max(z['max_mach'] for z in rows))
|
||||
continue
|
||||
case=label.rsplit('-',1)[1]; base=p/'output'/case;n=read(base/'summary.json');plan=read(p/'output/plan.json')
|
||||
c['native_receipt_matches_external_result']={k:z for k,z in n.items() if k!='history'}==r['native_summary']
|
||||
c['external_receipt_history_handling']='V2 external receipt omits the history path; complete history.csv is separately hashed'
|
||||
c['native_all_finite']=finite(n)
|
||||
c['native_runner_hash_matches']=plan['runner_sha256']==sha(R/'tests/validation/run_refined_dfg.py')
|
||||
sm=plan['source_manifest'];c['native_source_capture_matches']=sm['checkout_revision']==SHA and not sm['checkout_dirty'] and all(sha(R/f)==h==manifest['files_sha256'][f] for f,h in sm['sha256'].items())
|
||||
c['native_source_capture_file_count']=len(sm['sha256'])
|
||||
c['native']={k:z for k,z in n.items() if k not in ['history','cycles','jet_diagnostic_windows']}; c['comparison']=read(p/'output/comparison.json');c['gates']=plan['gates'];c['reference_gates']=plan['reference_gates'];c['diagnostic_windows']=n['jet_diagnostic_windows']
|
||||
rows=[{k:float(z) for k,z in row.items()} for row in csv.DictReader((base/'history.csv').open())];late=[x for x in rows if x['tstar']>=290]
|
||||
c['health']=dict(rows=len(rows),tstar=[rows[0]['tstar'],rows[-1]['tstar']],all_finite=finite(rows),rho_min=min(z['rho_min'] for z in rows),rho_max=max(z['rho_max'] for z in rows),max_mach=max(z['max_mach'] for z in rows),mass_first=rows[0]['mass'],mass_last=rows[-1]['mass'],mass_change_fraction=(rows[-1]['mass']-rows[0]['mass'])/rows[0]['mass'],mass_late_range_fraction=span([z['mass'] for z in late])/np.mean([z['mass'] for z in late]))
|
||||
if n['cycles']:
|
||||
tail=n['cycles'][-5:]; periods=[z['period'] for z in tail]; masses=[z['mass_time_mean'] for z in tail]
|
||||
c['periodic_actual']=dict(cycles_total=len(n['cycles']),tail_cycles=len(tail),tail_start=tail[0]['start'],tail_end=tail[-1]['end'],period_min=min(periods),period_max=max(periods),period_mean=float(np.mean(periods)),period_relative_range=span(periods)/np.mean(periods),last_cycle_age=rows[-1]['tstar_force']-tail[-1]['end'],consecutive_max_gap=max(abs(a['end']-b['start']) for a,b in zip(tail,tail[1:])),minimum_lift_amplitude=min(z['cl_amplitude'] for z in tail),extrema_ranges={k+'_'+e:span([z[k+'_'+e] for z in tail]) for k in ['cd','cl','dp'] for e in ['min','max']},mass_relative_range=span(masses)/abs(np.mean(masses)),max_cycle_flux_relative=max(z['flux_relative'] for z in tail),last_cycle={k:z for k,z in tail[-1].items() if not k.endswith('_waveform')})
|
||||
cv=[json.loads(x) for x in (base/'cv_history.jsonl').read_text().splitlines()];t=np.array([x['time_root'] for x in cv]);inv=np.array([x['inventory'] for x in cv]);flux=np.array([x['outward_flux'] for x in cv]);j=np.array([x['interval_impulse_root'] for x in cv[1:]]).sum(axis=0);duration=t[-1]-t[0];norm=duration*.5*.01**2*40
|
||||
cvout=c['cv']=dict(samples=len(cv),all_finite=finite(cv),tstar=[cv[0]['tstar'],cv[-1]['tstar']],duration_root=duration,coefficient_normalization=norm,raw_body_reaction_impulse_root=j.tolist(),first_preceding_impulse_excluded=True,contours_root=plan['cv_observation'][case+'_contours_root'],mass_qualification='Apparent mass source only: raw Jet mass_into_fluid ledger was not recorded, so mass-source-subtracted closure cannot be assessed',acceptance='offline diagnostics only; no CV pass threshold was defined',strides={})
|
||||
for stride in [1,2,4]:
|
||||
idx=np.arange(0,len(t),stride);assert idx[-1]==len(t)-1
|
||||
integral=np.trapz(flux[idx],t[idx],axis=0); delta=inv[-1]-inv[0];jcv=-delta[:,1:]-integral[:,1:];res=jcv-j
|
||||
cvout['strides'][str(stride)]=dict(samples=len(idx),momentum_delta_root=delta[:,1:].tolist(),outward_momentum_integral_root=integral[:,1:].tolist(),cv_reaction_impulse_root=jcv.tolist(),residual_impulse_root=res.tolist(),residual_coefficient=(res/norm).tolist(),apparent_mass_source_root=(delta[:,0]+integral[:,0]).tolist(),apparent_mass_source_fraction_initial_inventory=((delta[:,0]+integral[:,0])/inv[0,:,0]).tolist())
|
||||
a=np.array(cvout['strides']['1']['residual_coefficient']);cvout['contour_residual_coefficient_difference']=(a[1]-a[0]).tolist();cvout['coarsening_coefficient_difference']={s:(np.array(cvout['strides'][s]['residual_coefficient'])-a).tolist() for s in ['2','4']}
|
||||
v['all_six_source_unchanged']=all(c['source_unchanged'] and c['source_matches_manifest'] and c['external_manifest_hash_matches'] for c in out['cases'].values())
|
||||
v['all_six_native_receipts_match']=all(c['native_receipt_matches_external_result'] and c['native_all_finite'] for c in out['cases'].values())
|
||||
v['all_v2_native_captures_match']=all(c['native_runner_hash_matches'] and c['native_source_capture_matches'] for k,c in out['cases'].items() if k.startswith('v2'))
|
||||
out['limitations']=['V1 historical source pin remains blocked','V3 original TG source guard blocked all four dry-runs before CUDA; no TG result or acceptance','Short suite remains 767 passed, 24 failed, 1 skipped; not a release pass','Matched_case_pass remains false because each invocation selected one case; native paired-case comparison branch did not execute','Fixed Jet CV is diagnostic; mass source ledger absent; no physical acceptance','V4 both NOT_STEADY with original ranges/drifts/bands; boundary deviations retained','No formal performance claim: timings include observation/profiling and startup is separately recorded']
|
||||
dest=Q/'final-physical-digest.json';dest.write_text(json.dumps(out,indent=2,allow_nan=False)+'\n')
|
||||
print(json.dumps(dict(output=str(dest),bytes=dest.stat().st_size,sha256=sha(dest),verification=v),indent=2))
|
||||
@@ -0,0 +1,79 @@
|
||||
"""CPU-only contract tests for run_dfg_re20. Not CUDA or DFG acceptance evidence."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "validation"))
|
||||
import run_dfg_re20 as v # noqa: E402
|
||||
import run_refined_dfg as rr # noqa: E402
|
||||
|
||||
h, _ = rr.helpers(rr.DEFAULT_SOURCE)
|
||||
|
||||
|
||||
def rows(values, tstar_end=v.TSTAR_MAX):
|
||||
n = int(round(tstar_end / v.SAMPLE_TSTAR))
|
||||
return [dict(tstar=(i + 1) * v.SAMPLE_TSTAR, **values(i)) for i in range(n)]
|
||||
|
||||
|
||||
class Re20Contract(unittest.TestCase):
|
||||
def test_lattice_parameters_and_bounds(self):
|
||||
root, uniform = v.lattice_parameters(40), v.lattice_parameters(80)
|
||||
self.assertAlmostEqual(root["tau"], 0.56, places=12)
|
||||
self.assertAlmostEqual(uniform["tau"], 0.62, places=12)
|
||||
self.assertEqual((root["steps"], root["interval"]), (240000, 2000))
|
||||
self.assertEqual((uniform["steps"], uniform["interval"]), (480000, 4000))
|
||||
self.assertAlmostEqual(v.plan()["refined_fine_tau"], 0.62, places=12)
|
||||
|
||||
def test_config_only_changes_viscosity(self):
|
||||
base = v._re100_config(h, 40, "SRT", "none")
|
||||
cfg, body, p = v.config(h, 40, "SRT")
|
||||
self.assertAlmostEqual(cfg["physics"]["viscosity"], 0.01 * 40 / 20, places=15)
|
||||
self.assertEqual((p["re"], p["viscosity"]), (20.0, cfg["physics"]["viscosity"]))
|
||||
base[0]["physics"]["viscosity"] = cfg["physics"]["viscosity"]
|
||||
self.assertEqual(base[0], cfg)
|
||||
self.assertEqual(base[1], body)
|
||||
with self.assertRaisesRegex(ValueError, "no Jets"):
|
||||
v.config(h, 40, "MRT", "zero")
|
||||
|
||||
def test_units(self):
|
||||
s = v.sample([2.0, -0.5], 4, 40, 3.0)
|
||||
den = rr.SPEED**2 * 40 / 2
|
||||
self.assertAlmostEqual(s["cd"], 2.0 / 4 / den)
|
||||
self.assertAlmostEqual(s["cl"], -0.5 / 4 / den)
|
||||
self.assertAlmostEqual(s["p_diff"], 3.0 * 0.04)
|
||||
self.assertEqual(s["dp_scaled"], 3.0)
|
||||
|
||||
def test_steady_and_verdict(self):
|
||||
inside = dict(cd=5.58, cl=0.0106, p_diff=0.1174)
|
||||
out = v.summarize(rows(lambda i: inside))
|
||||
self.assertEqual((out["status"], out["steady"], out["window_samples"]),
|
||||
("WITHIN_INTERVALS", True, 21))
|
||||
self.assertTrue(out["cd_positive"])
|
||||
self.assertAlmostEqual(out["edge_distance"]["cl"], 0.0002)
|
||||
self.assertTrue(all(out["verdict"].values()))
|
||||
low = v.summarize(rows(lambda i: dict(inside, cl=0.0100)))
|
||||
self.assertEqual(low["status"], "OUTSIDE_INTERVALS")
|
||||
self.assertFalse(low["verdict"]["cl"])
|
||||
|
||||
def test_slow_drift_gives_no_verdict(self):
|
||||
# Range 2e-5 passes (limit 6e-5); slope * window 2e-5 exceeds 1.2e-5.
|
||||
out = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106 + 1e-6 * i, p_diff=0.1174)))
|
||||
self.assertEqual((out["status"], out["verdict"]), ("NOT_STEADY", None))
|
||||
self.assertLessEqual(out["ranges"]["cl"], out["range_limits"]["cl"])
|
||||
self.assertGreater(out["drifts"]["cl"], out["slope_limits"]["cl"])
|
||||
|
||||
def test_noise_range(self):
|
||||
quiet = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106 + 2e-5 * (i % 2), p_diff=0.1174)))
|
||||
self.assertTrue(quiet["steady"]) # within the range limit, no trend
|
||||
noisy = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106 + 1e-4 * (i % 2), p_diff=0.1174)))
|
||||
self.assertEqual(noisy["status"], "NOT_STEADY")
|
||||
self.assertGreater(noisy["ranges"]["cl"], noisy["range_limits"]["cl"])
|
||||
|
||||
def test_incomplete_run_gives_no_verdict(self):
|
||||
out = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106, p_diff=0.1174), tstar_end=30))
|
||||
self.assertEqual((out["status"], out["verdict"]), ("INCOMPLETE", None))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -80,11 +80,12 @@ class ThreeLevelCPU(unittest.TestCase):
|
||||
f, g = run._levels[1:]
|
||||
b = g.boundary
|
||||
events, feeds = [], []
|
||||
advance, gather = run._advance_child, run._gather_halo
|
||||
def record_feed(buffer, **kw):
|
||||
self.assertIs(buffer, (f.current, f.spare)[len(feeds)])
|
||||
feeds.append(buffer)
|
||||
advance(buffer, **kw)
|
||||
advance, gather = run._advance, run._gather_halo
|
||||
def record_feed(index, buffer, **kw): # index 2: L1 hop feeds L2
|
||||
if index == 2:
|
||||
self.assertIs(buffer, (f.current, f.spare)[len(feeds)])
|
||||
feeds.append(buffer)
|
||||
advance(index, buffer, **kw)
|
||||
def record_halo(level, buffer, **kw):
|
||||
if level is g:
|
||||
events.append(("halo", buffer))
|
||||
@@ -95,7 +96,7 @@ class ThreeLevelCPU(unittest.TestCase):
|
||||
self.assertIs(kind, g.kind)
|
||||
events.append(("step", src))
|
||||
g.kernel.side_effect = record_step
|
||||
with patch.object(run, "_advance_child", side_effect=record_feed), \
|
||||
with patch.object(run, "_advance", side_effect=record_feed), \
|
||||
patch.object(run, "_gather_halo", side_effect=record_halo):
|
||||
run.step()
|
||||
self.assertEqual(feeds, [f.current, f.spare])
|
||||
@@ -105,8 +106,8 @@ class ThreeLevelCPU(unittest.TestCase):
|
||||
self.assertGreater(len(np.unique(b.tables["block"])), 1)
|
||||
# Negative control: reusing L1's old source at the midpoint must fail.
|
||||
feeds.clear()
|
||||
with patch.object(run, "_advance_child", side_effect=lambda buffer, **kw:
|
||||
record_feed(f.current, **kw)):
|
||||
with patch.object(run, "_advance", side_effect=lambda index, buffer, **kw:
|
||||
record_feed(index, f.current if index == 2 else buffer, **kw)):
|
||||
with self.assertRaises(AssertionError):
|
||||
run.step()
|
||||
run.close()
|
||||
@@ -172,11 +173,12 @@ class ThreeLevelGPU(unittest.TestCase):
|
||||
p1, p2 = run.patch, run.child_patch
|
||||
run.initialize(state(16, 16, 1), state(*p1.fine_shape, 2), state(*p2.fine_shape, 3))
|
||||
f = run._levels[1]
|
||||
feeds, original = [], run._advance_child
|
||||
def record(buffer, **kw):
|
||||
feeds.append(int(buffer))
|
||||
original(buffer, **kw)
|
||||
with patch.object(run, "_advance_child", side_effect=record):
|
||||
feeds, original = [], run._advance
|
||||
def record(index, buffer, **kw): # index 2: L1 hop feeds L2
|
||||
if index == 2:
|
||||
feeds.append(int(buffer))
|
||||
original(index, buffer, **kw)
|
||||
with patch.object(run, "_advance", side_effect=record):
|
||||
cur, spare = int(f.current), int(f.spare)
|
||||
run.step(1)
|
||||
self.assertEqual(feeds, [cur, spare]) # current G, then the new G of hop 1
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
"""Steady DFG 2D-1 (Re 20): refined and uniform runs against the official reference intervals.
|
||||
|
||||
Not a full benchmark certification. Declared deviations from the official setup:
|
||||
pressure-NEQ outlet (``neq_extrap``) instead of do-nothing, local Zou-He inlet.
|
||||
Geometry, inflow profile, initial state, refined layout and observation reuse the
|
||||
pinned Re100 runner (``run_refined_dfg``) and ``re100_helpers`` unchanged; only the
|
||||
lattice viscosity is set for Re 20 (nu = U_lattice * D / 20 at the root level).
|
||||
|
||||
Units. The helpers use scaled DFG units with mean inflow speed 1, so
|
||||
Cd = 2 Fx / (rho U^2 D) and Cl = 2 Fy / (rho U^2 D) are scale-free, and
|
||||
``pressure_probes`` returns dp / (rho U^2). The official p_diff = p(0.15, 0.2) -
|
||||
p(0.25, 0.2) uses U = 0.2 m/s, rho = 1, so p_diff = dp * 0.04. Both are reported;
|
||||
only p_diff is compared with the official interval. Forces use the existing sign
|
||||
convention: F is the force of the fluid on the cylinder, i.e. the refined raw
|
||||
exchange impulse (window ledger, ``total.impulse``) or the uniform ``read_force``,
|
||||
with x along the inflow and y up from the bottom wall. Cd must be positive, and the
|
||||
official Cl is positive (centre 0.20 below the centreline 0.205), so a flipped sign
|
||||
shows up as Cl outside its interval.
|
||||
|
||||
Sampling. One sample every SAMPLE_TSTAR (t* = U t / D; 0.25 s), taken at the end of
|
||||
a completed root step with all levels synchronized; the step index is recorded. Cd
|
||||
and Cl are means over the sample interval (window exchange ledger refined, per-batch
|
||||
forces uniform), which filters acoustic and step-parity oscillation; p_diff is the
|
||||
end-of-interval value at the exact surface points.
|
||||
|
||||
Steady rule (our engineering choice, frozen before any run; not official). Over the
|
||||
trailing WINDOW_TSTAR (t* 50..60 inclusive, 21 samples), for each of Cd, Cl and
|
||||
p_diff: range (max - min) <= 10 % and |least-squares slope * window| <= 2 % of the
|
||||
width of its official interval. Absolute limits, so the small Cl is never a divisor;
|
||||
the slope term bounds slow monotone drift that the range alone would pass. If the
|
||||
rule fails at TSTAR_MAX the run reports "not steady" and gives no interval verdict;
|
||||
limits are never relaxed after a run. The verdict uses the window mean, and the
|
||||
distance of each mean to its nearest interval edge is reported (negative outside).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import numpy as np
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
import run_refined_dfg as rr # noqa: E402
|
||||
|
||||
RE = 20.0
|
||||
U_PHYS = 0.2 # m/s mean inflow; rho = 1
|
||||
TSTAR_MAX = 60.0 # 30 s physical
|
||||
SAMPLE_TSTAR = 0.5 # 0.25 s
|
||||
WINDOW_TSTAR = 10.0 # trailing 5 s, 21 samples
|
||||
# Schaefer & Turek (1996), 2D-1 lower/upper bounds.
|
||||
BANDS = dict(cd=(5.57, 5.59), cl=(0.0104, 0.0110), p_diff=(0.1172, 0.1176))
|
||||
STEADY_RANGE = {k: (hi - lo) / 10 for k, (lo, hi) in BANDS.items()}
|
||||
STEADY_SLOPE = {k: (hi - lo) / 50 for k, (lo, hi) in BANDS.items()}
|
||||
DEVIATIONS = ("pressure-NEQ outlet (neq_extrap), not do-nothing", "local Zou-He inlet")
|
||||
|
||||
_re100_config = rr.config
|
||||
|
||||
|
||||
def config(h, diameter, collision="SRT", jet_scenario="none"):
|
||||
"""The pinned Re100 configuration with the lattice viscosity set for Re 20."""
|
||||
if jet_scenario != "none":
|
||||
raise ValueError("DFG 2D-1 runs have no Jets")
|
||||
cfg, body, p = _re100_config(h, diameter, collision, jet_scenario)
|
||||
nu = p["mean_speed"] * p["diameter"] / RE
|
||||
cfg["physics"]["viscosity"] = nu
|
||||
return cfg, body, dict(p, viscosity=nu, re=RE)
|
||||
|
||||
|
||||
def lattice_parameters(diameter, speed=rr.SPEED):
|
||||
nu = speed * diameter / RE
|
||||
return dict(viscosity=nu, tau=0.5 + 3 * nu, steps=math.ceil(TSTAR_MAX * diameter / speed),
|
||||
interval=round(SAMPLE_TSTAR * diameter / speed))
|
||||
|
||||
|
||||
def sample(impulse, n, diameter, dp):
|
||||
den = rr.SPEED**2 * diameter / 2
|
||||
return dict(cd=float(impulse[0] / n / den), cl=float(impulse[1] / n / den),
|
||||
dp_scaled=float(dp), p_diff=float(dp) * U_PHYS**2)
|
||||
|
||||
|
||||
def summarize(rows):
|
||||
"""Steady rule over the trailing window, then the interval verdict (or none)."""
|
||||
if not rows or rows[-1]["tstar"] < TSTAR_MAX - 1e-9:
|
||||
return dict(status="INCOMPLETE", steady=False, verdict=None)
|
||||
window = [r for r in rows if r["tstar"] >= TSTAR_MAX - WINDOW_TSTAR - 1e-9]
|
||||
t = np.array([r["tstar"] for r in window])
|
||||
ranges = {k: max(r[k] for r in window) - min(r[k] for r in window) for k in BANDS}
|
||||
drifts = {k: abs(np.polyfit(t, [r[k] for r in window], 1)[0]) * WINDOW_TSTAR for k in BANDS}
|
||||
steady = all(ranges[k] <= STEADY_RANGE[k] and drifts[k] <= STEADY_SLOPE[k] for k in BANDS)
|
||||
final = {k: float(np.mean([r[k] for r in window])) for k in BANDS}
|
||||
edge = {k: min(final[k] - BANDS[k][0], BANDS[k][1] - final[k]) for k in BANDS}
|
||||
result = dict(window_samples=len(window), ranges=ranges, drifts=drifts,
|
||||
range_limits=STEADY_RANGE, slope_limits=STEADY_SLOPE, steady=steady,
|
||||
window_mean=final, edge_distance=edge, bands=BANDS,
|
||||
deviations=DEVIATIONS, cd_positive=final["cd"] > 0)
|
||||
if not steady:
|
||||
return dict(result, status="NOT_STEADY", verdict=None)
|
||||
inside = {k: BANDS[k][0] <= final[k] <= BANDS[k][1] for k in BANDS}
|
||||
return dict(result, status="WITHIN_INTERVALS" if all(inside.values()) else "OUTSIDE_INTERVALS",
|
||||
verdict=inside)
|
||||
|
||||
|
||||
def healthy(row):
|
||||
return row["max_mach"] <= 0.2 and row["rho_min"] >= 0.5 and row["rho_max"] <= 1.5
|
||||
|
||||
|
||||
def run_case(a, h, case):
|
||||
from contextlib import ExitStack
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
from CelerisLab.lbm.descriptors import FLUID
|
||||
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
|
||||
from CelerisLab.simulation import Simulation
|
||||
|
||||
refined = case == "refined"
|
||||
cfgdict, bodydict, p = config(h, 40 if refined else 80, a.collision)
|
||||
lp = lattice_parameters(p["diameter"])
|
||||
rows = []
|
||||
with ExitStack() as stack:
|
||||
if refined:
|
||||
cfg, pair, body, host = rr.host_geometry(a.source, h, a.collision)
|
||||
context = stack.enter_context(CudaContext(0))
|
||||
backend = stack.enter_context(FixedPatchStepper(
|
||||
cfg, rr.PLACEMENTS["wide"], context=context, coarse_boundary="channel",
|
||||
base_block=rr.BASE_BLOCK, fine_block=rr.FINE_BLOCK, fine_objects=(body,),
|
||||
observe_raw_exchange=True, observe_window_exchange=True))
|
||||
from CelerisLab.lbm.channel import channel_masks
|
||||
from CelerisLab.lbm.refinement import ACTIVE
|
||||
from CelerisLab.lbm.descriptors import SOLID
|
||||
masks = [channel_masks(pair.coarse_shape)[0] & ~pair.covered,
|
||||
(pair.fine_kind == ACTIVE)
|
||||
& ((host.flags.reshape(pair.fine_shape) & SOLID) == 0)]
|
||||
backend.initialize(rr.initial_state(pair.coarse_shape, (0.0, 0.0), 1.0, root=True),
|
||||
rr.initial_state(pair.fine_shape, pair.fine_origin, 0.5))
|
||||
probe_p = dict(p, center=[(80.5 - pair.fine_origin[0]) / 0.5 - 0.5,
|
||||
(81.0 - pair.fine_origin[1]) / 0.5 - 0.5], radius=40.0)
|
||||
else:
|
||||
tmp = Path(stack.enter_context(TemporaryDirectory()))
|
||||
(tmp / "lbm.json").write_text(json.dumps(cfgdict))
|
||||
(tmp / "body.json").write_text(json.dumps(bodydict))
|
||||
sim = stack.enter_context(Simulation(str(tmp / "lbm.json"), str(tmp / "body.json"),
|
||||
device_id=0))
|
||||
sim.initialize()
|
||||
probe_p = p
|
||||
fluid = (sim.field.flag.reshape(p["ny"], p["nx"]) & FLUID) != 0
|
||||
begin = 0
|
||||
while begin < lp["steps"]:
|
||||
rr.check_deadline(a.deadline)
|
||||
n = min(lp["interval"], lp["steps"] - begin)
|
||||
if refined:
|
||||
impulse = rr.advance_refined_interval(backend, n, a.deadline, "window")
|
||||
fields = [rr.macros(g) for g in backend.download()]
|
||||
rho, ux, uy = fields[1]
|
||||
fluid = masks[1]
|
||||
vals = [tuple(v[m] for v in f) for m, f in zip(masks, fields)]
|
||||
else:
|
||||
impulse, done = np.zeros(2), 0
|
||||
while done < n:
|
||||
batch = min(100, n - done)
|
||||
sim.run(batch)
|
||||
impulse += sim.read_force(0).astype(np.float64) * batch
|
||||
done += batch
|
||||
m = sim.get_macroscopic()
|
||||
rho, ux, uy = [np.asarray(m[k], np.float64).reshape(p["ny"], p["nx"])
|
||||
for k in ("rho", "ux", "uy")]
|
||||
vals = [(rho[fluid], ux[fluid], uy[fluid])]
|
||||
begin += n
|
||||
row = dict(step=begin, tstar=begin * rr.SPEED / p["diameter"],
|
||||
**sample(impulse, n, p["diameter"],
|
||||
h.pressure_probes(rho, fluid, probe_p)["dp"]),
|
||||
rho_min=float(min(v[0].min() for v in vals)),
|
||||
rho_max=float(max(v[0].max() for v in vals)),
|
||||
max_mach=float(max(np.sqrt(3 * (v[1]**2 + v[2]**2)).max() for v in vals)))
|
||||
rr.ensure_finite(row)
|
||||
rows.append(row)
|
||||
if not healthy(row):
|
||||
break
|
||||
summary = summarize(rows) if healthy(rows[-1]) else dict(status="UNHEALTHY", steady=False,
|
||||
verdict=None)
|
||||
return dict(case=case, collision=a.collision, lattice=lp, rows=rows, **summary)
|
||||
|
||||
|
||||
def plan(collision="SRT"):
|
||||
return dict(re=RE, collision=collision, tstar_max=TSTAR_MAX, sample_tstar=SAMPLE_TSTAR,
|
||||
window_tstar=WINDOW_TSTAR, bands=BANDS, range_limits=STEADY_RANGE, slope_limits=STEADY_SLOPE,
|
||||
deviations=DEVIATIONS, pressure_scale=U_PHYS**2,
|
||||
refined_root=lattice_parameters(40), refined_fine_tau=0.5 + 3 * 2 * rr.SPEED * 40 / RE,
|
||||
uniform=lattice_parameters(80),
|
||||
label="compared against the official reference bands (non-canonical neq_extrap outlet, zou_he_local inlet); not a full benchmark certification")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("--case", choices=["refined", "uniform", "both"], default="refined")
|
||||
parser.add_argument("--collision", choices=["SRT", "MRT"], default="SRT")
|
||||
parser.add_argument("--source", type=Path, default=rr.DEFAULT_SOURCE)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--plan", action="store_true", help="Write the frozen plan; no CUDA")
|
||||
parser.add_argument("--max-wall-seconds", type=float)
|
||||
a = parser.parse_args()
|
||||
a.source = a.source.resolve()
|
||||
a.output.mkdir(parents=True, exist_ok=False)
|
||||
(a.output / "plan.json").write_text(json.dumps(plan(a.collision), indent=2, allow_nan=False))
|
||||
if a.plan:
|
||||
return
|
||||
if not a.max_wall_seconds or a.max_wall_seconds <= 0 or not os.environ.get("CUDA_VISIBLE_DEVICES"):
|
||||
raise SystemExit("GPU run needs --max-wall-seconds > 0 and explicit CUDA_VISIBLE_DEVICES")
|
||||
h, _ = rr.helpers(a.source)
|
||||
rr.config = config # host_geometry builds its root configuration through this name
|
||||
a.deadline = time.monotonic() + a.max_wall_seconds
|
||||
cases = ["refined", "uniform"] if a.case == "both" else [a.case]
|
||||
results = [run_case(a, h, case) for case in cases]
|
||||
(a.output / "result.json").write_text(json.dumps(results, indent=2, allow_nan=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user