tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---CONFIG---" && cat task_config.json && echo "---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
---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"]
}
}
---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 "===MODEL===" && cat candidate_model.py && echo "===DATASET===" && cat dataset.py && echo "===TRAIN===" && cat train.py
===MODEL===
"""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===
"""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===
"""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, per-target loss bal
…[truncated]cd /app && python3 -c "
import torch, numpy as np
for split in ['train','val','public_test']:
p = torch.load(f'data/{split}/split.pt', weights_only=True)
print('===',split,'===')
print('keys:', list(p.keys()))
pts = p['points'].float()
print('points shape', pts.shape, pts.dtype)
# per-channel stats
flat = pts.reshape(-1,4)
for i,nm in enumerate(['x','y','vel','aoa']):
print(f' {nm}: min={flat[:,i].min():.4f} max={flat[:,i].max():.4f} mean={flat[:,i].mean():.4f} std={flat[:,i].std():.4f}')
if 'targets' in p:
t = p['targets'].float()
print('targets shape', t.shape)
for i,nm in enumerate(['C_D','C_L']):
print(f' {nm}: min={t[:,i].min():.4f} max={t[:,i].max():.4f} mean={t[:,i].mean():.4f} std={t[:,i].std():.4f}')
"
=== train === keys: ['points', 'targets'] points shape torch.Size([630, 256, 4]) torch.float32 x: min=-0.0070 max=1.0000 mean=0.3835 std=0.3956 y: min=-0.0997 max=0.1627 mean=0.0079 std=0.0335 vel: min=31.2830 max=74.9240 mean=52.9737 std=12.7859 aoa: min=-0.0862 max=0.2565 mean=0.0764 std=0.0956 targets shape torch.Size([630, 2]) C_D: min=0.0073 max=0.0459 mean=0.0128 std=0.0051 C_L: min=-0.5336 max=1.8501 mean=0.6596 std=0.5762 === val === keys: ['points', 'targets'] points shape torch.Size([80, 256, 4]) torch.float32 x: min=-0.0033 max=1.0000 mean=0.3846 std=0.3959 y: min=-0.0939 max=0.1366 mean=0.0078 std=0.0323 vel: min=31.4680 max=74.1620 mean=54.9773 std=11.3969 aoa: min=-0.0836 max=0.2512 mean=0.0799 std=0.0947 targets shape torch.Size([80, 2]) C_D: min=0.0076 max=0.0346 mean=0.0127 std=0.0053 C_L: min=-0.4803 max=1.8003 mean=0.6795 std=0.5978 === public_test === keys: ['points'] points shape torch.Size([100, 256, 4]) torch.float32 x: min=-0.0055 max=1.0000 mean=0.3761 std=0.3946 y: min=-0.0961 max=0.1528 mean=0.0088 std=0.0346 vel: min=75.0300 max=81.5930 mean=78.2086 std=1.9658 aoa: min=-0.0857 max=0.2582 mean=0.0757 std=0.0961
cd /app && python3 -c "
import torch, numpy as np
p = torch.load('data/train/split.pt', weights_only=True)
pts = p['points'].float().numpy(); t = p['targets'].float().numpy()
# per-case scalars
vel = pts[:,0,2]; aoa = pts[:,0,3]
# verify vel/aoa constant across points
print('vel spread within case (max):', np.abs(pts[:,:,2]-vel[:,None]).max())
print('aoa spread within case (max):', np.abs(pts[:,:,3]-aoa[:,None]).max())
CD=t[:,0]; CL=t[:,1]
def corr(a,b): return np.corrcoef(a,b)[0,1]
print('corr(vel,CD)=%.3f corr(aoa,CD)=%.3f'%(corr(vel,CD),corr(aoa,CD)))
print('corr(vel,CL)=%.3f corr(aoa,CL)=%.3f'%(corr(vel,CL),corr(aoa,CL)))
print('corr(vel,log CD)=%.3f'%corr(vel,np.log(CD)))
# geometry descriptors: max thickness, camber
x=pts[:,:,0]; y=pts[:,:,1]
thick = y.max(1)-y.min(1)
print('thickness range', thick.min(), thick.max())
print('corr(thick,CD)=%.3f corr(thick,CL)=%.3f'%(corr(thick,CD),corr(thick,CL)))
"
vel spread within case (max): 0.0 aoa spread within case (max): 0.0 corr(vel,CD)=-0.154 corr(aoa,CD)=0.739 corr(vel,CL)=-0.022 corr(aoa,CL)=0.943 corr(vel,log CD)=-0.170 thickness range 0.0509462 0.2018896 corr(thick,CD)=0.073 corr(thick,CL)=-0.023
cd /app && python3 -c "
import torch, numpy as np
p = torch.load('data/train/split.pt', weights_only=True)
pts = p['points'].float().numpy(); t = p['targets'].float().numpy()
vel = pts[:,0,2]; aoa = pts[:,0,3]; CD=t[:,0]; CL=t[:,1]
x=pts[:,:,0]; y=pts[:,:,1]
thick = y.max(1)-y.min(1)
camber = 0.5*(y.max(1)+y.min(1))
# Build design matrix for log CD
import numpy as np
logCD=np.log(CD); logV=np.log(vel)
X=np.column_stack([np.ones_like(aoa),aoa,aoa**2,logV,thick,camber])
beta,res,rank,sv=np.linalg.lstsq(X,logCD,rcond=None)
pred=X@beta
ss=1-((logCD-pred)**2).sum()/((logCD-logCD.mean())**2).sum()
print('logCD fit R2=%.3f'%ss)
print('coef: const=%.3f aoa=%.3f aoa2=%.3f logV=%.3f thick=%.3f camber=%.3f'%tuple(beta))
print('--> CD ~ V^(%.3f)'%beta[3])
# residual std
print('resid std (log units)=%.4f'%(logCD-pred).std())
# Now CL linear model
X2=np.column_stack([np.ones_like(aoa),aoa,logV,thick,camber])
beta2,_,_,_=np.linalg.lstsq(X2,CL,rcond=None)
pred2=X2@beta2
ss2=1-((CL-pred2)**2).sum()/((CL-CL.mean())**2).sum()
print('CL fit R2=%.3f coef const=%.3f aoa=%.3f logV=%.3f thick=%.3f camber=%.3f'%((ss2,)+tuple(beta2)))
"
logCD fit R2=0.895 coef: const=-4.120 aoa=0.306 aoa2=15.315 logV=-0.211 thick=1.545 camber=3.884 --> CD ~ V^(-0.211) resid std (log units)=0.1076 CL fit R2=0.979 coef const=0.007 aoa=5.727 logV=0.013 thick=-0.516 camber=10.997
cd /app && python3 -c "
import torch, numpy as np
def load(s):
p=torch.load(f'data/{s}/split.pt',weights_only=True)
pts=p['points'].float().numpy(); t=p['targets'].float().numpy()
return pts,t
ptr,ttr=load('train'); pv,tv=load('val')
def feats(pts):
vel=pts[:,0,2];aoa=pts[:,0,3];y=pts[:,:,1]
thick=y.max(1)-y.min(1);camber=0.5*(y.max(1)+y.min(1))
return vel,aoa,thick,camber
vel,aoa,thick,camber=feats(ptr);CD=ttr[:,0];CL=ttr[:,1]
logCD=np.log(CD);logV=np.log(vel)
# Fit with richer geometry to isolate logV exponent
X=np.column_stack([np.ones_like(aoa),aoa,aoa**2,aoa**3,logV,thick,thick**2,camber,camber*aoa])
beta,_,_,_=np.linalg.lstsq(X,logCD,rcond=None)
pred=X@beta;ss=1-((logCD-pred)**2).sum()/((logCD-logCD.mean())**2).sum()
print('rich logCD R2=%.3f, logV exponent=%.3f'%(ss,beta[4]))
resid=logCD-pred
# check residual correlation with logV (should be ~0 if power law captured)
print('corr(resid,logV)=%.3f'%np.corrcoef(resid,logV)[0,1])
# per aoa-tercile exponent
order=np.argsort(aoa)
for name,idx in [('low-aoa',order[:210]),('mid',order[210:420]),('hi',order[420:])]:
a=aoa[idx];v=logV[idx];c=logCD[idx];th=thick[idx];cm=camber[idx]
Xl=np.column_stack([np.ones_like(a),a,a**2,v,th,cm]);b,_,_,_=np.linalg.lstsq(Xl,c,rcond=None)
print(f' {name}: exponent={b[3]:.3f} (n={len(idx)}, aoa range {a.min():.3f}-{a.max():.3f})')
# validate exponent predicts val CD: use train model to predict val logCD
velv,aoav,thv,cmv=feats(pv);CDv=tv[:,0]
Xv=np.column_stack([np.ones_like(aoav),aoav,aoav**2,aoav**3,np.log(velv),thv,thv**2,cmv,cmv*aoav])
predv=Xv@beta
print('val logCD R2 using train fit=%.3f'%(1-((np.log(CDv)-predv)**2).sum()/((np.log(CDv)-np.log(CDv).mean())**2).sum()))
"
rich logCD R2=0.908, logV exponent=-0.198 corr(resid,logV)=0.000 low-aoa: exponent=-0.200 (n=210, aoa range -0.086-0.019) mid: exponent=-0.179 (n=210, aoa range 0.019-0.126) hi: exponent=-0.216 (n=210, aoa range 0.126-0.257) val logCD R2 using train fit=0.964
cd /app && python3 -c "
import torch, numpy as np
p=torch.load('data/train/split.pt',weights_only=True)
pts=p['points'].float().numpy(); t=p['targets'].float().numpy()
vel=pts[:,0,2];aoa=pts[:,0,3];y=pts[:,:,1]
thick=y.max(1)-y.min(1);camber=0.5*(y.max(1)+y.min(1));CL=t[:,1]
# CL model residual vs velocity
X=np.column_stack([np.ones_like(aoa),aoa,aoa**2,thick,camber,camber*aoa])
beta,_,_,_=np.linalg.lstsq(X,CL,rcond=None)
resid=CL-X@beta
print('CL R2(no vel)=%.3f, corr(resid,vel)=%.3f, corr(resid,logV)=%.3f'%(
1-(resid**2).sum()/((CL-CL.mean())**2).sum(), np.corrcoef(resid,vel)[0,1], np.corrcoef(resid,np.log(vel))[0,1]))
# add logV, see if it helps
X2=np.column_stack([X,np.log(vel)])
b2,_,_,_=np.linalg.lstsq(X2,CL,rcond=None)
r2=CL-X2@b2
print('CL R2(with logV)=%.3f, logV coef=%.4f'%(1-(r2**2).sum()/((CL-CL.mean())**2).sum(), b2[-1]))
"
echo "---torch/gpu---"
python3 -c "import torch;print('torch',torch.__version__,'cuda',torch.cuda.is_available())"
nprocCL R2(no vel)=0.981, corr(resid,vel)=0.029, corr(resid,logV)=0.028 CL R2(with logV)=0.981, logV coef=0.0090 ---torch/gpu--- torch 2.5.1+cu124 cuda False 16
cd /app && python3 -c "
import torch, numpy as np
def load(s):
p=torch.load(f'data/{s}/split.pt',weights_only=True)
return p['points'].float(), p['targets'].float()
ptr,ttr=load('train'); pv,tv=load('val')
pts=torch.cat([ptr,pv]); tg=torch.cat([ttr,tv])
vel=pts[:,0,2].numpy()
print('combined N=',len(vel))
for thr in [64,66,68,70,72]:
n_hi=(vel>=thr).sum(); print(f' vel>={thr}: {n_hi} hold-out, {len(vel)-n_hi} train')
print('percentiles:',np.percentile(vel,[0,25,50,75,90,95,100]).round(2))
"combined N= 710 vel>=64: 183 hold-out, 527 train vel>=66: 146 hold-out, 564 train vel>=68: 104 hold-out, 606 train vel>=70: 77 hold-out, 633 train vel>=72: 50 hold-out, 660 train percentiles: [31.28 42.27 53.87 64.16 70.3 72.69 74.92]
cd /app && cat > /app/exp.py << 'PY'
"""Experiment harness: compare training recipes on a real extrapolation split."""
from __future__ import annotations
import json, os, sys, time, copy
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
sys.path.insert(0, '/app')
from candidate_model import build_model
CFG = json.load(open('/app/task_config.json'))
CD_EXP = -0.20 # empirical C_D ~ V^exp (turbulent skin-friction-like)
def load(s):
p = torch.load(f'/app/data/{s}/split.pt', weights_only=True)
return p['points'].float(), p['targets'].float()
def standardizers(points, targets):
flat = points.reshape(-1, points.shape[-1])
fm, fs = flat.mean(0), flat.std(0).clamp_min(1e-8)
tm, ts = targets.mean(0), targets.std(0).clamp_min(1e-8)
return fm, fs, tm, ts
def augment(xb, yb, fm, fs, tm, ts, p=0.6, vlo=30.0, vhi=98.0, jitter=0.0, exp=CD_EXP):
"""xb standardized inputs [B,256,4]; yb standardized targets [B,2].
Rescale velocity (channel 2) to random target, adjust CD by k^exp, CL fixed."""
B = xb.shape[0]
xb = xb.clone(); yb = yb.clone()
# de-standardize needed channels
vel = xb[:, :, 2] * fs[2] + fm[2] # [B,256] (constant across points)
v0 = vel[:, 0] # [B]
cd = yb[:, 0] * ts[0] + tm[0]
cl = yb[:, 1] * ts[1] + tm[1]
do = torch.rand(B) < p
vnew = torch.empty(B).uniform_(vlo, vhi)
vnew = torch.where(do, vnew, v0)
k = vnew / v0
cd_new = cd * k.pow(exp)
# write back
xb[:, :, 2] = ((vnew.unsqueeze(1)).expand(-1, xb.shape[1]) - fm[2]) / fs[2]
yb[:, 0] = (cd_new - tm[0]) / ts[0]
# cl unchanged
if jitter > 0:
# jitter geometry channels (x,y) in standardized space
xb[:, :, 0] += torch.randn_like(xb[:, :, 0]) * jitter
xb[:, :, 1] += torch.randn_like(xb[:, :, 1]) * jitter
return xb, yb
def make_loss(kind, tm, ts):
def loss_fn(out, yb):
if kind == 'mse_std':
return F.mse_loss(out, yb)
if kind == 'huber_std':
return F.smooth_l1_loss(out, yb, beta=1.0)
if kind == 'mse_cd_rel':
# MSE on CL(std) + relative-ish on CD via log of physical
cl_l = F.mse_loss(out[:, 1], yb[:, 1])
cd_p = out[:, 0] * ts[0] + tm[0]
cd_t = yb[:, 0] * ts[0] + tm[0]
# stabilized log via softplus to keep positive
eps = 1e-4
cd_p = F.softplus(cd_p - eps) + eps
cd_l = F.mse_loss(torch.log(cd_p), torch.log(cd_t.clamp_min(eps)))
return cl_l + cd_l
raise ValueError(kind)
return loss_fn
def evaluate(model, x, tg, fm, fs, tm, ts, device='cpu'):
model.eval()
with torch.no_grad():
out = model(x.to(device)).cpu()
pred = out * ts + tm
err = pred - tg
rmse = (err**2).mean(0).sqrt()
std = tg.std(0)
nrmse = rmse / std
cd_nmae = err[:, 0].abs().mean() / tg[:, 0].abs().mean()
cl_nmae = err[:, 1].abs().mean() / tg[:, 1].abs().mean()
return dict(nrmse_cd=nrmse[0].item(), nrmse_cl=nrmse[1].item(),
mean_nrmse=nrmse.mean().item(), worst_nrmse=nrmse.max().item(),
cd_nmae=cd_nmae.item(), cl_nmae=cl_nmae.item())
def train_once(xtr, ytr_std, fm, fs, tm, ts, *, epochs=200, bs=32, lr=1e-3, wd=1e-4,
aug_p=0.0, jitter=0.0, loss_kind='mse_std', seed=0, swa=False,
swa_start_frac=0.7, eval_fn=None, verbose=False):
torch.manual_seed(seed); np.random.seed(seed)
device = 'cpu'
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=epochs)
loss_fn = make_loss(loss_kind, tm, ts)
n = xtr.shape[0]
swa_model = None; swa_n = 0
swa_start = int(epochs * swa_start_frac)
for ep in range(epochs):
model.train()
idx = torch.randperm(n)
for s in range(0, n
…[truncated]harness written
cd /app && cat > /app/run_extrap.py << 'PY'
import torch, numpy as np, time
from exp import load, standardizers, train_once, evaluate, CFG
ptr, ttr = load('train'); pv, tv = load('val')
pts = torch.cat([ptr, ptv]) if False else torch.cat([ptr, pv])
tg = torch.cat([ttr, tv])
vel = pts[:, 0, 2]
THR = 68.0
tr_mask = vel < THR; ho_mask = vel >= THR
xtr_raw, ytr_raw = pts[tr_mask], tg[tr_mask]
xho_raw, yho_raw = pts[ho_mask], tg[ho_mask]
print(f'train<{THR}: {tr_mask.sum().item()} holdout>={THR}: {ho_mask.sum().item()}')
# standardizers from TRAIN ONLY (the <68 regime)
fm, fs, tm, ts = standardizers(xtr_raw, ytr_raw)
def std_x(x): return (x - fm.view(1,1,-1)) / fs.view(1,1,-1)
def std_y(y): return (y - tm) / ts
xtr = std_x(xtr_raw); ytr = std_y(ytr_raw)
xho = std_x(xho_raw)
def run(tag, **kw):
seeds = kw.pop('seeds', [0,1,2])
ms = []
for sd in seeds:
model = train_once(xtr, ytr, fm, fs, tm, ts, seed=sd, **kw)
m = evaluate(model, xho, yho_raw, fm, fs, tm, ts)
ms.append(m)
agg = {k: np.mean([m[k] for m in ms]) for k in ms[0]}
print(f'{tag:28s} meanNRMSE={agg["mean_nrmse"]:.4f} '
f'NRMSE_cd={agg["nrmse_cd"]:.4f} NRMSE_cl={agg["nrmse_cl"]:.4f} '
f'worst={agg["worst_nrmse"]:.4f} cd_nmae={agg["cd_nmae"]:.4f}')
return agg
t0=time.time()
run('baseline mse (no aug)', epochs=200, aug_p=0.0, loss_kind='mse_std')
run('physics-aug p=0.6', epochs=200, aug_p=0.6, loss_kind='mse_std')
run('physics-aug p=0.8', epochs=200, aug_p=0.8, loss_kind='mse_std')
print('elapsed %.1fs'%(time.time()-t0))
PY
python3 /app/run_extrap.py[stderr] Shell command timed out. Tool interaction exceeded 300 seconds.
restart: true
<system>tool has been restarted.</system>
cd /app && timeout 120 python3 -c "
import time, torch
torch.set_num_threads(16)
from exp import load, standardizers, train_once, evaluate
ptr,ttr=load('train')
fm,fs,tm,ts=standardizers(ptr,ttr)
x=(ptr-fm.view(1,1,-1))/fs.view(1,1,-1); y=(ttr-tm)/ts
t0=time.time()
m=train_once(x,y,fm,fs,tm,ts,epochs=50,aug_p=0.0,loss_kind='mse_std',seed=0)
print('50 epochs took %.1fs -> %.2fs/epoch'%(time.time()-t0,(time.time()-t0)/50))
"50 epochs took 36.4s -> 0.73s/epoch
cd /app && cat > /app/run_extrap.py << 'PY'
import torch, numpy as np, time, sys, json, os
torch.set_num_threads(int(os.environ.get('NTHREADS','2')))
from exp import load, standardizers, train_once, evaluate
cfg = json.loads(sys.argv[1])
tag = cfg.pop('tag'); seeds = cfg.pop('seeds', [0,1]); THR = cfg.pop('thr', 68.0)
ptr, ttr = load('train'); pv, tv = load('val')
pts = torch.cat([ptr, pv]); tg = torch.cat([ttr, tv])
vel = pts[:, 0, 2]
tr_mask = vel < THR; ho_mask = vel >= THR
xtr_raw, ytr_raw = pts[tr_mask], tg[tr_mask]
xho_raw, yho_raw = pts[ho_mask], tg[ho_mask]
fm, fs, tm, ts = standardizers(xtr_raw, ytr_raw)
xtr = (xtr_raw - fm.view(1,1,-1))/fs.view(1,1,-1); ytr = (ytr_raw - tm)/ts
xho = (xho_raw - fm.view(1,1,-1))/fs.view(1,1,-1)
ms = []
for sd in seeds:
model = train_once(xtr, ytr, fm, fs, tm, ts, seed=sd, **cfg)
ms.append(evaluate(model, xho, yho_raw, fm, fs, tm, ts))
agg = {k: float(np.mean([m[k] for m in ms])) for k in ms[0]}
line = (f'{tag:30s} meanNRMSE={agg["mean_nrmse"]:.4f} cd={agg["nrmse_cd"]:.4f} '
f'cl={agg["nrmse_cl"]:.4f} worst={agg["worst_nrmse"]:.4f} cd_nmae={agg["cd_nmae"]:.4f}')
with open('/app/results.txt','a') as f: f.write(line+'\n')
print(line)
PY
echo okok
cd /app && rm -f results.txt && J(){ python3 run_extrap.py "$1" >/dev/null 2>>err.log & }
J '{"tag":"baseline_mse","epochs":140,"aug_p":0.0,"loss_kind":"mse_std","seeds":[0,1]}'
J '{"tag":"aug0.6_mse","epochs":140,"aug_p":0.6,"loss_kind":"mse_std","seeds":[0,1]}'
J '{"tag":"aug0.8_mse","epochs":140,"aug_p":0.8,"loss_kind":"mse_std","seeds":[0,1]}'
J '{"tag":"aug0.6_cdrel","epochs":140,"aug_p":0.6,"loss_kind":"mse_cd_rel","seeds":[0,1]}'
echo "launched $(jobs -p | wc -w) jobs"; sleep 2; echo "running:"; pgrep -fa run_extrap | wc -llaunched 4 jobs running: 4
cd /app && for i in $(seq 1 28); do n=$(pgrep -fa run_extrap | grep -c run_extrap); [ "$n" -eq 0 ] && break; sleep 10; done; echo "=== results ==="; cat results.txt 2>/dev/null; echo "=== errors (tail) ==="; tail -5 err.log 2>/dev/null
=== results === === errors (tail) ===
cd /app && echo "still running: $(pgrep -fa run_extrap | grep -c run_extrap)"; ps aux | grep run_extrap | grep -v grep | head; tail -5 err.log 2>/dev/null
still running: 5
h2tagent 404 190 4.9 5913240 3594360 ? Rl 21:36 9:15 python3 run_extrap.py {"tag":"baseline_mse","epochs":140,"aug_p":0.0,"loss_kind":"mse_std","seeds":[0,1]}
h2tagent 405 190 4.9 5910932 3591184 ? Rl 21:36 9:16 python3 run_extrap.py {"tag":"aug0.6_mse","epochs":140,"aug_p":0.6,"loss_kind":"mse_std","seeds":[0,1]}
h2tagent 406 190 4.9 5893896 3574756 ? Rl 21:36 9:15 python3 run_extrap.py {"tag":"aug0.8_mse","epochs":140,"aug_p":0.8,"loss_kind":"mse_std","seeds":[0,1]}
h2tagent 407 190 4.9 5912980 3593952 ? Rl 21:36 9:16 python3 run_extrap.py {"tag":"aug0.6_cdrel","epochs":140,"aug_p":0.6,"loss_kind":"mse_cd_rel","seeds":[0,1]}cd /app && for i in $(seq 1 25); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "=== results ==="; cat results.txt 2>/dev/null; echo "=== err tail ==="; tail -3 err.log 2>/dev/null
=== results === aug0.6_cdrel meanNRMSE=562.6665 cd=1125.2960 cl=0.0370 worst=1125.2960 cd_nmae=379.4790 baseline_mse meanNRMSE=0.0949 cd=0.1146 cl=0.0752 worst=0.1146 cd_nmae=0.0219 aug0.6_mse meanNRMSE=0.0779 cd=0.1115 cl=0.0444 worst=0.1115 cd_nmae=0.0227 aug0.8_mse meanNRMSE=0.0868 cd=0.1286 cl=0.0449 worst=0.1286 cd_nmae=0.0248 === err tail ===
command: str_replace
path: /app/exp.py
old_str: def make_loss(kind, tm, ts):
def loss_fn(out, yb):
if kind == 'mse_std':
return F.mse_loss(out, yb)
if kind == 'huber_std':
return F.smooth_l1_loss(out, yb, beta=1.0)
if kind == 'mse_cd_rel':
# MSE on CL(std) + relative-ish on CD via log of physical
cl_l = F.mse_loss(out[:, 1], yb[:, 1])
cd_p = out[:, 0] * ts[0] + tm[0]
cd_t = yb[:, 0] * ts[0] + tm[0]
# stabilized log via softplus to keep positive
eps = 1e-4
cd_p = F.softplus(cd_p - eps) + eps
cd_l = F.mse_loss(torch.log(cd_p), torch.log(cd_t.clamp_min(eps)))
return cl_l + cd_l
raise ValueError(kind)
return loss_fn
new_str: def make_loss(kind, tm, ts, w_cd=1.0):
def loss_fn(out, yb):
if kind == 'mse_std':
d = (out - yb) ** 2
return w_cd * d[:, 0].mean() + d[:, 1].mean()
if kind == 'huber_std':
d = F.smooth_l1_loss(out, yb, beta=1.0, reduction='none')
return w_cd * d[:, 0].mean() + d[:, 1].mean()
if kind == 'relcd':
# CL: std MSE. CD: stable relative squared error in physical space.
cl_l = F.mse_loss(out[:, 1], yb[:, 1])
cd_p = out[:, 0] * ts[0] + tm[0]
cd_t = yb[:, 0] * ts[0] + tm[0]
rel = (cd_p - cd_t) / cd_t.clamp_min(1e-4)
return cl_l + w_cd * (rel ** 2).mean()
if kind == 'mix':
# std-MSE on both (matches NRMSE) + small relative-CD term (helps cd_nmae)
d = (out - yb) ** 2
base = w_cd * d[:, 0].mean() + d[:, 1].mean()
cd_p = out[:, 0] * ts[0] + tm[0]
cd_t = yb[:, 0] * ts[0] + tm[0]
rel = ((cd_p - cd_t) / cd_t.clamp_min(1e-4)) ** 2
return base + 0.3 * rel.mean()
raise ValueError(kind)
return loss_fnThe file /app/exp.py has been edited successfully.
command: str_replace
path: /app/exp.py
old_str: def train_once(xtr, ytr_std, fm, fs, tm, ts, *, epochs=200, bs=32, lr=1e-3, wd=1e-4,
aug_p=0.0, jitter=0.0, loss_kind='mse_std', seed=0, swa=False,
swa_start_frac=0.7, eval_fn=None, verbose=False):
new_str: def train_once(xtr, ytr_std, fm, fs, tm, ts, *, epochs=200, bs=32, lr=1e-3, wd=1e-4,
aug_p=0.0, jitter=0.0, loss_kind='mse_std', w_cd=1.0, vlo=30.0, vhi=98.0,
seed=0, swa=False, swa_start_frac=0.7, eval_fn=None, verbose=False):The file /app/exp.py has been edited successfully.
cd /app && sed -i 's/ loss_fn = make_loss(loss_kind, tm, ts)/ loss_fn = make_loss(loss_kind, tm, ts, w_cd=w_cd)/' exp.py sed -i 's/ xb, yb = augment(xb, yb, fm, fs, tm, ts, p=aug_p, jitter=jitter)/ xb, yb = augment(xb, yb, fm, fs, tm, ts, p=aug_p, jitter=jitter, vlo=vlo, vhi=vhi)/' exp.py grep -n "make_loss(loss_kind" exp.py; grep -n "augment(xb, yb" exp.py
99: loss_fn = make_loss(loss_kind, tm, ts, w_cd=w_cd) 25:def augment(xb, yb, fm, fs, tm, ts, p=0.6, vlo=30.0, vhi=98.0, jitter=0.0, exp=CD_EXP): 110: xb, yb = augment(xb, yb, fm, fs, tm, ts, p=aug_p, jitter=jitter, vlo=vlo, vhi=vhi)
cd /app && cp results.txt results_r1.txt; : > results.txt
J(){ NTHREADS=2 python3 run_extrap.py "$1" >/dev/null 2>>err.log & }
J '{"tag":"aug0.6_swa","epochs":160,"aug_p":0.6,"loss_kind":"mse_std","swa":true,"seeds":[0,1]}'
J '{"tag":"aug0.6_wcd2","epochs":140,"aug_p":0.6,"loss_kind":"mse_std","w_cd":2.0,"seeds":[0,1]}'
J '{"tag":"aug0.6_mix","epochs":140,"aug_p":0.6,"loss_kind":"mix","w_cd":1.0,"seeds":[0,1]}'
J '{"tag":"aug0.6_relcd","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":1.0,"seeds":[0,1]}'
echo "launched, running: $(pgrep -f run_extrap | wc -l)"launched, running: 4
cd /app && for i in $(seq 1 28); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "=== round2 ==="; sort results.txt; echo "(r1 best: aug0.6_mse meanNRMSE=0.0779 cd=0.1115 cl=0.0444)"; tail -3 err.log
=== round2 === (r1 best: aug0.6_mse meanNRMSE=0.0779 cd=0.1115 cl=0.0444)
cd /app && for i in $(seq 1 28); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "=== round2 (running: $(pgrep -f run_extrap | wc -l)) ==="; sort results.txt; tail -3 err.log
=== round2 (running: 0) === aug0.6_mix meanNRMSE=0.0802 cd=0.1159 cl=0.0445 worst=0.1159 cd_nmae=0.0233 aug0.6_relcd meanNRMSE=0.0651 cd=0.0908 cl=0.0395 worst=0.0908 cd_nmae=0.0184 aug0.6_swa meanNRMSE=0.1013 cd=0.1546 cl=0.0480 worst=0.1546 cd_nmae=0.0412 aug0.6_wcd2 meanNRMSE=0.0798 cd=0.1138 cl=0.0457 worst=0.1138 cd_nmae=0.0221
cd /app && sed -i 's/ loss_fn = make_loss(loss_kind, tm, ts, w_cd=w_cd)/ loss_fn = make_loss(loss_kind, tm, ts, w_cd=w_cd)\n aug_exp = float(os.environ.get("AUG_EXP", CD_EXP))/' exp.py
sed -i 's/p=aug_p, jitter=jitter, vlo=vlo, vhi=vhi)/p=aug_p, jitter=jitter, vlo=vlo, vhi=vhi, exp=aug_exp)/' exp.py
grep -n "aug_exp" exp.py; python3 -c "import exp; print('import ok')"100: aug_exp = float(os.environ.get("AUG_EXP", CD_EXP))
111: xb, yb = augment(xb, yb, fm, fs, tm, ts, p=aug_p, jitter=jitter, vlo=vlo, vhi=vhi, exp=aug_exp)
import okcd /app && cp results.txt results_r2.txt; : > results.txt
J(){ NTHREADS=2 python3 run_extrap.py "$1" >/dev/null 2>>err.log & }
J '{"tag":"relcd_wcd2","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[0,1]}'
J '{"tag":"relcd_wcd3","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":3.0,"seeds":[0,1]}'
J '{"tag":"relcd_p0.7","epochs":140,"aug_p":0.7,"loss_kind":"relcd","w_cd":1.0,"seeds":[0,1]}'
J '{"tag":"relcd_p0.5","epochs":140,"aug_p":0.5,"loss_kind":"relcd","w_cd":1.0,"seeds":[0,1]}'
echo "launched: $(pgrep -f run_extrap | wc -l)"launched: 4
cd /app && for i in $(seq 1 28); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f run_extrap | wc -l)"; echo "=== round3 (best so far relcd: mean=0.0651 cd=0.0908 cl=0.0395) ==="; sort -t= -k2 -n results.txt
running: 4 === round3 (best so far relcd: mean=0.0651 cd=0.0908 cl=0.0395) ===
cd /app && for i in $(seq 1 25); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f run_extrap | wc -l)"; echo "=== round3 ==="; sort results.txt; echo "(prev best relcd w_cd1 p0.6: mean=0.0651 cd=0.0908 cl=0.0395 cd_nmae=0.0184)"
running: 0 === round3 === relcd_p0.5 meanNRMSE=0.0647 cd=0.0898 cl=0.0395 worst=0.0898 cd_nmae=0.0179 relcd_p0.7 meanNRMSE=0.0650 cd=0.0908 cl=0.0392 worst=0.0908 cd_nmae=0.0187 relcd_wcd2 meanNRMSE=0.0643 cd=0.0887 cl=0.0399 worst=0.0887 cd_nmae=0.0182 relcd_wcd3 meanNRMSE=0.0657 cd=0.0914 cl=0.0399 worst=0.0914 cd_nmae=0.0189 (prev best relcd w_cd1 p0.6: mean=0.0651 cd=0.0908 cl=0.0395 cd_nmae=0.0184)
cd /app && cp results.txt results_r3.txt; : > results.txt
J(){ env="$2"; NTHREADS=2 $env python3 run_extrap.py "$1" >/dev/null 2>>err.log & }
# robustness across gap sizes
J '{"tag":"best_thr70","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"thr":70.0,"seeds":[0,1]}'
J '{"tag":"base_thr70","epochs":140,"aug_p":0.0,"loss_kind":"mse_std","thr":70.0,"seeds":[0,1]}'
J '{"tag":"best_thr66","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"thr":66.0,"seeds":[0,1]}'
# exponent sensitivity at thr68
J '{"tag":"best_exp0.18","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[0,1]}' 'AUG_EXP=-0.18'
J '{"tag":"best_exp0.22","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[0,1]}' 'AUG_EXP=-0.22'
J '{"tag":"best_jitter","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"jitter":0.02,"seeds":[0,1]}'
echo "launched: $(pgrep -f run_extrap | wc -l)"launched: 4
cd /app && sleep 3; echo "running: $(pgrep -f run_extrap | wc -l)"; pgrep -af run_extrap | sed 's/{.*tag":"/ /; s/".*//' ; tail -3 err.logrunning: 4 1432 python3 run_extrap.py best_thr70 1433 python3 run_extrap.py base_thr70 1434 python3 run_extrap.py best_thr66 1437 python3 run_extrap.py best_jitter main: line 351: AUG_EXP=-0.18: command not found main: line 351: AUG_EXP=-0.22: command not found
cd /app && AUG_EXP=-0.18 NTHREADS=2 python3 run_extrap.py '{"tag":"best_exp0.18","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[0,1]}' >/dev/null 2>>err.log &
AUG_EXP=-0.22 NTHREADS=2 python3 run_extrap.py '{"tag":"best_exp0.22","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[0,1]}' >/dev/null 2>>err.log &
echo "running: $(pgrep -f run_extrap | wc -l)"running: 6
cd /app && for i in $(seq 1 28); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f run_extrap | wc -l)"; echo "=== round4 ==="; sort results.txt
running: 6 === round4 ===
cd /app && for i in $(seq 1 28); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f run_extrap | wc -l)"; echo "=== round4 ==="; sort results.txt
running: 0 === round4 === base_thr70 meanNRMSE=0.1005 cd=0.1257 cl=0.0753 worst=0.1257 cd_nmae=0.0227 best_exp0.18 meanNRMSE=0.0648 cd=0.0897 cl=0.0398 worst=0.0897 cd_nmae=0.0159 best_exp0.22 meanNRMSE=0.0669 cd=0.0937 cl=0.0400 worst=0.0937 cd_nmae=0.0219 best_jitter meanNRMSE=0.0665 cd=0.0960 cl=0.0370 worst=0.0960 cd_nmae=0.0205 best_thr66 meanNRMSE=0.0627 cd=0.0826 cl=0.0427 worst=0.0826 cd_nmae=0.0188 best_thr70 meanNRMSE=0.0572 cd=0.0844 cl=0.0301 worst=0.0844 cd_nmae=0.0160
cd /app && cp results.txt results_r4.txt; : > results.txt
J(){ NTHREADS=2 python3 run_extrap.py "$1" >/dev/null 2>>err.log & }
J '{"tag":"ep180","epochs":180,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[0,1]}'
J '{"tag":"ep220","epochs":220,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[0,1]}'
J '{"tag":"seeds23","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"seeds":[2,3]}'
J '{"tag":"vhi105","epochs":140,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"vhi":105.0,"seeds":[0,1]}'
echo "launched: $(pgrep -f run_extrap | wc -l)"launched: 4
cd /app && for i in $(seq 1 29); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f run_extrap | wc -l)"; echo "=== round5 (ref: seeds01 ep140 = 0.0643 cd0.0887) ==="; sort results.txt
running: 4 === round5 (ref: seeds01 ep140 = 0.0643 cd0.0887) ===
cd /app && for i in $(seq 1 26); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f run_extrap | wc -l)"; echo "=== round5 (ref seeds01 ep140=0.0643 cd0.0887) ==="; sort results.txt
running: 1 === round5 (ref seeds01 ep140=0.0643 cd0.0887) === ep180 meanNRMSE=0.0585 cd=0.0881 cl=0.0289 worst=0.0881 cd_nmae=0.0173 seeds23 meanNRMSE=0.0611 cd=0.0925 cl=0.0296 worst=0.0925 cd_nmae=0.0180 vhi105 meanNRMSE=0.0645 cd=0.0889 cl=0.0401 worst=0.0889 cd_nmae=0.0181
cd /app && for i in $(seq 1 12); do n=$(pgrep -f run_extrap | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; grep ep220 results.txt
ep220 meanNRMSE=0.0614 cd=0.0821 cl=0.0407 worst=0.0821 cd_nmae=0.0168
cd /app && cat > /app/train_seed.py << 'PY'
"""Train one final-config seed on ALL real data; save weights + public_test preds."""
import sys, os, json, torch, numpy as np
os.environ.setdefault('NTHREADS','2'); torch.set_num_threads(int(os.environ['NTHREADS']))
from exp import load, standardizers, train_once, CFG
seed = int(sys.argv[1])
out_dir = '/app/outputs'; os.makedirs(out_dir, exist_ok=True)
# all real data
ptr, ttr = load('train'); pv, tv = load('val')
pts = torch.cat([ptr, pv]); tg = torch.cat([ttr, tv])
fm, fs, tm, ts = standardizers(pts, tg)
x = (pts - fm.view(1,1,-1))/fs.view(1,1,-1); y = (tg - tm)/ts
RECIPE = dict(epochs=200, bs=32, lr=1e-3, wd=1e-4, aug_p=0.6,
loss_kind='relcd', w_cd=2.0, vlo=30.0, vhi=105.0)
model = train_once(x, y, fm, fs, tm, ts, seed=seed, **RECIPE)
# predictions on public_test for medoid selection
pte, _ = load('public_test')
xte = (pte - fm.view(1,1,-1))/fs.view(1,1,-1)
model.eval()
with torch.no_grad():
pred = (model(xte) * ts + tm).cpu()
ckpt = {'state_dict': {k: v.cpu() for k, v in model.state_dict().items()},
'feat_mean': fm.cpu(), 'feat_std': fs.cpu(),
'targ_mean': tm.cpu(), 'targ_std': ts.cpu()}
torch.save(ckpt, f'{out_dir}/seed{seed}.pt')
torch.save(pred, f'{out_dir}/pred{seed}.pt')
print(f'seed {seed} done: CD[{pred[:,0].min():.4f},{pred[:,0].max():.4f}] '
f'CL[{pred[:,1].min():.3f},{pred[:,1].max():.3f}]')
PY
echo okok
cd /app && rm -f outputs/seed*.pt outputs/pred*.pt; : > results.txt
for s in 0 1 2 3 4; do NTHREADS=2 python3 train_seed.py $s >>seedlog.txt 2>>err.log & done
# proxy: exact final recipe on closest real proxy vel>=72
NTHREADS=2 python3 run_extrap.py '{"tag":"PROXY_thr72_final","epochs":200,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"vhi":105.0,"thr":72.0,"seeds":[0,1]}' >/dev/null 2>>err.log &
echo "launched: $(pgrep -f 'train_seed|run_extrap' | wc -l)"launched: 6
cd /app && for i in $(seq 1 29); do n=$(pgrep -f 'train_seed|run_extrap' | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f 'train_seed|run_extrap' | wc -l)"; echo "=== seedlog ==="; cat seedlog.txt 2>/dev/null; echo "=== proxy ==="; cat results.txt; tail -3 err.log
running: 6 === seedlog === === proxy === main: line 351: AUG_EXP=-0.18: command not found main: line 351: AUG_EXP=-0.22: command not found
cd /app && for i in $(seq 1 29); do n=$(pgrep -f 'train_seed|run_extrap' | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "running: $(pgrep -f 'train_seed|run_extrap' | wc -l)"; echo "=== seedlog ==="; cat seedlog.txt 2>/dev/null; echo "=== proxy ==="; grep PROXY results.txt
running: 1 === seedlog === === proxy ===
cd /app && ls -la outputs/ 2>/dev/null; echo "--- err tail ---"; tail -6 err.log; echo "--- still running ---"; pgrep -af 'train_seed|run_extrap'
total 8
drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 19 22:17 .
drwxrws--- 5 h2tagent h2tagent 4096 Jun 19 22:17 ..
--- err tail ---
pte, _ = load('public_test')
^^^^^^^^^^^^^^^^^^^
File "/app/exp.py", line 17, in load
return p['points'].float(), p['targets'].float()
~^^^^^^^^^^^
KeyError: 'targets'
--- still running ---
2309 python3 run_extrap.py {"tag":"PROXY_thr72_final","epochs":200,"aug_p":0.6,"loss_kind":"relcd","w_cd":2.0,"vhi":105.0,"thr":72.0,"seeds":[0,1]}command: str_replace
path: /app/exp.py
old_str: p = torch.load(f'/app/data/{s}/split.pt', weights_only=True)
return p['points'].float(), p['targets'].float()
new_str: p = torch.load(f'/app/data/{s}/split.pt', weights_only=True)
tg = p['targets'].float() if 'targets' in p else None
return p['points'].float(), tgThe file /app/exp.py has been edited successfully.
cd /app && : > seedlog.txt; for s in 0 1 2 3 4; do NTHREADS=2 python3 train_seed.py $s >>seedlog.txt 2>>err.log & done; echo "launched seeds; total running: $(pgrep -f 'train_seed|run_extrap' | wc -l)"
launched seeds; total running: 6
cd /app && for i in $(seq 1 29); do n=$(pgrep -f 'train_seed' | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "train_seed running: $(pgrep -f 'train_seed' | wc -l)"; echo "=== seedlog ==="; cat seedlog.txt; echo "=== proxy ==="; grep PROXY results.txt; echo "=== outputs ==="; ls outputs/
train_seed running: 5 === seedlog === === proxy === PROXY_thr72_final meanNRMSE=0.0467 cd=0.0692 cl=0.0242 worst=0.0692 cd_nmae=0.0156 === outputs ===
cd /app && for i in $(seq 1 29); do n=$(pgrep -f 'train_seed' | wc -l); [ "$n" -eq 0 ] && break; sleep 10; done; echo "train_seed running: $(pgrep -f 'train_seed' | wc -l)"; echo "=== seedlog ==="; cat seedlog.txt; echo "=== outputs ==="; ls -la outputs/ | grep -E 'seed|pred'
train_seed running: 0 === seedlog === seed 0 done: CD[0.0072,0.0269] CL[-0.521,1.684] seed 1 done: CD[0.0072,0.0270] CL[-0.535,1.712] seed 3 done: CD[0.0072,0.0277] CL[-0.491,1.718] seed 2 done: CD[0.0072,0.0270] CL[-0.517,1.709] seed 4 done: CD[0.0072,0.0264] CL[-0.530,1.697] === outputs === -rw-r--r-- 1 h2tagent h2tagent 1938 Jun 19 22:33 pred0.pt -rw-r--r-- 1 h2tagent h2tagent 1938 Jun 19 22:33 pred1.pt -rw-r--r-- 1 h2tagent h2tagent 1938 Jun 19 22:33 pred2.pt -rw-r--r-- 1 h2tagent h2tagent 1938 Jun 19 22:33 pred3.pt -rw-r--r-- 1 h2tagent h2tagent 1938 Jun 19 22:33 pred4.pt -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:33 seed0.pt -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:33 seed1.pt -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:33 seed2.pt -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:33 seed3.pt -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:33 seed4.pt
cd /app && python3 << 'PY'
import torch, numpy as np
seeds=[0,1,2,3,4]
preds=torch.stack([torch.load(f'outputs/pred{s}.pt') for s in seeds]) # [5,100,2]
# normalize per-target by std across all preds for fair distance
scale = preds.reshape(-1,2).std(0).clamp_min(1e-8)
pn = preds/scale
mean = pn.mean(0, keepdim=True)
d = ((pn-mean)**2).sum(dim=(1,2)) # per-seed distance to centroid
for s,di in zip(seeds,d): print(f'seed {s}: dist_to_centroid={di.item():.4f}')
medoid = seeds[int(torch.argmin(d))]
print('MEDOID seed =', medoid)
# agreement check: max pairwise disagreement relative to signal
print('per-seed CD std across seeds (mean over cases):', preds[:,:,0].std(0).mean().item())
print('per-seed CL std across seeds (mean over cases):', preds[:,:,1].std(0).mean().item())
# save medoid as model.pt
ck = torch.load(f'outputs/seed{medoid}.pt')
torch.save(ck, 'outputs/model.pt')
print('wrote outputs/model.pt from seed', medoid)
print('keys:', list(ck.keys()))
for k in ['feat_mean','feat_std','targ_mean','targ_std']:
print(f' {k} = {ck[k].tolist()}')
PYseed 0: dist_to_centroid=0.2595 seed 1: dist_to_centroid=0.2056 seed 2: dist_to_centroid=0.0648 seed 3: dist_to_centroid=0.1768 seed 4: dist_to_centroid=0.1342 MEDOID seed = 2 per-seed CD std across seeds (mean over cases): 0.00013610968017019331 per-seed CL std across seeds (mean over cases): 0.012845206074416637 wrote outputs/model.pt from seed 2 keys: ['state_dict', 'feat_mean', 'feat_std', 'targ_mean', 'targ_std'] feat_mean = [0.38364875316619873, 0.007897475734353065, 53.19941711425781, 0.07678442448377609] feat_std = [0.39559754729270935, 0.03333360701799393, 12.652849197387695, 0.09548104554414749] targ_mean = [0.012753964401781559, 0.6618618965148926] targ_std = [0.005126286298036575, 0.5782685279846191] [stderr] <stdin>:3: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://[code-host]/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on [code-host] for any issues related to this experimental feature. <stdin>:16: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://[code-host]/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on [code-host] for any issues related to this experimental feature.
cd /app && python3 << 'PY'
import torch, json
from candidate_model import build_model
cfg=json.load(open('task_config.json'))
# load EXACTLY as grader (weights_only=True) to ensure it is a pure tensor checkpoint
ck=torch.load('outputs/model.pt', weights_only=True)
assert set(['state_dict','feat_mean','feat_std','targ_mean','targ_std'])<=set(ck), ck.keys()
for k in ['feat_mean','feat_std']: assert ck[k].shape==(4,), (k,ck[k].shape)
for k in ['targ_mean','targ_std']: assert ck[k].shape==(2,), (k,ck[k].shape)
m=build_model(cfg)
res=m.load_state_dict(ck['state_dict'], strict=True)
print('load_state_dict strict OK:', res)
m.eval()
# full grader-style inference on public_test
p=torch.load('data/public_test/split.pt', weights_only=True)['points'].float()
fm,fs,tm,ts=ck['feat_mean'],ck['feat_std'],ck['targ_mean'],ck['targ_std']
x=(p-fm.view(1,1,-1))/fs.view(1,1,-1)
with torch.no_grad():
out=m(x)
pred=out*ts+tm
print('public_test preds: shape',tuple(pred.shape))
print(f' C_D: min={pred[:,0].min():.4f} max={pred[:,0].max():.4f} mean={pred[:,0].mean():.4f}')
print(f' C_L: min={pred[:,1].min():.4f} max={pred[:,1].max():.4f} mean={pred[:,1].mean():.4f}')
print(' any NaN/Inf:', bool(torch.isnan(pred).any() or torch.isinf(pred).any()))
assert (pred[:,0]>0).all(), 'CD must be positive'
print('ALL CHECKS PASSED')
PYload_state_dict strict OK: <All keys matched successfully> public_test preds: shape (100, 2) C_D: min=0.0072 max=0.0270 mean=0.0118 C_L: min=-0.5173 max=1.7086 mean=0.6694 any NaN/Inf: False ALL CHECKS PASSED
cd /app && python3 << 'PY'
import torch, numpy as np
def load(s):
p=torch.load(f'data/{s}/split.pt',weights_only=True);
return p['points'].float().numpy(), (p['targets'].float().numpy() if 'targets' in p else None)
ptr,ttr=load('train')
def F(pts):
v=pts[:,0,2];a=pts[:,0,3];y=pts[:,:,1];th=y.max(1)-y.min(1);cm=0.5*(y.max(1)+y.min(1));return v,a,th,cm
v,a,th,cm=F(ptr)
# physics fits on train
XcD=np.column_stack([np.ones_like(a),a,a**2,a**3,np.log(v),th,th**2,cm,cm*a])
bD,_,_,_=np.linalg.lstsq(XcD,np.log(ttr[:,0]),rcond=None)
XcL=np.column_stack([np.ones_like(a),a,a**2,th,cm,cm*a])
bL,_,_,_=np.linalg.lstsq(XcL,ttr[:,1],rcond=None)
# apply to public_test
pte,_=load('public_test')
v2,a2,th2,cm2=F(pte)
cd_phys=np.exp(np.column_stack([np.ones_like(a2),a2,a2**2,a2**3,np.log(v2),th2,th2**2,cm2,cm2*a2])@bD)
cl_phys=np.column_stack([np.ones_like(a2),a2,a2**2,th2,cm2,cm2*a2])@bL
# network preds
net=torch.load('outputs/pred2.pt').numpy()
def rel(a,b): return np.abs(a-b)/ (np.abs(b)+1e-9)
print('Network vs physics on public_test (75-82 m/s):')
print(' CD corr=%.3f median rel diff=%.3f'%(np.corrcoef(net[:,0],cd_phys)[0,1], np.median(rel(net[:,0],cd_phys))))
print(' CL corr=%.3f median abs diff=%.3f (CL units)'%(np.corrcoef(net[:,1],cl_phys)[0,1], np.median(np.abs(net[:,1]-cl_phys))))
# also check CD decreases with velocity as expected (network)
print(' corr(net CD, velocity)=%.3f (expect negative)'%np.corrcoef(net[:,0],v2)[0,1])
print(' network CD mean=%.4f vs physics CD mean=%.4f'%(net[:,0].mean(),cd_phys.mean()))
PYNetwork vs physics on public_test (75-82 m/s): CD corr=0.976 median rel diff=0.035 CL corr=0.987 median abs diff=0.047 (CL units) corr(net CD, velocity)=-0.086 (expect negative) network CD mean=0.0118 vs physics CD mean=0.0118 [stderr] <stdin>:20: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://[code-host]/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on [code-host] for any issues related to this experimental feature.
cd /app && rm -f outputs/seed*.pt outputs/pred*.pt && ls -la outputs/ && echo "--- final integrity re-check ---" && python3 -c "
import torch, json
from candidate_model import build_model
ck=torch.load('outputs/model.pt', weights_only=True)
m=build_model(json.load(open('task_config.json')))
m.load_state_dict(ck['state_dict'], strict=True); m.eval()
p=torch.load('data/public_test/split.pt', weights_only=True)['points'].float()
x=(p-ck['feat_mean'].view(1,1,-1))/ck['feat_std'].view(1,1,-1)
with torch.no_grad(): pred=m(x)*ck['targ_std']+ck['targ_mean']
print('OK: model.pt loads, runs. pred shape',tuple(pred.shape),
'| CD mean %.4f CL mean %.4f'%(pred[:,0].mean(),pred[:,1].mean()),
'| finite:',bool(torch.isfinite(pred).all()))
print('contains keys:', sorted(ck.keys()))
"total 347 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 19 22:35 . drwxrws--- 5 h2tagent h2tagent 4096 Jun 19 22:26 .. -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:34 model.pt --- final integrity re-check --- OK: model.pt loads, runs. pred shape (100, 2) | CD mean 0.0118 CL mean 0.6694 | finite: True contains keys: ['feat_mean', 'feat_std', 'state_dict', 'targ_mean', 'targ_std']
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: airfrans-high-reynolds-drag-extrapolation # attempt: 1 status: completed raw_score: 0.5 # reward rule: continuous: score 0.5 >= 0.5 # => reward = 1.0 (PASS) # # 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:35 . drwxrws--- 5 h2tagent h2tagent 4096 Jun 19 22:26 .. -rw-r--r-- 1 h2tagent h2tagent 346611 Jun 19 22:34 model.pt --- final integrity re-check --- OK: model.pt loads, runs. pred shape (100, 2) | CD mean 0.0118 CL mean 0.6694 | finite: True contains keys: ['feat_mean', 'feat_std', 'state_dict', 'targ_mean', 'targ_std']
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_4cdd653e42de43e0. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_4cdd653e42de43e0 · verifier authoritative; classifier explanatory.