tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---TASK CONFIG---" && cat task_config.json && echo "---FEATURE SCHEMA---" && cat feature_schema.json
total 21
drwxrws--- 3 h2tagent h2tagent 4096 Jun 19 21:15 .
drwxr-xr-x 26 root root 4096 Jun 19 21:26 ..
-rw-rw---- 1 h2tagent h2tagent 2093 Jun 19 21:13 candidate_model.py
drwxrws--- 5 h2tagent h2tagent 4096 Jun 19 21:13 data
-rw-rw---- 1 h2tagent h2tagent 1411 Jun 19 21:13 dataset.py
-rw-rw---- 1 h2tagent h2tagent 723 Jun 19 21:13 feature_schema.json
-rw-rw---- 1 h2tagent h2tagent 547 Jun 19 21:13 task_config.json
-rw-rw---- 1 h2tagent h2tagent 2900 Jun 19 21:13 train.py
---TASK CONFIG---
{
"task_id": "airfrans-high-reynolds-drag-extrapolation",
"dataset_name": "airfrans-original-subset",
"n_points": 256,
"in_dim": 4,
"n_targets": 2,
"chord_axis": 1,
"aoa_index": 3,
"lift_index": 1,
"velocity_index": 2,
"seed": 20260518,
"model": {
"point_mlp": [64, 128, 256],
"head": [128, 64]
},
"training": {
"epochs": 40,
"batch_size": 32,
"lr": 0.001,
"weight_decay": 0.0001
},
"evaluation": {
"primary_metric": "mean_nrmse",
"secondary_metrics": ["worst_nrmse", "cd_nmae"]
}
}
---FEATURE SCHEMA---
{
"version": "airfrans-original-subset-v1",
"point_cloud": {
"n_points": 256,
"in_dim": 4,
"channels": [
{"name": "x", "description": "Airfoil-surface node x coordinate (chord-normalized)."},
{"name": "y", "description": "Airfoil-surface node y coordinate (chord-normalized)."},
{"name": "inlet_velocity", "description": "Free-stream inlet velocity magnitude in m/s, broadcast to every point."},
{"name": "angle_of_attack", "description": "Free-stream angle of attack in radians, broadcast to every point."}
]
},
"targets": [
{"name": "C_D", "description": "Drag coefficient of the airfoil."},
{"name": "C_L", "description": "Lift coefficient of the airfoil."}
]
}cd /app && echo "===candidate_model.py===" && cat candidate_model.py && echo "===dataset.py===" && cat dataset.py && echo "===train.py===" && cat train.py
===candidate_model.py===
"""PointNet regressor for the airfoil force-coefficient task.
Your trained weights are loaded back into the exact module returned by
build_model and a forward pass is run on held-out point clouds, so the
architecture is part of the task contract. The model maps a surface point cloud
[B, P, in_dim] carrying per-point geometry and broadcast flow conditions to
n_targets coefficients via a shared per-point MLP, global max-pool, and a head.
Output is in standardized target space and is mapped back to physical units
afterward.
"""
from __future__ import annotations
from typing import Mapping
import torch
import torch.nn as nn
class SharedMLP(nn.Module):
def __init__(self, channels: list[int]) -> None:
super().__init__()
layers: list[nn.Module] = []
for i in range(len(channels) - 1):
layers += [nn.Conv1d(channels[i], channels[i + 1], 1), nn.BatchNorm1d(channels[i + 1]), nn.GELU()]
self.net = nn.Sequential(*layers)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
class PointNetRegressor(nn.Module):
def __init__(self, in_dim: int, point_mlp: list[int], head: list[int], n_targets: int) -> None:
super().__init__()
self.n_targets = n_targets
self.encoder = SharedMLP([in_dim, *point_mlp])
head_layers: list[nn.Module] = []
dims = [point_mlp[-1], *head]
for i in range(len(dims) - 1):
head_layers += [nn.Linear(dims[i], dims[i + 1]), nn.GELU()]
head_layers += [nn.Linear(dims[-1], n_targets)]
self.head = nn.Sequential(*head_layers)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x.transpose(1, 2)
feat = self.encoder(x)
pooled = feat.max(dim=2).values
return self.head(pooled)
def build_model(config: Mapping) -> PointNetRegressor:
m = config["model"]
return PointNetRegressor(
in_dim=int(config["in_dim"]),
point_mlp=[int(c) for c in m["point_mlp"]],
head=[int(c) for c in m["head"]],
n_targets=int(config["n_targets"]),
)
===dataset.py===
"""Point-cloud dataset utilities for the airfoil force-coefficient task.
On-disk format per split: a single split.pt containing a dict with key points
(tensor [N, P, in_dim], float16 on disk) and, for labeled splits, targets
(tensor [N, n_targets], float32). The public_test split ships points only; the
hidden split ships points and labels separately and only points are exposed to
the model at inference.
"""
from __future__ import annotations
from pathlib import Path
import torch
def load_split(split_dir: str | Path) -> tuple[torch.Tensor, torch.Tensor | None]:
payload = torch.load(Path(split_dir) / "split.pt", map_location="cpu", weights_only=True)
targets = payload["targets"].float() if "targets" in payload else None
return payload["points"].float(), targets
def coord_standardizer(points: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
flat = points.reshape(-1, points.shape[-1])
mean = flat.mean(dim=0)
std = flat.std(dim=0).clamp_min(1e-8)
return mean, std
def target_standardizer(targets: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
mean = targets.mean(dim=0)
std = targets.std(dim=0).clamp_min(1e-8)
return mean, std
def iter_indices(n: int, batch_size: int, shuffle: bool = True):
idx = torch.randperm(n) if shuffle else torch.arange(n)
for start in range(0, n, batch_size):
yield idx[start : start + batch_size]
===train.py===
"""Weak baseline trainer: single global-pool PointNet, plain MSE, short budget.
Standardizes point coordinates and targets, trains the pinned regressor to
predict standardized targets under MSE, and saves the four normalization
tensors alongside the state_dict. No augmentation, no per-target weighting, no
early stopping. Candidates are expected to do better on the held-out regime --
input jitter/rotation augmentation, deeper pooling,
…[truncated]cd /app && python -c "
import torch
for split in ['train','val','public_test']:
p = torch.load(f'data/{split}/split.pt', map_location='cpu', weights_only=True)
print('===', split, '===')
print('keys:', list(p.keys()))
print('points shape/dtype:', p['points'].shape, p['points'].dtype)
if 'targets' in p:
print('targets shape/dtype:', p['targets'].shape, p['targets'].dtype)
"=== train === keys: ['points', 'targets'] points shape/dtype: torch.Size([630, 256, 4]) torch.float32 targets shape/dtype: torch.Size([630, 2]) torch.float32 === val === keys: ['points', 'targets'] points shape/dtype: torch.Size([80, 256, 4]) torch.float32 targets shape/dtype: torch.Size([80, 2]) torch.float32 === public_test === keys: ['points'] points shape/dtype: torch.Size([100, 256, 4]) torch.float32
cd /app && python -c "
import torch
def stats(name, t):
print(f'{name}: min={t.min().item():.5f} max={t.max().item():.5f} mean={t.mean().item():.5f} std={t.std().item():.5f}')
for split in ['train','val','public_test']:
p = torch.load(f'data/{split}/split.pt', map_location='cpu', weights_only=True)
pts = p['points']
print('======', split, 'N=', pts.shape[0], '======')
# channels: x, y, inlet_velocity, aoa
for i,nm in enumerate(['x','y','vel','aoa']):
stats(f' ch{i}-{nm}', pts[:,:,i])
# per-case velocity and aoa are constant across points; take point 0
vel = pts[:,0,2]; aoa = pts[:,0,3]
stats(' case-vel', vel)
stats(' case-aoa', aoa)
if 'targets' in p:
t = p['targets']
stats(' C_D', t[:,0])
stats(' C_L', t[:,1])
"====== train N= 630 ====== ch0-x: min=-0.00702 max=1.00000 mean=0.38353 std=0.39556 ch1-y: min=-0.09970 max=0.16270 mean=0.00791 std=0.03346 ch2-vel: min=31.28300 max=74.92400 mean=52.97366 std=12.78588 ch3-aoa: min=-0.08622 max=0.25651 mean=0.07639 std=0.09557 case-vel: min=31.28300 max=74.92400 mean=52.97366 std=12.79600 case-aoa: min=-0.08622 max=0.25651 mean=0.07639 std=0.09565 C_D: min=0.00727 max=0.04595 mean=0.01276 std=0.00510 C_L: min=-0.53363 max=1.85012 mean=0.65962 std=0.57620 ====== val N= 80 ====== ch0-x: min=-0.00328 max=1.00000 mean=0.38458 std=0.39590 ch1-y: min=-0.09394 max=0.13663 mean=0.00777 std=0.03233 ch2-vel: min=31.46800 max=74.16200 mean=54.97725 std=11.39685 ch3-aoa: min=-0.08360 max=0.25121 mean=0.07986 std=0.09472 case-vel: min=31.46800 max=74.16200 mean=54.97725 std=11.46848 case-aoa: min=-0.08360 max=0.25121 mean=0.07986 std=0.09532 C_D: min=0.00756 max=0.03459 mean=0.01268 std=0.00533 C_L: min=-0.48027 max=1.80035 mean=0.67952 std=0.59777 ====== public_test N= 100 ====== ch0-x: min=-0.00553 max=1.00000 mean=0.37608 std=0.39461 ch1-y: min=-0.09606 max=0.15281 mean=0.00879 std=0.03464 ch2-vel: min=75.03000 max=81.59300 mean=78.20860 std=1.96583 ch3-aoa: min=-0.08570 max=0.25820 mean=0.07571 std=0.09605 case-vel: min=75.03000 max=81.59300 mean=78.20860 std=1.97570 case-aoa: min=-0.08570 max=0.25820 mean=0.07571 std=0.09653
cd /app && python -c "
import torch
p = torch.load('data/train/split.pt', map_location='cpu', weights_only=True)
pts = p['points']; t = p['targets']
vel = pts[:,0,2]; aoa = pts[:,0,3]
cd = t[:,0]; cl = t[:,1]
import numpy as np
def corr(a,b):
a=a.numpy(); b=b.numpy()
return np.corrcoef(a,b)[0,1]
print('corr(vel, C_D) =', corr(vel,cd))
print('corr(vel, C_L) =', corr(vel,cl))
print('corr(aoa, C_D) =', corr(aoa,cd))
print('corr(aoa, C_L) =', corr(aoa,cl))
print('corr(aoa^2, C_D) =', corr(aoa**2,cd))
print()
# Does velocity matter once we know shape+aoa? Check how much C_L varies per unit aoa
# Fit linear model C_L ~ aoa
A = torch.stack([aoa, torch.ones_like(aoa)],1)
sol = torch.linalg.lstsq(A, cl).solution
print('C_L ~ aoa: slope,intercept=', sol.tolist())
pred = A@sol
print('C_L linear-in-aoa residual std:', (cl-pred).std().item(), 'vs C_L std', cl.std().item())
"corr(vel, C_D) = -0.15417731232687426 corr(vel, C_L) = -0.022042552660221815 corr(aoa, C_D) = 0.738708826123821 corr(aoa, C_L) = 0.9433278860776938 corr(aoa^2, C_D) = 0.8596726372791974 C_L ~ aoa: slope,intercept= [5.682861328125, 0.22548021376132965] C_L linear-in-aoa residual std: 0.19121742248535156 vs C_L std 0.5761952996253967
cd /app && python -c "
import torch, time
print('cuda available:', torch.cuda.is_available())
print('torch version:', torch.__version__)
import os
print('cpus:', os.cpu_count())
" && nvidia-smi 2>/dev/null | head -15 || echo "no nvidia-smi"cuda available: False torch version: 2.5.1+cu124 cpus: 16
cd /app && python -c "
import torch, numpy as np
p = torch.load('data/train/split.pt', map_location='cpu', weights_only=True)
pts = p['points']; t = p['targets']
vel = pts[:,0,2].numpy(); aoa = pts[:,0,3].numpy()
cd = t[:,0].numpy(); cl = t[:,1].numpy()
N=len(vel)
def fit(X, y, names):
X = np.asarray(X).T
beta, res, rank, sv = np.linalg.lstsq(X, y, rcond=None)
pred = X@beta
r2 = 1 - ((y-pred)**2).sum()/((y-y.mean())**2).sum()
print(' R2=%.4f'%r2, {n:round(float(b),5) for n,b in zip(names,beta)})
return pred
one=np.ones(N)
print('C_D ~ aoa, aoa^2:')
fit([one,aoa,aoa**2], cd, ['1','aoa','aoa2'])
print('C_D ~ aoa, aoa^2, vel:')
fit([one,aoa,aoa**2,vel], cd, ['1','aoa','aoa2','vel'])
print('C_D ~ aoa, aoa^2, log(vel):')
fit([one,aoa,aoa**2,np.log(vel)], cd, ['1','aoa','aoa2','logv'])
print('C_L ~ aoa:')
fit([one,aoa], cl, ['1','aoa'])
print('C_L ~ aoa, vel:')
fit([one,aoa,vel], cl, ['1','aoa','vel'])
# Reynolds scaling test: does C_D*vel^0.2 reduce velocity dependence?
print()
print('std C_D residual after aoa,aoa2 fit, correlate with vel:')
pred = np.linalg.lstsq(np.array([one,aoa,aoa**2]).T, cd, rcond=None)[0]
predv = np.array([one,aoa,aoa**2]).T@pred
resid = cd-predv
print(' corr(resid, vel)=%.3f corr(resid,aoa)=%.3f'%(np.corrcoef(resid,vel)[0,1], np.corrcoef(resid,aoa)[0,1]))
"C_D ~ aoa, aoa^2:
R2=0.7405 {'1': 0.00913, 'aoa': -0.00436, 'aoa2': 0.26481}
C_D ~ aoa, aoa^2, vel:
R2=0.7616 {'1': 0.01221, 'aoa': -0.00454, 'aoa2': 0.26521, 'vel': -6e-05}
C_D ~ aoa, aoa^2, log(vel):
R2=0.7616 {'1': 0.02071, 'aoa': -0.00442, 'aoa2': 0.26471, 'logv': -0.00294}
C_L ~ aoa:
R2=0.8899 {'1': 0.22548, 'aoa': 5.68286}
C_L ~ aoa, vel:
R2=0.8899 {'1': 0.24421, 'aoa': 5.68215, 'vel': -0.00035}
std C_D residual after aoa,aoa2 fit, correlate with vel:
corr(resid, vel)=-0.285 corr(resid,aoa)=-0.000cd /app && python -c "
import torch, numpy as np
p = torch.load('data/train/split.pt', map_location='cpu', weights_only=True)
pts=p['points']; t=p['targets']
vel=pts[:,0,2].numpy(); aoa=pts[:,0,3].numpy()
print('corr(vel,aoa)=%.3f'%np.corrcoef(vel,aoa)[0,1])
# log-log reynolds exponent for C_D, controlling for aoa,aoa2
cd=t[:,0].numpy(); one=np.ones(len(vel))
X=np.array([one,aoa,aoa**2,np.log(vel)]).T
beta=np.linalg.lstsq(X,np.log(cd),rcond=None)[0]
print('log(C_D) vel-exponent (partial):',round(float(beta[3]),4))
# velocity distribution quantiles
print('vel quantiles:', np.quantile(vel,[0,.6,.7,.8,.9,1.0]).round(2))
print('count vel>=65:', int((vel>=65).sum()), ' vel<65:', int((vel<65).sum()))
print('aoa mean low vs high vel:', round(aoa[vel<65].mean(),3), round(aoa[vel>=65].mean(),3))
"corr(vel,aoa)=-0.015 log(C_D) vel-exponent (partial): -0.2106 vel quantiles: [31.28 57.88 62.32 66.6 70.35 74.92] count vel>=65: 148 vel<65: 482 aoa mean low vs high vel: 0.078 0.072
cd /app && cat > exp.py << 'PYEOF'
"""Flexible trainer + extrapolation-holdout harness for model selection."""
from __future__ import annotations
import json, os, sys, time, math
from pathlib import Path
import numpy as np
import torch
import torch.nn.functional as F
APP_DIR = Path('/app'); sys.path.insert(0, str(APP_DIR))
from candidate_model import build_model
from dataset import load_split
with (APP_DIR/'task_config.json').open() as fh:
CFG = json.load(fh)
def get_data():
pts, tg = load_split(APP_DIR/'data'/'train')
return pts, tg
def metrics(pred, true):
# pred,true: [N,2] physical units
err = pred-true
rmse = err.pow(2).mean(0).sqrt()
mae = err.abs().mean(0)
std = true.std(0).clamp_min(1e-8)
nrmse = rmse/std
bias = err.mean(0)
return {
'rmse_cd': rmse[0].item(), 'rmse_cl': rmse[1].item(),
'nrmse_cd': nrmse[0].item(), 'nrmse_cl': nrmse[1].item(),
'mean_nrmse': nrmse.mean().item(), 'worst_nrmse': nrmse.max().item(),
'mae_cd': mae[0].item(), 'bias_cd': bias[0].item(), 'bias_cl': bias[1].item(),
}
def train_eval(xtr_pts, ytr, xva_pts, yva, *,
epochs=120, bs=32, lr=1e-3, wd=1e-4, seed=0,
loss='huber', huber_beta=1.0, w_cd=1.0, w_cl=1.0,
feat_mean=None, feat_std=None,
re_aug=0.0, re_exp=-0.21, vel_lo=31.0, vel_hi=90.0,
pt_jitter=0.0, vel_jitter=0.0, drop_vel=False,
sched='cosine', verbose=False, seeds_ens=None,
return_model=False):
device='cpu'
pts, tg = xtr_pts, ytr
N = pts.shape[0]
# standardizers from training pts/targets
if feat_mean is None:
flat = pts.reshape(-1,4); feat_mean = flat.mean(0); feat_std = flat.std(0).clamp_min(1e-8)
targ_mean = tg.mean(0); targ_std = tg.std(0).clamp_min(1e-8)
if drop_vel:
feat_mean = feat_mean.clone(); feat_std = feat_std.clone()
def standardize(p):
return (p-feat_mean.view(1,1,-1))/feat_std.view(1,1,-1)
def run_seed(seed):
torch.manual_seed(seed); np.random.seed(seed)
model = build_model(CFG).to(device)
opt = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)
if sched=='cosine':
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=epochs)
else:
scheduler=None
for ep in range(epochs):
model.train()
idx = torch.randperm(N)
for s in range(0, N, bs):
bi = idx[s:s+bs]
pb = pts[bi].clone(); yb = tg[bi].clone()
# Reynolds velocity augmentation
if re_aug>0:
m = torch.rand(pb.shape[0])<re_aug
if m.any():
v_old = pb[m,0,2].clone()
v_new = torch.empty(m.sum()).uniform_(vel_lo, vel_hi)
ratio = (v_new/v_old)
pb[m,:,2] = v_new.view(-1,1)
yb[m,0] = yb[m,0]*ratio.pow(re_exp) # cd scaling
if vel_jitter>0:
f = torch.empty(pb.shape[0]).uniform_(1-vel_jitter,1+vel_jitter)
pb[:,:,2]=pb[:,:,2]*f.view(-1,1)
if pt_jitter>0:
pb[:,:,:2]=pb[:,:,:2]+torch.randn_like(pb[:,:,:2])*pt_jitter
xb = standardize(pb)
yb_s = (yb-targ_mean)/targ_std
opt.zero_grad(set_to_none=True)
out = model(xb)
if loss=='mse':
per = (out-yb_s).pow(2)
else:
per = F.huber_loss(out, yb_s, reduction='none', delta=huber_beta)
wl = per[:,0]*w_cd + per[:,1]*w_cl
l = wl.mean()
l.backward(); opt.step()
if scheduler: scheduler.step()
model.eval()
return model
seeds = seeds_ens if seeds_ens is not None else [seed]
models = [run_seed(s) for s in seeds]
…[truncated]train(low vel) n=482, holdout(high vel) n=148 holdout vel range: 65.0-74.9 baseline(60ep) time=39.4s rmse_cd=0.0009 rmse_cl=0.0680 nrmse_cd=0.2102 nrmse_cl=0.1121 mean_nrmse=0.1612 worst_nrmse=0.2102 mae_cd=0.0006 bias_cd=0.0005 bias_cl=0.0011
cd /app && cat > sweep.py << 'PYEOF'
import time, numpy as np, torch
from exp import get_data, train_eval
pts,tg = get_data()
vel = pts[:,0,2]; tr=vel<65; va=vel>=65
Xtr,Ytr,Xva,Yva = pts[tr],tg[tr],pts[va],tg[va]
def run(name, **kw):
t0=time.time()
# average 2 seeds for stability
ms=[train_eval(Xtr,Ytr,Xva,Yva, seed=s, **kw) for s in (0,1)]
agg={k:np.mean([m[k] for m in ms]) for k in ms[0]}
print('%-28s mean_nrmse=%.4f cd=%.4f cl=%.4f bias_cd=%+.5f t=%.0fs'%(
name, agg['mean_nrmse'], agg['nrmse_cd'], agg['nrmse_cl'], agg['bias_cd'], time.time()-t0))
return agg
base=dict(epochs=60, loss='huber', huber_beta=1.0)
run('baseline', **base)
run('drop_velocity', drop_vel=True, **base)
run('vel_jitter0.1', vel_jitter=0.1, **base)
run('re_aug0.5_exp-0.21', re_aug=0.5, re_exp=-0.21, **base)
run('re_aug1.0_exp-0.21', re_aug=1.0, re_exp=-0.21, **base)
run('re_aug0.7_exp-0.21', re_aug=0.7, re_exp=-0.21, **base)
run('re_aug0.7_exp-0.30', re_aug=0.7, re_exp=-0.30, **base)
run('re_aug0.7_exp0.0', re_aug=0.7, re_exp=0.0, **base)
PYEOF
python sweep.py[stderr] Shell command timed out. Tool interaction exceeded 300 seconds.
restart: true
<system>tool has been restarted.</system>
cd /app && cat > sweep.py << 'PYEOF'
import time, numpy as np, torch, sys
from exp import get_data, train_eval
pts,tg = get_data()
vel = pts[:,0,2]; tr=vel<65; va=vel>=65
Xtr,Ytr,Xva,Yva = pts[tr],tg[tr],pts[va],tg[va]
torch.set_num_threads(16)
def run(name, **kw):
t0=time.time()
ms=[train_eval(Xtr,Ytr,Xva,Yva, seed=s, **kw) for s in (0,1)]
agg={k:np.mean([m[k] for m in ms]) for k in ms[0]}
print('%-28s mean_nrmse=%.4f cd=%.4f cl=%.4f bias_cd=%+.5f t=%.0fs'%(
name, agg['mean_nrmse'], agg['nrmse_cd'], agg['nrmse_cl'], agg['bias_cd'], time.time()-t0), flush=True)
return agg
base=dict(epochs=60, loss='huber', huber_beta=1.0)
run('baseline', **base)
run('drop_velocity', drop_vel=True, **base)
run('re_aug0.5_exp-0.21', re_aug=0.5, re_exp=-0.21, **base)
run('re_aug1.0_exp-0.21', re_aug=1.0, re_exp=-0.21, **base)
run('re_aug0.7_exp-0.21', re_aug=0.7, re_exp=-0.21, **base)
run('re_aug0.7_exp-0.30', re_aug=0.7, re_exp=-0.30, **base)
run('re_aug0.7_exp0.0', re_aug=0.7, re_exp=0.0, **base)
print('DONE', flush=True)
PYEOF
nohup python sweep.py > sweep.log 2>&1 &
echo "started pid $!"started pid 349
cd /app && sleep 200 && cat sweep.log
baseline mean_nrmse=0.1628 cd=0.2060 cl=0.1195 bias_cd=+0.00051 t=59s drop_velocity mean_nrmse=0.1628 cd=0.2060 cl=0.1195 bias_cd=+0.00051 t=51s re_aug0.5_exp-0.21 mean_nrmse=0.1395 cd=0.1637 cl=0.1153 bias_cd=+0.00003 t=53s
cd /app && sleep 220 && cat sweep.log
baseline mean_nrmse=0.1628 cd=0.2060 cl=0.1195 bias_cd=+0.00051 t=59s drop_velocity mean_nrmse=0.1628 cd=0.2060 cl=0.1195 bias_cd=+0.00051 t=51s re_aug0.5_exp-0.21 mean_nrmse=0.1395 cd=0.1637 cl=0.1153 bias_cd=+0.00003 t=53s re_aug1.0_exp-0.21 mean_nrmse=0.1393 cd=0.2225 cl=0.0561 bias_cd=+0.00038 t=49s re_aug0.7_exp-0.21 mean_nrmse=0.1087 cd=0.1352 cl=0.0822 bias_cd=+0.00004 t=49s re_aug0.7_exp-0.30 mean_nrmse=0.1158 cd=0.1489 cl=0.0828 bias_cd=-0.00032 t=50s re_aug0.7_exp0.0 mean_nrmse=0.1918 cd=0.3051 cl=0.0786 bias_cd=+0.00096 t=56s DONE
cd /app && cat > sweep2.py << 'PYEOF'
import time, numpy as np, torch
from exp import get_data, train_eval
pts,tg = get_data()
vel = pts[:,0,2]; tr=vel<65; va=vel>=65
Xtr,Ytr,Xva,Yva = pts[tr],tg[tr],pts[va],tg[va]
torch.set_num_threads(16)
def run(name, seeds=(0,1), **kw):
t0=time.time()
ms=[train_eval(Xtr,Ytr,Xva,Yva, seed=s, **kw) for s in seeds]
agg={k:np.mean([m[k] for m in ms]) for k in ms[0]}
print('%-30s mean_nrmse=%.4f cd=%.4f cl=%.4f bias_cd=%+.5f t=%.0fs'%(
name, agg['mean_nrmse'], agg['nrmse_cd'], agg['nrmse_cl'], agg['bias_cd'], time.time()-t0), flush=True)
W=dict(re_aug=0.7, re_exp=-0.21, loss='huber', huber_beta=1.0)
run('w0.7_e100', epochs=100, **W)
run('w0.8_e80', epochs=80, **{**W,'re_aug':0.8})
run('w0.6_e80', epochs=80, **{**W,'re_aug':0.6})
run('w0.7_e80_jit005', epochs=80, pt_jitter=0.005, **W)
run('w0.7_e80_mse', epochs=80, **{**W,'loss':'mse'})
run('w0.7_e80_velhi85', epochs=80, vel_hi=85.0, **W)
run('w0.7_e80_wcd1.5', epochs=80, w_cd=1.5, **W)
print('DONE', flush=True)
PYEOF
nohup python sweep2.py > sweep2.log 2>&1 &
echo "started $!"started 418
cd /app && sleep 280 && cat sweep2.log
w0.7_e100 mean_nrmse=0.1394 cd=0.1703 cl=0.1085 bias_cd=+0.00001 t=109s w0.8_e80 mean_nrmse=0.1290 cd=0.1791 cl=0.0789 bias_cd=+0.00015 t=75s w0.6_e80 mean_nrmse=0.1449 cd=0.2320 cl=0.0578 bias_cd=+0.00032 t=77s
cd /app && sleep 270 && cat sweep2.log
w0.7_e100 mean_nrmse=0.1394 cd=0.1703 cl=0.1085 bias_cd=+0.00001 t=109s w0.8_e80 mean_nrmse=0.1290 cd=0.1791 cl=0.0789 bias_cd=+0.00015 t=75s w0.6_e80 mean_nrmse=0.1449 cd=0.2320 cl=0.0578 bias_cd=+0.00032 t=77s w0.7_e80_jit005 mean_nrmse=0.1876 cd=0.2654 cl=0.1099 bias_cd=+0.00028 t=77s w0.7_e80_mse mean_nrmse=0.1888 cd=0.3042 cl=0.0734 bias_cd=+0.00038 t=75s w0.7_e80_velhi85 mean_nrmse=0.1135 cd=0.1625 cl=0.0646 bias_cd=+0.00011 t=78s
cd /app && sleep 30 && tail -2 sweep2.log
w0.7_e80_wcd1.5 mean_nrmse=0.1174 cd=0.1676 cl=0.0672 bias_cd=+0.00013 t=82s DONE
cd /app && cat > conf.py << 'PYEOF'
import time, numpy as np, torch
from exp import get_data, train_eval
pts,tg = get_data()
vel = pts[:,0,2]; tr=vel<65; va=vel>=65
Xtr,Ytr,Xva,Yva = pts[tr],tg[tr],pts[va],tg[va]
torch.set_num_threads(16)
def run(name, seeds, **kw):
t0=time.time()
m=train_eval(Xtr,Ytr,Xva,Yva, seeds_ens=list(seeds), **kw)
print('%-26s ENS%d mean_nrmse=%.4f cd=%.4f cl=%.4f worst=%.4f bias_cd=%+.5f t=%.0fs'%(
name, len(seeds), m['mean_nrmse'], m['nrmse_cd'], m['nrmse_cl'], m['worst_nrmse'], m['bias_cd'], time.time()-t0), flush=True)
W=dict(re_exp=-0.21, loss='huber', huber_beta=1.0)
run('re0.75_e70', range(5), epochs=70, re_aug=0.75, **W)
run('re0.80_e70', range(5), epochs=70, re_aug=0.80, **W)
run('re0.75_e60', range(5), epochs=60, re_aug=0.75, **W)
print('DONE', flush=True)
PYEOF
nohup python conf.py > conf.log 2>&1 &
echo "started $!"started 495
cd /app && cat >> exp.py << 'PYEOF'
def train_swa_eval(xtr_pts, ytr, xva_pts, yva, *,
epochs=80, swa_start=45, swa_lr=5e-4, bs=32, lr=1e-3, wd=1e-4, seed=0,
loss='huber', huber_beta=1.0, w_cd=1.0, w_cl=1.0,
re_aug=0.75, re_exp=-0.21, vel_lo=31.0, vel_hi=90.0,
feat_mean=None, feat_std=None, bn_update_passes=8,
return_model=False):
"""Single-run SWA: cosine to swa_start, then constant swa_lr collecting weight avg."""
from torch.optim.swa_utils import AveragedModel, update_bn
device='cpu'; pts,tg = xtr_pts, ytr; N=pts.shape[0]
if feat_mean is None:
flat=pts.reshape(-1,4); feat_mean=flat.mean(0); feat_std=flat.std(0).clamp_min(1e-8)
targ_mean=tg.mean(0); targ_std=tg.std(0).clamp_min(1e-8)
def standardize(p): return (p-feat_mean.view(1,1,-1))/feat_std.view(1,1,-1)
def aug_batch(pb, yb):
if re_aug>0:
m=torch.rand(pb.shape[0])<re_aug
if m.any():
v_old=pb[m,0,2].clone(); v_new=torch.empty(int(m.sum())).uniform_(vel_lo,vel_hi)
pb[m,:,2]=v_new.view(-1,1); yb[m,0]=yb[m,0]*(v_new/v_old).pow(re_exp)
return pb, yb
torch.manual_seed(seed); np.random.seed(seed)
model=build_model(CFG).to(device)
opt=torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)
sched=torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=swa_start)
swa_model=AveragedModel(model)
n_avg=0
for ep in range(epochs):
model.train(); idx=torch.randperm(N)
for s in range(0,N,bs):
bi=idx[s:s+bs]; pb=pts[bi].clone(); yb=tg[bi].clone()
pb,yb=aug_batch(pb,yb)
xb=standardize(pb); yb_s=(yb-targ_mean)/targ_std
opt.zero_grad(set_to_none=True); out=model(xb)
per=(out-yb_s).pow(2) if loss=='mse' else F.huber_loss(out,yb_s,reduction='none',delta=huber_beta)
(per[:,0]*w_cd+per[:,1]*w_cl).mean().backward(); opt.step()
if ep<swa_start:
sched.step()
else:
for g in opt.param_groups: g['lr']=swa_lr
swa_model.update_parameters(model); n_avg+=1
# finalize: use swa weights if collected else last model
final = swa_model.module if n_avg>0 else model
# recompute BN stats over augmented training distribution
def bn_loader():
for _ in range(bn_update_passes):
idx=torch.randperm(N)
for s in range(0,N,bs):
bi=idx[s:s+bs]; pb=pts[bi].clone(); yb=tg[bi].clone()
pb,_=aug_batch(pb,yb)
yield standardize(pb)
# manual BN reset+update
for m_ in final.modules():
if isinstance(m_, torch.nn.modules.batchnorm._BatchNorm):
m_.reset_running_stats(); m_.momentum=None
final.train()
with torch.no_grad():
for xb in bn_loader(): final(xb)
final.eval()
with torch.no_grad():
pred=final(standardize(xva_pts))*targ_std+targ_mean
m=metrics(pred, yva)
if return_model:
return m, final, (feat_mean,feat_std,targ_mean,targ_std)
return m
PYEOF
echo "added train_swa_eval"; sleep 120; cat conf.logadded train_swa_eval
cd /app && sleep 230 && cat conf.log
re0.75_e70 ENS5 mean_nrmse=0.1607 cd=0.2661 cl=0.0552 worst=0.2661 bias_cd=+0.00049 t=221s re0.80_e70 ENS5 mean_nrmse=0.1026 cd=0.1542 cl=0.0510 worst=0.1542 bias_cd=+0.00015 t=154s
cd /app && sleep 130 && cat conf.log
re0.75_e70 ENS5 mean_nrmse=0.1607 cd=0.2661 cl=0.0552 worst=0.2661 bias_cd=+0.00049 t=221s re0.80_e70 ENS5 mean_nrmse=0.1026 cd=0.1542 cl=0.0510 worst=0.1542 bias_cd=+0.00015 t=154s re0.75_e60 ENS5 mean_nrmse=0.1208 cd=0.1794 cl=0.0621 worst=0.1794 bias_cd=+0.00027 t=132s DONE
cd /app && cat > swa_test.py << 'PYEOF'
import time, numpy as np, torch
from exp import get_data, train_swa_eval, train_eval
pts,tg = get_data()
vel = pts[:,0,2]; tr=vel<65; va=vel>=65
Xtr,Ytr,Xva,Yva = pts[tr],tg[tr],pts[va],tg[va]
torch.set_num_threads(16)
W=dict(re_aug=0.8, re_exp=-0.21, loss='huber', huber_beta=1.0, vel_hi=90.0)
print('--- SWA per-seed (holdout vel>=65) ---', flush=True)
swres=[]
for s in range(4):
t0=time.time()
m=train_swa_eval(Xtr,Ytr,Xva,Yva, seed=s, epochs=80, swa_start=45, swa_lr=5e-4, bn_update_passes=6, **W)
swres.append(m)
print('swa seed%d mean=%.4f cd=%.4f cl=%.4f bias_cd=%+.5f t=%.0fs'%(s,m['mean_nrmse'],m['nrmse_cd'],m['nrmse_cl'],m['bias_cd'],time.time()-t0), flush=True)
agg={k:np.mean([m[k] for m in swres]) for k in swres[0]}
sd={k:np.std([m[k] for m in swres]) for k in swres[0]}
print('SWA avg mean=%.4f(+-%.4f) cd=%.4f(+-%.4f) cl=%.4f bias_cd=%+.5f'%(agg['mean_nrmse'],sd['mean_nrmse'],agg['nrmse_cd'],sd['nrmse_cd'],agg['nrmse_cl'],agg['bias_cd']), flush=True)
print('DONE', flush=True)
PYEOF
nohup python swa_test.py > swa_test.log 2>&1 &
echo "started $!"started 573
cd /app && sleep 280 && cat swa_test.log
--- SWA per-seed (holdout vel>=65) --- swa seed0 mean=0.1115 cd=0.1636 cl=0.0595 bias_cd=+0.00013 t=47s swa seed1 mean=0.1045 cd=0.1475 cl=0.0615 bias_cd=+0.00013 t=40s swa seed2 mean=0.1125 cd=0.1499 cl=0.0750 bias_cd=+0.00006 t=40s swa seed3 mean=0.1027 cd=0.1505 cl=0.0549 bias_cd=+0.00009 t=40s SWA avg mean=0.1078(+-0.0043) cd=0.1529(+-0.0063) cl=0.0627 bias_cd=+0.00011 DONE
cd /app && cat > train_final.py << 'PYEOF'
"""Final model: Reynolds-augmented SWA PointNet trained on train+val.
Physics-grounded design (see analysis):
- C_L ~ f(shape, aoa), ~velocity-independent.
- C_D carries a turbulent-skin-friction Reynolds trend C_D ~ V^(-0.21),
fit from the training data (partial exponent controlling for aoa).
The scored cases sit ABOVE the training inlet-velocity range, so we augment
by resampling velocity across/above that range and rescaling C_D by the
physical law, turning extrapolation into interpolation. SWA yields a single,
low-variance checkpoint. Loss is Huber in standardized target space so both
coefficients are balanced (aligns with per-target NRMSE).
"""
from __future__ import annotations
import json, sys, time
from pathlib import Path
import numpy as np, torch
import torch.nn.functional as F
from torch.optim.swa_utils import AveragedModel
APP=Path('/app'); sys.path.insert(0,str(APP))
from candidate_model import build_model
from dataset import load_split
CFG=json.load(open(APP/'task_config.json'))
# ---- hyperparameters selected via extrapolation-holdout (train vel<65 -> test vel>=65) ----
RE_AUG=0.8; RE_EXP=-0.21; VEL_LO=31.0; VEL_HI=90.0
EPOCHS=100; SWA_START=55; LR=1e-3; SWA_LR=5e-4; WD=1e-4; BS=32
HUBER_BETA=1.0; BN_PASSES=10; SEED=0
def main():
torch.set_num_threads(16)
torch.manual_seed(SEED); np.random.seed(SEED)
ptr,ttr=load_split(APP/'data'/'train'); pva,tva=load_split(APP/'data'/'val')
pts=torch.cat([ptr,pva],0); tg=torch.cat([ttr,tva],0)
N=pts.shape[0]; print(f'[final] training on {N} cases (train+val)',flush=True)
flat=pts.reshape(-1,4); feat_mean=flat.mean(0); feat_std=flat.std(0).clamp_min(1e-8)
targ_mean=tg.mean(0); targ_std=tg.std(0).clamp_min(1e-8)
def standardize(p): return (p-feat_mean.view(1,1,-1))/feat_std.view(1,1,-1)
def aug(pb,yb):
m=torch.rand(pb.shape[0])<RE_AUG
if m.any():
v_old=pb[m,0,2].clone(); v_new=torch.empty(int(m.sum())).uniform_(VEL_LO,VEL_HI)
pb[m,:,2]=v_new.view(-1,1); yb[m,0]=yb[m,0]*(v_new/v_old).pow(RE_EXP)
return pb,yb
model=build_model(CFG)
opt=torch.optim.Adam(model.parameters(),lr=LR,weight_decay=WD)
sched=torch.optim.lr_scheduler.CosineAnnealingLR(opt,T_max=SWA_START)
swa=AveragedModel(model); nsnap=0; t0=time.time()
for ep in range(EPOCHS):
model.train(); idx=torch.randperm(N)
for s in range(0,N,BS):
bi=idx[s:s+BS]; pb=pts[bi].clone(); yb=tg[bi].clone()
pb,yb=aug(pb,yb); xb=standardize(pb); ys=(yb-targ_mean)/targ_std
opt.zero_grad(set_to_none=True)
per=F.huber_loss(model(xb),ys,reduction='none',delta=HUBER_BETA)
per.mean().backward(); opt.step()
if ep<SWA_START: sched.step()
else:
for g in opt.param_groups: g['lr']=SWA_LR
swa.update_parameters(model); nsnap+=1
final=swa.module
print(f'[final] trained {EPOCHS} ep, {nsnap} SWA snapshots, {time.time()-t0:.0f}s',flush=True)
# recompute BN running stats over augmented training distribution
for mod in final.modules():
if isinstance(mod,torch.nn.modules.batchnorm._BatchNorm):
mod.reset_running_stats(); mod.momentum=None
final.train()
with torch.no_grad():
for _ in range(BN_PASSES):
idx=torch.randperm(N)
for s in range(0,N,BS):
bi=idx[s:s+BS]; pb=pts[bi].clone(); yb=tg[bi].clone()
pb,_=aug(pb,yb); final(standardize(pb))
final.eval()
out=APP/'outputs'; out.mkdir(parents=True,exist_ok=True)
sd={k:v.cpu() for k,v in final.state_dict().items() if not k.startswith('n_averaged')}
torch.save({'state_dict':sd,'feat_mean':feat_mean.cpu(),'feat_std':feat_std.cpu(),
'targ_mean':targ_mean.cpu(),'targ_std':targ_std.cpu()}, out/'model.pt')
print(f'[final] wrote {out/"model.pt"}',flush=True)
if __name__=='__main__': main()
PYEOF
echo "written train
…[truncated]written train_final.py
cd /app && nohup python train_final.py > train_final.log 2>&1 & echo "started $!"; sleep 150; cat train_final.log
started 640 [final] training on 710 cases (train+val) [final] trained 100 ep, 45 SWA snapshots, 75s [final] wrote /app/outputs/model.pt
cd /app && cat > validate.py << 'PYEOF'
import json, sys, numpy as np, torch
from pathlib import Path
APP=Path('/app'); sys.path.insert(0,str(APP))
from candidate_model import build_model
from dataset import load_split
CFG=json.load(open(APP/'task_config.json'))
ck=torch.load(APP/'outputs'/'model.pt', weights_only=True)
print('checkpoint keys:', list(ck.keys()))
for k in ['feat_mean','feat_std','targ_mean','targ_std']:
assert k in ck, f'MISSING {k}'
print(f' {k} shape={tuple(ck[k].shape)} vals={ck[k].numpy().round(4)}')
# load exactly as grader would
model=build_model(CFG)
missing,unexpected=model.load_state_dict(ck['state_dict'], strict=False)
print('missing keys:', missing)
print('unexpected keys:', unexpected)
model.eval()
fm,fs=ck['feat_mean'],ck['feat_std']; tm,ts=ck['targ_mean'],ck['targ_std']
def predict(pts):
x=(pts-fm.view(1,1,-1))/fs.view(1,1,-1)
with torch.no_grad():
out=model(x)
return out*ts+tm
# ---- public_test (HIGH velocity, the scored regime) ----
pt,_=load_split(APP/'data'/'public_test')
pred=predict(pt)
vel=pt[:,0,2].numpy(); aoa=pt[:,0,3].numpy()
cd=pred[:,0].numpy(); cl=pred[:,1].numpy()
print('\n=== public_test predictions (vel %.1f-%.1f) ==='%(vel.min(),vel.max()))
print('C_D: min=%.4f max=%.4f mean=%.4f (train mean 0.0128)'%(cd.min(),cd.max(),cd.mean()))
print('C_L: min=%.4f max=%.4f mean=%.4f (train mean 0.660)'%(cl.min(),cl.max(),cl.mean()))
print('any C_D<=0?', bool((cd<=0).any()), ' any NaN?', bool(np.isnan(cd).any() or np.isnan(cl).any()))
# physics checks
print('corr(pred C_L, aoa)=%.3f (expect ~0.94)'%np.corrcoef(cl,aoa)[0,1])
print('corr(pred C_D, aoa^2)=%.3f (expect high)'%np.corrcoef(cd,aoa**2)[0,1])
# C_L slope vs aoa
A=np.array([aoa,np.ones_like(aoa)]).T
sl=np.linalg.lstsq(A,cl,rcond=None)[0]
print('pred C_L ~ aoa slope=%.2f intercept=%.2f (train:5.68,0.23)'%(sl[0],sl[1]))
PYEOF
python validate.pycheckpoint keys: ['state_dict', 'feat_mean', 'feat_std', 'targ_mean', 'targ_std'] feat_mean shape=(4,) vals=[3.83600e-01 7.90000e-03 5.31994e+01 7.68000e-02] feat_std shape=(4,) vals=[ 0.3956 0.0333 12.6528 0.0955] targ_mean shape=(2,) vals=[0.0128 0.6619] targ_std shape=(2,) vals=[0.0051 0.5783] missing keys: [] unexpected keys: [] === public_test predictions (vel 75.0-81.6) === C_D: min=0.0072 max=0.0258 mean=0.0118 (train mean 0.0128) C_L: min=-0.5176 max=1.6655 mean=0.6668 (train mean 0.660) any C_D<=0? False any NaN? False corr(pred C_L, aoa)=0.947 (expect ~0.94) corr(pred C_D, aoa^2)=0.940 (expect high) pred C_L ~ aoa slope=5.44 intercept=0.26 (train:5.68,0.23)
cd /app && python -c "
import json,sys,numpy as np,torch
from pathlib import Path
APP=Path('/app'); sys.path.insert(0,str(APP))
from candidate_model import build_model
from dataset import load_split
CFG=json.load(open(APP/'task_config.json'))
ck=torch.load(APP/'outputs/model.pt',weights_only=True)
model=build_model(CFG); model.load_state_dict(ck['state_dict']); model.eval()
fm,fs,tm,ts=ck['feat_mean'],ck['feat_std'],ck['targ_mean'],ck['targ_std']
def predict(pts):
x=(pts-fm.view(1,1,-1))/fs.view(1,1,-1)
with torch.no_grad(): out=model(x)
return out*ts+tm
pt,_=load_split(APP/'data/public_test')
# velocity sweep on the SAME shapes/aoa
print('vel meanC_D meanC_L (C_D ratio vs v=53)')
base=None
for v in [50,55,60,65,70,75,78,82,86,90,95]:
p=pt.clone(); p[:,:,2]=float(v)
pr=predict(p); mcd=pr[:,0].mean().item(); mcl=pr[:,1].mean().item()
if v==50: base=mcd
print('%3d %.5f %.4f %.3f'%(v,mcd,mcl,mcd/base))
# compare to physical law (v/50)^-0.21
print()
for v in [78,90]:
print('physical (v=%d/50)^-0.21 = %.3f'%(v,(v/50.)**-0.21))
"vel meanC_D meanC_L (C_D ratio vs v=53) 50 0.01299 0.6654 1.000 55 0.01274 0.6649 0.980 60 0.01251 0.6645 0.963 65 0.01230 0.6651 0.947 70 0.01211 0.6663 0.932 75 0.01193 0.6669 0.918 78 0.01182 0.6668 0.909 82 0.01168 0.6666 0.899 86 0.01157 0.6669 0.890 90 0.01149 0.6683 0.884 95 0.01145 0.6728 0.882 physical (v=78/50)^-0.21 = 0.911 physical (v=90/50)^-0.21 = 0.884
cd /app && python -c "
import json,sys,torch
from pathlib import Path
APP=Path('/app'); sys.path.insert(0,str(APP))
from candidate_model import build_model
CFG=json.load(open(APP/'task_config.json'))
ck=torch.load(APP/'outputs/model.pt',weights_only=True)
m=build_model(CFG)
m.load_state_dict(ck['state_dict'], strict=True) # must not raise
print('STRICT load OK; param tensors:', len(ck['state_dict']))
print('total params:', sum(p.numel() for p in m.parameters()))
import os; print('checkpoint size (KB): %.1f'%(os.path.getsize(APP/'outputs/model.pt')/1024))
"STRICT load OK; param tensors: 27 total params: 83842 checkpoint size (KB): 338.5
cd /app && cat > headtohead.py << 'PYEOF'
import numpy as np, torch
from exp import get_data, train_eval, train_swa_eval
pts,tg=get_data(); vel=pts[:,0,2]; tr=vel<65; va=vel>=65
Xtr,Ytr,Xva,Yva=pts[tr],tg[tr],pts[va],tg[va]
torch.set_num_threads(16)
def avg(fn,**kw):
res=[fn(Xtr,Ytr,Xva,Yva,seed=s,**kw) for s in range(3)]
return {k:np.mean([r[k] for r in res]) for k in res[0]}
# exact provided-baseline recipe: plain MSE, 40 epochs, no aug, constant LR
b=avg(train_eval, epochs=40, loss='mse', re_aug=0.0, sched='none')
print('BASELINE (MSE,40ep,no aug): mean=%.4f cd=%.4f cl=%.4f bias_cd=%+.5f'%(b['mean_nrmse'],b['nrmse_cd'],b['nrmse_cl'],b['bias_cd']),flush=True)
# my method: Reynolds-aug SWA
m=avg(train_swa_eval, epochs=100, swa_start=55, swa_lr=5e-4, re_aug=0.8, re_exp=-0.21, loss='huber', huber_beta=1.0, vel_hi=90.0, bn_update_passes=10)
print('OURS (ReynoldsAug+SWA): mean=%.4f cd=%.4f cl=%.4f bias_cd=%+.5f'%(m['mean_nrmse'],m['nrmse_cd'],m['nrmse_cl'],m['bias_cd']),flush=True)
print('improvement: mean %.1f%% cd %.1f%%'%(100*(1-m['mean_nrmse']/b['mean_nrmse']),100*(1-m['nrmse_cd']/b['nrmse_cd'])),flush=True)
print('DONE',flush=True)
PYEOF
nohup python headtohead.py > headtohead.log 2>&1 &
echo "started $!"; sleep 200; cat headtohead.logstarted 793 BASELINE (MSE,40ep,no aug): mean=0.3157 cd=0.4335 cl=0.1979 bias_cd=+0.00124
cd /app && sleep 140 && cat headtohead.log
BASELINE (MSE,40ep,no aug): mean=0.3157 cd=0.4335 cl=0.1979 bias_cd=+0.00124 OURS (ReynoldsAug+SWA): mean=0.1099 cd=0.1615 cl=0.0583 bias_cd=+0.00016 improvement: mean 65.2% cd 62.7% DONE
cd /app && ls -la outputs/ && echo "--- final integrity re-check ---" && python -c "
import json,sys,torch,numpy as np
from pathlib import Path
APP=Path('/app'); sys.path.insert(0,str(APP))
from candidate_model import build_model
from dataset import load_split
CFG=json.load(open(APP/'task_config.json'))
ck=torch.load(APP/'outputs/model.pt',weights_only=True)
assert set(ck)>= {'state_dict','feat_mean','feat_std','targ_mean','targ_std'}
for k in ['feat_mean','feat_std','targ_mean','targ_std']:
t=ck[k]; assert t.shape[0]==(4 if 'feat' in k else 2) and torch.isfinite(t).all()
m=build_model(CFG); m.load_state_dict(ck['state_dict'],strict=True); m.eval()
# full inference on public_test end-to-end
pt,_=load_split(APP/'data/public_test')
x=(pt-ck['feat_mean'].view(1,1,-1))/ck['feat_std'].view(1,1,-1)
with torch.no_grad(): out=m(x)*ck['targ_std']+ck['targ_mean']
print('OK: strict-load + inference clean. pred shape',tuple(out.shape),
'| C_D>0:',bool((out[:,0]>0).all()),'| finite:',bool(torch.isfinite(out).all()))
print('C_D mean=%.4f (high-vel, <train 0.0128) | C_L mean=%.3f'%(out[:,0].mean(),out[:,1].mean()))
"total 347 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 19 22:10 . drwxrws--- 5 h2tagent h2tagent 4096 Jun 19 22:12 .. -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:10 model.pt --- final integrity re-check --- OK: strict-load + inference clean. pred shape (100, 2) | C_D>0: True | finite: True C_D mean=0.0118 (high-vel, <train 0.0128) | C_L mean=0.667
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: airfrans-high-reynolds-drag-extrapolation # attempt: 2 status: completed raw_score: 0.38 # reward rule: continuous: score 0.38 < 0.5 # => reward = 0.0 (FAIL) # # Note: these tasks score via an external hidden grader; no pytest/verifier # block is embedded in the agent transcript. The block below is the last # sizable tool-output captured in the run (supporting evidence). ---------------------------------------------------------------------- total 347 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 19 22:10 . drwxrws--- 5 h2tagent h2tagent 4096 Jun 19 22:12 .. -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:10 model.pt --- final integrity re-check --- OK: strict-load + inference clean. pred shape (100, 2) | C_D>0: True | finite: True C_D mean=0.0118 (high-vel, <train 0.0128) | C_L mean=0.667
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_c8d4433af7b34581. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_c8d4433af7b34581 · verifier authoritative; classifier explanatory.