tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---TREE---" && find . -maxdepth 3 -not -path '*/node_modules/*' | head -100
total 20 drwxrws--- 4 h2tagent h2tagent 4096 Jun 21 05:20 . drwxr-xr-x 26 root root 4096 Jun 21 05:25 .. drwxrws--- 2 h2tagent h2tagent 4096 Jun 21 05:20 data drwxrws--- 2 h2tagent h2tagent 4096 Jun 21 05:20 output -rwxrwx--- 1 h2tagent h2tagent 3211 Jun 21 05:18 train_and_predict.py ---TREE--- . ./output ./data ./data/validation.csv ./data/train.csv ./data/test_features.csv ./train_and_predict.py
cd /app && cat train_and_predict.py
#!/usr/bin/env python3
"""Starter baseline for the Airfoil Self-Noise surrogate task.
This deliberately modest Ridge model is useful as a working end-to-end
baseline, but it is not strong enough to clear the hidden grouped split.
"""
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
DATA_DIR = Path("/app/data")
OUTPUT_DIR = Path("/app/output")
TARGET = "scaled_sound_pressure_level"
PREDICTION = "predicted_scaled_sound_pressure_level"
FEATURES = [
"frequency",
"attack_angle",
"chord_length",
"free_stream_velocity",
"suction_side_displacement_thickness",
]
class AirfoilFeatures(BaseEstimator, TransformerMixin):
def fit(self, X, y=None):
return self
def transform(self, X):
frame = pd.DataFrame(X, columns=FEATURES if not isinstance(X, pd.DataFrame) else X.columns)
frequency = frame["frequency"].astype(float)
angle = frame["attack_angle"].astype(float)
chord = frame["chord_length"].astype(float)
velocity = frame["free_stream_velocity"].astype(float)
thickness = frame["suction_side_displacement_thickness"].astype(float)
log_frequency = np.log10(frequency)
log_thickness = np.log10(thickness)
out = pd.DataFrame(index=frame.index)
out["frequency"] = frequency
out["attack_angle"] = angle
out["chord_length"] = chord
out["free_stream_velocity"] = velocity
out["suction_side_displacement_thickness"] = thickness
out["log_frequency"] = log_frequency
out["log_thickness"] = log_thickness
out["freq_velocity"] = log_frequency * velocity
out["freq_angle"] = log_frequency * angle
out["angle_velocity"] = angle * velocity
out["thickness_angle"] = log_thickness * angle
out["chord_velocity"] = chord * velocity
return out.to_numpy(dtype=float)
def load_labeled_data() -> pd.DataFrame:
parts = [pd.read_csv(DATA_DIR / "train.csv")]
validation_path = DATA_DIR / "validation.csv"
if validation_path.exists():
parts.append(pd.read_csv(validation_path))
return pd.concat(parts, ignore_index=True)
def load_evaluation_features() -> pd.DataFrame:
test_path = DATA_DIR / "test_features.csv"
if test_path.exists():
return pd.read_csv(test_path)
validation = pd.read_csv(DATA_DIR / "validation.csv")
return validation.drop(columns=[TARGET])
def main() -> None:
labeled = load_labeled_data()
evaluation = load_evaluation_features()
model = make_pipeline(
AirfoilFeatures(),
StandardScaler(),
Ridge(alpha=10.0),
)
model.fit(labeled[FEATURES], labeled[TARGET])
predictions = model.predict(evaluation[FEATURES])
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
pd.DataFrame(
{
"sample_id": evaluation["sample_id"].astype(str),
PREDICTION: predictions,
}
).to_csv(OUTPUT_DIR / "predictions.csv", index=False)
if __name__ == "__main__":
main()cd /app && echo "=== TRAIN ===" && head -5 data/train.csv && echo "..." && wc -l data/train.csv && echo "=== VALIDATION ===" && head -5 data/validation.csv && wc -l data/validation.csv && echo "=== TEST_FEATURES ===" && cat data/test_features.csv && wc -l data/test_features.csv
=== TRAIN === sample_id,frequency,attack_angle,chord_length,free_stream_velocity,suction_side_displacement_thickness,scaled_sound_pressure_level af_train_0000,5003.72368377,0.034997843085,0.101695197262,71.2730786718,0.00120791730525,131.375168508 af_train_0001,1997.22544774,15.6337827466,0.101571096822,71.3314610261,0.0436885053477,119.435701427 af_train_0002,1248.10938712,9.47742633424,0.0253695328899,31.6576914656,0.00461706652486,131.137445156 af_train_0003,200.228488983,15.5929878809,0.101720824729,39.5544682279,0.0527780201324,124.741187675 ... 957 data/train.csv === VALIDATION === sample_id,frequency,attack_angle,chord_length,free_stream_velocity,suction_side_displacement_thickness,scaled_sound_pressure_level af_val_0000,1998.53240303,12.3252028109,0.101492415739,39.5853154533,0.040726689388,115.836432573 af_val_0001,629.718430551,0.0186262062548,0.152498614376,39.5692331074,0.00193529208188,124.213347611 af_val_0002,12482.9663422,0.0105284256024,0.0253720007765,71.3111171362,0.000400617998197,133.621156597 af_val_0003,1600.32755515,0.0338230474173,0.0253792825467,39.6448710656,0.000429527467403,123.534534482 202 data/validation.csv === TEST_FEATURES === sample_id,frequency,attack_angle,chord_length,free_stream_velocity,suction_side_displacement_thickness af_val_0000,1998.53240303,12.3252028109,0.101492415739,39.5853154533,0.040726689388 af_val_0001,629.718430551,0.0186262062548,0.152498614376,39.5692331074,0.00193529208188 af_val_0002,12482.9663422,0.0105284256024,0.0253720007765,71.3111171362,0.000400617998197 af_val_0003,1600.32755515,0.0338230474173,0.0253792825467,39.6448710656,0.000429527467403 af_val_0004,4991.52268156,4.79375092775,0.0254026486661,71.338126346,0.000846568200014 af_val_0005,2495.86144742,0.0184944330227,0.0253695232334,39.6589100769,0.000429048074288 af_val_0006,2498.46919499,-0.0221311526004,0.0507598806268,55.4715562727,0.00076139950933 af_val_0007,400.055023138,4.02558783258,0.22886423046,31.7065298471,0.00508038188852 af_val_0008,1252.05696213,-0.033956960167,0.0508312723036,55.4216023091,0.000760162781301 af_val_0009,3153.0620092,-0.0187379247997,0.304951229779,31.7266222719,0.0033172021384 af_val_0010,6291.52560924,4.80686739476,0.0253900657548,71.3076410261,0.000846622561216 af_val_0011,2499.48153179,-0.0283046278268,0.304802654945,31.714108322,0.00332017638188 af_val_0012,3993.42797965,0.00242905842755,0.050739046381,55.519571195,0.00076333790294 af_val_0013,801.236174603,4.8347627987,0.0253800677813,39.628200731,0.000906448464204 af_val_0014,2504.24785431,3.33499766708,0.101486169411,71.2230178916,0.00203328705957 af_val_0015,4005.91664864,8.43054742545,0.050781496492,55.5508996708,0.0054472470328 af_val_0016,5005.51202734,4.76602056341,0.0254146764802,39.5428889537,0.000909692343177 af_val_0017,3154.55089729,12.3017464063,0.101570667133,39.6195959364,0.0407647946879 af_val_0018,3149.80872579,4.77187008506,0.0254243421119,71.3870262412,0.000848159837222 af_val_0019,998.303428181,6.67736699996,0.101654592175,55.5085985719,0.00522649318285 af_val_0020,500.450561494,6.71452065018,0.101720542321,55.5692532656,0.00520929310826 af_val_0021,800.787357893,17.4061955392,0.025425336639,39.5642882537,0.017263191882 af_val_0022,2001.20577618,-0.0305587984668,0.0507820242688,55.4501707401,0.000760512564219 af_val_0023,1248.70725388,3.31526608552,0.101606401399,71.1937166404,0.0020275259469 af_val_0024,3155.2828154,9.87155886369,0.152415248344,31.7212258081,0.0253334961215 af_val_0025,314.994039148,12.2780378494,0.101717191597,39.5690644654,0.0408329489427 af_val_0026,798.931879537,6.68878460505,0.101694222023,55.5321482532,0.00522281130998 af_val_0027,998.333264845,12.334777031,0.101554460172,55.5374339048,0.0368430722189 af_val_0028,400.019432519,8.36524539981,0.0507588951123,55.4640708311,0.00543869067677 af_val_0029,3154.19523204,-0.0305966105622,0.152271629543,39.6592773927,0.00192823733416 af_val_0030,629.878625292,17.4184416215,0.0254162514745,39.5795196568,0.0172534371927 af_val_0031,3996.4529 …[truncated]
cd /app && python3 --version && python3 -c "import sklearn; print('sklearn', sklearn.__version__)" && python3 -c "import numpy; print('numpy', numpy.__version__)" && python3 -c "import pandas; print('pandas', pandas.__version__)" && python3 -c "import scipy; print('scipy', scipy.__version__)"Python 3.12.3 sklearn 1.4.2 numpy 1.26.4 pandas 2.2.2 scipy 1.17.1
cd /app && for p in xgboost lightgbm catboost; do python3 -c "import $p; print('$p', $p.__version__)" 2>/dev/null || echo "$p NOT available"; donexgboost NOT available lightgbm NOT available catboost NOT available
cd /app && python3 -c "
import pandas as pd, numpy as np
tr = pd.read_csv('data/train.csv')
va = pd.read_csv('data/validation.csv')
print('train shape', tr.shape, 'val shape', va.shape)
print()
print('=== TRAIN describe ===')
print(tr.describe().T)
print()
print('=== target summary ===')
print('train target', tr.scaled_sound_pressure_level.min(), tr.scaled_sound_pressure_level.max(), tr.scaled_sound_pressure_level.mean())
print('val target', va.scaled_sound_pressure_level.min(), va.scaled_sound_pressure_level.max(), va.scaled_sound_pressure_level.mean())
"train shape (956, 7) val shape (201, 7)
=== TRAIN describe ===
count ... max
frequency 956.0 ... 20025.582040
attack_angle 956.0 ... 22.234770
chord_length 956.0 ... 0.305164
free_stream_velocity 956.0 ... 71.406950
suction_side_displacement_thickness 956.0 ... 0.058556
scaled_sound_pressure_level 956.0 ... 140.289040
[6 rows x 8 columns]
=== target summary ===
train target 104.935001617 140.289040194 125.40245300891945
val target 109.788714074 138.661406742 126.48301733910945cd /app && python3 -c "
import pandas as pd, numpy as np
pd.set_option('display.width', 200)
tr = pd.read_csv('data/train.csv')
va = pd.read_csv('data/validation.csv')
for name, df in [('train', tr), ('val', va)]:
print(f'=== {name} unique values ===')
for c in ['frequency','attack_angle','chord_length','free_stream_velocity','suction_side_displacement_thickness']:
print(f'{c}: n_unique={df[c].nunique()}, min={df[c].min():.5g}, max={df[c].max():.5g}')
print()
"=== train unique values === frequency: n_unique=956, min=199.66, max=20026 attack_angle: n_unique=956, min=-0.035, max=22.235 chord_length: n_unique=956, min=0.02537, max=0.30516 free_stream_velocity: n_unique=956, min=31.652, max=71.407 suction_side_displacement_thickness: n_unique=956, min=0.00041129, max=0.058556 === val unique values === frequency: n_unique=201, min=199.66, max=20017 attack_angle: n_unique=201, min=-0.034726, max=17.435 chord_length: n_unique=201, min=0.02537, max=0.30516 free_stream_velocity: n_unique=201, min=31.653, max=71.407 suction_side_displacement_thickness: n_unique=201, min=0.00039969, max=0.040928
cd /app && python3 -c "
import pandas as pd, numpy as np
tr = pd.read_csv('data/train.csv')
va = pd.read_csv('data/validation.csv')
both = pd.concat([tr, va], ignore_index=True)
# nominal frequency: 1/3 octave bands
bands = [200,250,315,400,500,630,800,1000,1250,1600,2000,2500,3150,4000,5000,6300,8000,10000,12500,16000,20000]
def nearest(v, arr):
arr = np.array(arr)
return arr[np.argmin(np.abs(arr[None,:]-np.array(v)[:,None]), axis=1)]
both['nom_freq'] = nearest(both.frequency.values, bands)
# relative error
both['freq_relerr'] = (both.frequency - both.nom_freq)/both.nom_freq
print('freq rel err range:', both.freq_relerr.min(), both.freq_relerr.max())
# chord nominal
chords=[0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]
both['nom_chord']=nearest(both.chord_length.values, chords)
print('chord rel err:', ((both.chord_length-both.nom_chord)/both.nom_chord).abs().max())
# velocity nominal
vels=[31.7,39.6,55.5,71.3]
both['nom_vel']=nearest(both.free_stream_velocity.values, vels)
print('vel abs err:', (both.free_stream_velocity-both.nom_vel).abs().max())
"freq rel err range: -0.001699996623750053 0.0016999952320000376 chord rel err: 0.0011999999980314066 vel abs err: 0.10694985240000676
cd /app && python3 -c "
import pandas as pd, numpy as np
tr = pd.read_csv('data/train.csv'); va = pd.read_csv('data/validation.csv')
both = pd.concat([tr, va], ignore_index=True)
# attack angle nominal candidates from UCI
angles=[0.0,1.5,2.0,3.0,4.0,5.3,6.7,7.3,8.4,8.9,9.9,11.2,12.3,12.6,15.4,15.6,17.4,19.7,22.2]
a=np.sort(both.attack_angle.unique())
# cluster by rounding to nearest 0.1 then group
from collections import Counter
r=np.round(both.attack_angle.values,1)
c=Counter(r)
print('distinct rounded-0.1 angles:', sorted(c.keys()))
"distinct rounded-0.1 angles: [0.0, 1.5, 2.0, 3.0, 3.3, 4.0, 4.2, 4.8, 5.3, 5.4, 6.7, 7.2, 7.3, 8.4, 8.9, 9.5, 9.9, 11.2, 12.3, 12.6, 12.7, 15.4, 15.6, 17.4, 19.7, 22.2]
cd /app && python3 -c "
import pandas as pd, numpy as np
tr = pd.read_csv('data/train.csv'); va = pd.read_csv('data/validation.csv')
both = pd.concat([tr, va], ignore_index=True)
bands = [200,250,315,400,500,630,800,1000,1250,1600,2000,2500,3150,4000,5000,6300,8000,10000,12500,16000,20000]
chords=[0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]; vels=[31.7,39.6,55.5,71.3]
def nearest(v, arr):
arr=np.array(arr); return arr[np.argmin(np.abs(arr[None,:]-np.array(v)[:,None]),axis=1)]
both['nom_chord']=nearest(both.chord_length.values,chords)
both['nom_vel']=nearest(both.free_stream_velocity.values,vels)
# cluster attack angle: round to nearest known angle set
angset=np.array([0.0,1.5,3.0,4.0,5.3,7.3,8.9,9.9,12.3,12.6,15.4,15.6,17.4,19.7,22.2])
both['nom_ang']=nearest(both.attack_angle.values, angset)
# group and look at displacement thickness spread within group
g=both.groupby(['nom_ang','nom_chord','nom_vel'])
print('n groups:', g.ngroups)
spread=g['suction_side_displacement_thickness'].agg(lambda x:(x.max()-x.min())/x.mean())
print('within-group rel spread of thickness: max', spread.max(),'median',spread.median())
sz=g.size()
print('group sizes: min',sz.min(),'max',sz.max(),'median',sz.median())
print('n groups with size<=3:', (sz<=3).sum())
"n groups: 82 within-group rel spread of thickness: max 0.004976953677377613 median 0.004940784123065735 group sizes: min 8 max 18 median 14.0 n groups with size<=3: 0
cd /app && python3 -c "
import pandas as pd, numpy as np
tr = pd.read_csv('data/train.csv'); va = pd.read_csv('data/validation.csv')
chords=[0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]; vels=[31.7,39.6,55.5,71.3]
angset=np.array([0.0,1.5,3.0,4.0,5.3,7.3,8.9,9.9,12.3,12.6,15.4,15.6,17.4,19.7,22.2])
def nearest(v, arr):
arr=np.array(arr); return arr[np.argmin(np.abs(arr[None,:]-np.array(v)[:,None]),axis=1)]
def cond(df):
return set(zip(nearest(df.attack_angle.values,angset), nearest(df.chord_length.values,chords), nearest(df.free_stream_velocity.values,vels)))
ctr=cond(tr); cva=cond(va)
print('train conds:', len(ctr), 'val conds:', len(cva))
print('overlap train&val:', len(ctr & cva))
print('val-only conds:', len(cva - ctr))
"train conds: 67 val conds: 15 overlap train&val: 0 val-only conds: 15
cd /app && python3 -c "
import pandas as pd, numpy as np
tr = pd.read_csv('data/train.csv'); va = pd.read_csv('data/validation.csv')
both=pd.concat([tr,va],ignore_index=True)
chords=[0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]; vels=[31.7,39.6,55.5,71.3]
angset=np.array([0.0,1.5,3.0,4.0,5.3,7.3,8.9,9.9,12.3,12.6,15.4,15.6,17.4,19.7,22.2])
def nearest(v, arr):
arr=np.array(arr); return arr[np.argmin(np.abs(arr[None,:]-np.array(v)[:,None]),axis=1)]
both['na']=nearest(both.attack_angle.values,angset)
# check within-group angle spread
both['nc']=nearest(both.chord_length.values,chords); both['nv']=nearest(both.free_stream_velocity.values,vels)
g=both.groupby(['na','nc','nv'])
aspread=g['attack_angle'].agg(lambda x:x.max()-x.min())
print('max within-group angle spread (deg):', aspread.max())
print('so angle jitter is small; nominal rounding is safe')
# Distribution of target within condition vs across
print('overall target std:', both.scaled_sound_pressure_level.std())
print('within-condition target std (mean):', g['scaled_sound_pressure_level'].std().mean())
"max within-group angle spread (deg): 0.06977577590000017 so angle jitter is small; nominal rounding is safe overall target std: 6.762171813067857 within-condition target std (mean): 5.5328541802922935
cd /app && mkdir -p scratch && cat > scratch/evallib.py << 'EOF'
import numpy as np, pandas as pd
from sklearn.model_selection import GroupKFold
FEATURES = ["frequency","attack_angle","chord_length","free_stream_velocity","suction_side_displacement_thickness"]
TARGET = "scaled_sound_pressure_level"
CHORDS=[0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]; VELS=[31.7,39.6,55.5,71.3]
ANGSET=np.array([0.0,1.5,3.0,4.0,5.3,7.3,8.9,9.9,12.3,12.6,15.4,15.6,17.4,19.7,22.2])
def nearest(v, arr):
arr=np.array(arr); return arr[np.argmin(np.abs(arr[None,:]-np.array(v)[:,None]),axis=1)]
def condition_ids(df):
na=nearest(df.attack_angle.values,ANGSET); nc=nearest(df.chord_length.values,CHORDS); nv=nearest(df.free_stream_velocity.values,VELS)
return pd.Series([f"{a:.2f}_{c:.4f}_{v:.1f}" for a,c,v in zip(na,nc,nv)], index=df.index)
def load():
tr=pd.read_csv('/app/data/train.csv'); va=pd.read_csv('/app/data/validation.csv')
return tr, va
def metrics_from(df_pred):
# df_pred has columns: cond, err
g=df_pred.groupby('cond')['err']
cond_rmse=g.apply(lambda e: np.sqrt(np.mean(e**2)))
allerr=df_pred['err'].values
return {
'rmse': float(np.sqrt(np.mean(allerr**2))),
'mae': float(np.mean(np.abs(allerr))),
'p90_cond': float(np.percentile(cond_rmse,90)),
'p95_cond': float(np.percentile(cond_rmse,95)),
'max_cond': float(cond_rmse.max()),
'n_cond': len(cond_rmse),
}
def grouped_cv(make_model, data, n_splits=10, seed=0, repeats=1):
"""Return pooled out-of-fold metrics across grouped CV."""
rows=[]
groups=condition_ids(data).values
uniq=np.unique(groups)
rng=np.random.RandomState(seed)
for rep in range(repeats):
# shuffle group assignment to folds
gk=GroupKFold(n_splits=n_splits)
# To get variety across repeats, remap groups via permutation
perm=rng.permutation(len(uniq))
gmap={u:i for i,u in enumerate(uniq[perm])}
gidx=np.array([gmap[g] for g in groups])
for tr_idx, te_idx in gk.split(data, data[TARGET], gidx):
Xtr=data.iloc[tr_idx]; Xte=data.iloc[te_idx]
m=make_model(); m.fit(Xtr[FEATURES], Xtr[TARGET].values)
pred=m.predict(Xte[FEATURES])
rows.append(pd.DataFrame({'cond':groups[te_idx],'err':pred-Xte[TARGET].values,'rep':rep}))
allp=pd.concat(rows, ignore_index=True)
# per-rep metrics then average for stability of percentile stats
res=[]
for rep in range(repeats):
res.append(metrics_from(allp[allp.rep==rep]))
out={k:np.mean([r[k] for r in res]) for k in res[0]}
return out, allp
def val_holdout(make_model, tr, va):
m=make_model(); m.fit(tr[FEATURES], tr[TARGET].values)
pred=m.predict(va[FEATURES])
dfp=pd.DataFrame({'cond':condition_ids(va).values,'err':pred-va[TARGET].values})
return metrics_from(dfp)
def report(name, d):
print(f"{name:28s} rmse={d['rmse']:.3f} mae={d['mae']:.3f} p90={d['p90_cond']:.3f} p95={d['p95_cond']:.3f} max={d['max_cond']:.3f} (nc={d.get('n_cond','?')})")
EOF
echo "written"written
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd
import evallib as E
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
from sklearn.ensemble import RandomForestRegressor, ExtraTreesRegressor, HistGradientBoostingRegressor
from sklearn.neural_network import MLPRegressor
tr, va = E.load()
both = pd.concat([tr,va], ignore_index=True)
def ridge():
return make_pipeline(StandardScaler(), Ridge(alpha=10.0))
def rf():
return RandomForestRegressor(n_estimators=300, n_jobs=-1, random_state=0)
def et():
return ExtraTreesRegressor(n_estimators=300, n_jobs=-1, random_state=0)
def hgb():
return HistGradientBoostingRegressor(random_state=0)
for name, mk in [('ridge',ridge),('rf',rf),('et',et),('hgb',hgb)]:
d,_=E.grouped_cv(mk, both, n_splits=10, seed=0, repeats=1)
E.report('cv10 '+name, d)
" 2>&1 | grep -v Warningcv10 ridge rmse=5.024 mae=3.920 p90=7.196 p95=7.722 max=9.453 (nc=82.0) cv10 rf rmse=2.983 mae=2.270 p90=4.416 p95=4.744 max=5.962 (nc=82.0) cv10 et rmse=2.677 mae=2.038 p90=4.103 p95=4.207 max=5.412 (nc=82.0) cv10 hgb rmse=2.923 mae=2.234 p90=4.081 p95=4.751 max=5.690 (nc=82.0)
cd /app/scratch && cat > feats.py << 'EOF'
import numpy as np, pandas as pd
C_SOUND=340.0
def phys_features(X):
f=X['frequency'].astype(float).values
a=X['attack_angle'].astype(float).values
c=X['chord_length'].astype(float).values
U=X['free_stream_velocity'].astype(float).values
d=X['suction_side_displacement_thickness'].astype(float).values
logf=np.log10(f); logd=np.log10(d); logc=np.log10(c); logU=np.log10(U)
St=f*d/U # Strouhal
logSt=np.log10(St)
M=U/C_SOUND
logM=np.log10(M)
Re_c=U*c/1.5e-5 # chord Reynolds
logRe=np.log10(Re_c)
out=pd.DataFrame(index=X.index)
out['logf']=logf; out['a']=a; out['logc']=logc; out['logU']=logU; out['logd']=logd
out['logSt']=logSt; out['logSt2']=logSt**2; out['logSt3']=logSt**3
out['logM']=logM; out['logRe']=logRe
out['f_d_U']=St
out['a2']=a**2
out['logf2']=logf**2
out['logf_a']=logf*a
out['logf_logc']=logf*logc
out['logf_logU']=logf*logU
out['logd_a']=logd*a
out['logSt_a']=logSt*a
out['logSt_logc']=logSt*logc
out['logSt_logU']=logSt*logU
out['a_logU']=a*logU
return out
EOF
python3 -c "
import numpy as np, pandas as pd, evallib as E, feats
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
from sklearn.preprocessing import FunctionTransformer
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
ft=FunctionTransformer(feats.phys_features)
def ridge(alpha=1.0):
return make_pipeline(ft, StandardScaler(), Ridge(alpha=alpha))
for al in [0.3,1.0,3.0,10.0]:
d,_=E.grouped_cv(lambda al=al: ridge(al), both, n_splits=10, seed=0)
E.report(f'ridge-phys a={al}', d)
" 2>&1 | grep -v Warningridge-phys a=0.3 rmse=3.459 mae=2.625 p90=4.607 p95=5.296 max=10.485 (nc=82.0) ridge-phys a=1.0 rmse=3.460 mae=2.624 p90=4.749 p95=5.350 max=10.447 (nc=82.0) ridge-phys a=3.0 rmse=3.490 mae=2.646 p90=5.005 p95=5.489 max=10.200 (nc=82.0) ridge-phys a=10.0 rmse=3.577 mae=2.714 p90=5.263 p95=5.846 max=9.661 (nc=82.0)
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler, FunctionTransformer
from sklearn.neural_network import MLPRegressor
from sklearn.ensemble import ExtraTreesRegressor, HistGradientBoostingRegressor
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
ft=FunctionTransformer(feats.phys_features)
def mlp(h=(128,128),a=1e-3):
return make_pipeline(ft, StandardScaler(), MLPRegressor(hidden_layer_sizes=h, alpha=a, max_iter=2000, random_state=0, early_stopping=False))
def et():
return make_pipeline(ft, ExtraTreesRegressor(n_estimators=400,n_jobs=-1,random_state=0))
def hgb():
return make_pipeline(ft, HistGradientBoostingRegressor(random_state=0, max_iter=500, learning_rate=0.05))
for name,mk in [('et-phys',et),('hgb-phys',hgb),('mlp-128x128',lambda:mlp((128,128)))]:
d,_=E.grouped_cv(mk, both, n_splits=10, seed=0)
E.report(name, d)
"et-phys rmse=2.292 mae=1.702 p90=3.296 p95=3.738 max=5.327 (nc=82.0) hgb-phys rmse=2.351 mae=1.729 p90=3.304 p95=4.084 max=6.343 (nc=82.0) mlp-128x128 rmse=2.642 mae=1.936 p90=3.377 p95=4.152 max=8.766 (nc=82.0)
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler, FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor, HistGradientBoostingRegressor
tr,va=E.load()
ft=FunctionTransformer(feats.phys_features)
def et():
return make_pipeline(ft, ExtraTreesRegressor(n_estimators=600,n_jobs=-1,random_state=0))
def hgb():
return make_pipeline(ft, HistGradientBoostingRegressor(random_state=0, max_iter=500, learning_rate=0.05))
for name,mk in [('et-phys',et),('hgb-phys',hgb)]:
d=E.val_holdout(mk, tr, va)
E.report('VAL '+name, d)
print('val n conditions:', E.condition_ids(va).nunique())
"VAL et-phys rmse=2.321 mae=1.835 p90=3.022 p95=3.345 max=3.870 (nc=15) VAL hgb-phys rmse=2.507 mae=1.954 p90=3.348 p95=3.798 max=4.542 (nc=15) val n conditions: 15
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
both['cond']=E.condition_ids(both)
# pick a condition with many rows
for cond, g in list(both.groupby('cond'))[:3]:
g=g.sort_values('frequency')
print('COND', cond, 'n=',len(g))
for _,r in g.iterrows():
print(f' f={r.frequency:8.1f} St={r.frequency*r.suction_side_displacement_thickness/r.free_stream_velocity:8.4f} spl={r.scaled_sound_pressure_level:8.3f}')
"COND 0.00_0.0254_31.7 n= 13 f= 998.3 St= 0.0139 spl= 122.810 f= 1248.5 St= 0.0174 spl= 125.831 f= 1599.9 St= 0.0223 spl= 127.397 f= 2002.2 St= 0.0278 spl= 129.063 f= 2504.2 St= 0.0347 spl= 131.499 f= 3154.5 St= 0.0437 spl= 134.064 f= 4001.7 St= 0.0554 spl= 136.108 f= 4996.1 St= 0.0691 spl= 137.852 f= 6290.0 St= 0.0871 spl= 137.943 f= 7987.3 St= 0.1107 spl= 136.743 f= 9992.2 St= 0.1386 spl= 133.846 f= 12505.3 St= 0.1735 spl= 128.639 f= 16022.7 St= 0.2224 spl= 125.285 COND 0.00_0.0254_39.6 n= 11 f= 1600.3 St= 0.0173 spl= 123.535 f= 1998.1 St= 0.0216 spl= 129.673 f= 2495.9 St= 0.0270 spl= 131.746 f= 3145.3 St= 0.0340 spl= 133.448 f= 3997.7 St= 0.0431 spl= 135.769 f= 5003.3 St= 0.0540 spl= 137.170 f= 6309.7 St= 0.0680 spl= 137.646 f= 8013.1 St= 0.0865 spl= 136.839 f= 10009.0 St= 0.1082 spl= 134.448 f= 12496.3 St= 0.1353 spl= 130.241 f= 15978.6 St= 0.1733 spl= 126.113 COND 0.00_0.0254_55.5 n= 11 f= 2003.2 St= 0.0149 spl= 127.974 f= 2503.9 St= 0.0187 spl= 130.693 f= 3152.2 St= 0.0235 spl= 131.388 f= 3997.9 St= 0.0297 spl= 134.363 f= 4992.6 St= 0.0371 spl= 137.158 f= 6289.5 St= 0.0467 spl= 137.655 f= 7991.9 St= 0.0593 spl= 137.884 f= 10001.6 St= 0.0742 spl= 136.405 f= 12515.7 St= 0.0929 spl= 133.375 f= 16027.2 St= 0.1191 spl= 127.638 f= 20025.6 St= 0.1489 spl= 123.868
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
both['cond']=E.condition_ids(both)
both['logSt']=np.log10(both.frequency*both.suction_side_displacement_thickness/both.free_stream_velocity)
res=[]
for cond,g in both.groupby('cond'):
if len(g)<6: continue
x=g.logSt.values; y=g.scaled_sound_pressure_level.values
# fit cubic in logSt
c=np.polyfit(x,y,min(4,len(g)-1)); yp=np.polyval(c,x)
res.append(y-yp)
res=np.concatenate(res)
print('within-condition residual std after poly4(logSt):', np.std(res), 'max abs', np.max(np.abs(res)))
"within-condition residual std after poly4(logSt): 1.0144417385588087 max abs 6.020707582023817
cd /app/scratch && cat > feats.py << 'EOF'
import numpy as np, pandas as pd
C_SOUND=343.0; NU=1.5e-5
def phys_features(X):
f=X['frequency'].astype(float).values
a=X['attack_angle'].astype(float).values
c=X['chord_length'].astype(float).values
U=X['free_stream_velocity'].astype(float).values
d=X['suction_side_displacement_thickness'].astype(float).values
logf=np.log10(f); logd=np.log10(d); logc=np.log10(c); logU=np.log10(U)
M=U/C_SOUND; logM=np.log10(M)
St=f*d/U; logSt=np.log10(St)
logRe=np.log10(U*c/NU)
logRed=np.log10(U*d/NU)
out=pd.DataFrame(index=X.index)
# base
out['logf']=logf; out['a']=a; out['logc']=logc; out['logU']=logU; out['logd']=logd
out['M']=M; out['logM']=logM
# strouhal family
out['logSt']=logSt; out['logSt2']=logSt**2; out['logSt3']=logSt**3; out['logSt4']=logSt**4
out['logRe']=logRe; out['logRed']=logRed
# angle family
out['a2']=a**2; out['a3']=a**3; out['sqrt_a']=np.sqrt(np.abs(a))
# interactions (peak-shift & level)
out['logSt_logM']=logSt*logM
out['logSt_a']=logSt*a
out['logSt_logc']=logSt*logc
out['logSt_logU']=logSt*logU
out['logSt_logd']=logSt*logd
out['a_logU']=a*logU
out['a_logc']=a*logc
out['logd_logU']=logd*logU
out['logc_logU']=logc*logU
out['a_logd']=a*logd
return out
EOF
python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
ft=FunctionTransformer(feats.phys_features)
for ne in [400,800]:
for mf in [0.5,0.7,1.0]:
for ms in [1,2,3]:
def mk(ne=ne,mf=mf,ms=ms):
return make_pipeline(ft, ExtraTreesRegressor(n_estimators=ne,max_features=mf,min_samples_leaf=ms,n_jobs=-1,random_state=0))
d,_=E.grouped_cv(mk, both, n_splits=10, seed=0)
dv=E.val_holdout(mk, tr, va)
print(f'ET ne={ne} mf={mf} ms={ms} | CV rmse={d[\"rmse\"]:.3f} p90={d[\"p90_cond\"]:.3f} max={d[\"max_cond\"]:.3f} | VAL rmse={dv[\"rmse\"]:.3f} mae={dv[\"mae\"]:.3f} p90={dv[\"p90_cond\"]:.3f} max={dv[\"max_cond\"]:.3f}')
"[stderr] ET ne=400 mf=0.5 ms=1 | CV rmse=2.312 p90=3.163 max=5.396 | VAL rmse=2.369 mae=1.885 p90=3.003 max=4.624 ET ne=400 mf=0.5 ms=2 | CV rmse=2.335 p90=3.224 max=5.447 | VAL rmse=2.436 mae=1.929 p90=3.070 max=4.559 ET ne=400 mf=0.5 ms=3 | CV rmse=2.405 p90=3.324 max=5.305 | VAL rmse=2.536 mae=2.004 p90=3.131 max=4.737 ET ne=400 mf=0.7 ms=1 | CV rmse=2.274 p90=3.108 max=5.473 | VAL rmse=2.347 mae=1.853 p90=3.042 max=4.422 ET ne=400 mf=0.7 ms=2 | CV rmse=2.330 p90=3.155 max=5.374 | VAL rmse=2.389 mae=1.891 p90=2.890 max=4.413 ET ne=400 mf=0.7 ms=3 | CV rmse=2.383 p90=3.211 max=5.306 | VAL rmse=2.471 mae=1.970 p90=3.038 max=4.569 ET ne=400 mf=1.0 ms=1 | CV rmse=2.282 p90=3.174 max=5.451 | VAL rmse=2.283 mae=1.805 p90=2.872 max=4.117 ET ne=400 mf=1.0 ms=2 | CV rmse=2.307 p90=3.149 max=5.352 | VAL rmse=2.308 mae=1.827 p90=2.881 max=4.154 ET ne=400 mf=1.0 ms=3 | CV rmse=2.365 p90=3.257 max=5.287 | VAL rmse=2.424 mae=1.921 p90=2.986 max=4.444 ET ne=800 mf=0.5 ms=1 | CV rmse=2.302 p90=3.175 max=5.448 | VAL rmse=2.378 mae=1.883 p90=3.069 max=4.580 ET ne=800 mf=0.5 ms=2 | CV rmse=2.328 p90=3.163 max=5.366 | VAL rmse=2.430 mae=1.931 p90=3.052 max=4.578 ET ne=800 mf=0.5 ms=3 | CV rmse=2.405 p90=3.289 max=5.310 | VAL rmse=2.540 mae=2.018 p90=3.134 max=4.717 ET ne=800 mf=0.7 ms=1 | CV rmse=2.279 p90=3.163 max=5.466 | VAL rmse=2.338 mae=1.855 p90=2.977 max=4.431 ET ne=800 mf=0.7 ms=2 | CV rmse=2.330 p90=3.144 max=5.407 | VAL rmse=2.391 mae=1.895 p90=2.918 max=4.438 ET ne=800 mf=0.7 ms=3 | CV rmse=2.381 p90=3.222 max=5.294 | VAL rmse=2.482 mae=1.975 p90=3.049 max=4.642 ET ne=800 mf=1.0 ms=1 | CV rmse=2.275 p90=3.143 max=5.435 | VAL rmse=2.283 mae=1.806 p90=2.913 max=4.104Shell command timed out. Tool interaction exceeded 300 seconds.
restart: true
<system>tool has been restarted.</system>
cd /app/scratch && timeout 250 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler, FunctionTransformer
from sklearn.kernel_ridge import KernelRidge
from sklearn.svm import SVR
tr,va=E.load()
ft=FunctionTransformer(feats.phys_features)
def kr(alpha,gamma):
return make_pipeline(ft, StandardScaler(), KernelRidge(kernel='rbf', alpha=alpha, gamma=gamma))
def svr(C,g,e):
return make_pipeline(ft, StandardScaler(), SVR(kernel='rbf', C=C, gamma=g, epsilon=e))
print('--- KernelRidge RBF (val holdout) ---')
for alpha in [0.01,0.03,0.1]:
for gamma in [0.02,0.05,0.1]:
dv=E.val_holdout(lambda: kr(alpha,gamma), tr, va)
print(f'KR a={alpha} g={gamma} | VAL rmse={dv[\"rmse\"]:.3f} mae={dv[\"mae\"]:.3f} p90={dv[\"p90_cond\"]:.3f} p95={dv[\"p95_cond\"]:.3f} max={dv[\"max_cond\"]:.3f}')
"--- KernelRidge RBF (val holdout) --- KR a=0.01 g=0.02 | VAL rmse=3.312 mae=2.334 p90=4.915 p95=6.571 max=8.739 KR a=0.01 g=0.05 | VAL rmse=6.712 mae=3.724 p90=12.677 p95=18.419 max=20.313 KR a=0.01 g=0.1 | VAL rmse=13.140 mae=7.057 p90=25.618 p95=35.392 max=40.978 KR a=0.03 g=0.02 | VAL rmse=3.431 mae=2.438 p90=4.662 p95=6.340 max=9.254 KR a=0.03 g=0.05 | VAL rmse=6.751 mae=3.888 p90=13.304 p95=18.648 max=19.115 KR a=0.03 g=0.1 | VAL rmse=13.350 mae=7.346 p90=26.548 p95=36.229 max=40.469 KR a=0.1 g=0.02 | VAL rmse=3.814 mae=2.679 p90=5.088 p95=7.036 max=10.526 KR a=0.1 g=0.05 | VAL rmse=7.278 mae=4.378 p90=13.226 p95=18.564 max=21.693 KR a=0.1 g=0.1 | VAL rmse=14.002 mae=7.884 p90=28.565 p95=38.163 max=40.343
cd /app/scratch && cat > mlpens.py << 'EOF'
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin, clone
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler, FunctionTransformer
from sklearn.neural_network import MLPRegressor
import feats
class MLPEnsemble(BaseEstimator, RegressorMixin):
def __init__(self, hidden=(96,96), alpha=3e-3, n=6, max_iter=1500):
self.hidden=hidden; self.alpha=alpha; self.n=n; self.max_iter=max_iter
def fit(self, X, y):
self.models_=[]
for s in range(self.n):
m=make_pipeline(FunctionTransformer(feats.phys_features), StandardScaler(),
MLPRegressor(hidden_layer_sizes=self.hidden, alpha=self.alpha, max_iter=self.max_iter,
random_state=s, activation='tanh', solver='lbfgs'))
m.fit(X, y); self.models_.append(m)
return self
def predict(self, X):
return np.mean([m.predict(X) for m in self.models_], axis=0)
EOF
timeout 250 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
from mlpens import MLPEnsemble
tr,va=E.load()
for hid in [(64,64),(96,96),(128,64)]:
for al in [1e-3,3e-3]:
dv=E.val_holdout(lambda hid=hid,al=al: MLPEnsemble(hidden=hid, alpha=al, n=6), tr, va)
print(f'MLP-lbfgs {hid} a={al} | VAL rmse={dv[\"rmse\"]:.3f} mae={dv[\"mae\"]:.3f} p90={dv[\"p90_cond\"]:.3f} p95={dv[\"p95_cond\"]:.3f} max={dv[\"max_cond\"]:.3f}')
"MLP-lbfgs (64, 64) a=0.001 | VAL rmse=2.671 mae=1.958 p90=3.737 p95=4.446 max=6.060 MLP-lbfgs (64, 64) a=0.003 | VAL rmse=2.676 mae=1.984 p90=3.599 p95=4.497 max=6.393 MLP-lbfgs (96, 96) a=0.001 | VAL rmse=3.316 mae=2.456 p90=5.516 p95=6.705 max=7.317 MLP-lbfgs (96, 96) a=0.003 | VAL rmse=3.162 mae=2.355 p90=4.382 p95=5.547 max=7.779 MLP-lbfgs (128, 64) a=0.001 | VAL rmse=2.853 mae=2.237 p90=4.356 p95=4.525 max=4.578 MLP-lbfgs (128, 64) a=0.003 | VAL rmse=2.705 mae=2.120 p90=4.043 p95=4.394 max=4.842
cd /app/scratch && timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor
tr,va=E.load()
ft=FunctionTransformer(feats.phys_features)
m=make_pipeline(ft, ExtraTreesRegressor(n_estimators=600,max_features=1.0,min_samples_leaf=1,n_jobs=-1,random_state=0))
m.fit(tr[E.FEATURES], tr[E.TARGET].values)
va=va.copy(); va['pred']=m.predict(va[E.FEATURES]); va['err']=va.pred-va[E.TARGET]; va['cond']=E.condition_ids(va)
cr=va.groupby('cond').apply(lambda g: pd.Series({'rmse':np.sqrt(np.mean(g.err**2)),'n':len(g),'ang':g.attack_angle.mean(),'chord':g.chord_length.mean(),'vel':g.free_stream_velocity.mean()}))
cr=cr.sort_values('rmse',ascending=False)
pd.set_option('display.width',200)
print(cr)
print('worst rows:')
print(va.reindex(va.err.abs().sort_values(ascending=False).index)[['frequency','attack_angle','chord_length','free_stream_velocity','scaled_sound_pressure_level','pred','err']].head(10).to_string())
"rmse n ang chord vel
cond
0.00_0.0254_71.3 4.109520 10.0 0.016846 0.025399 71.354087
12.30_0.1016_55.5 3.030280 16.0 12.301458 0.101604 55.514674
8.90_0.0508_55.5 2.692189 12.0 8.404495 0.050814 55.542907
12.30_0.1016_39.6 2.658206 16.0 12.301440 0.101606 39.584861
5.30_0.0254_71.3 2.559216 11.0 4.802919 0.025397 71.304755
17.40_0.0254_39.6 2.347896 15.0 17.399239 0.025400 39.582282
7.30_0.1016_55.5 2.216706 8.0 6.681868 0.101618 55.497174
0.00_0.3048_31.7 2.130686 18.0 0.002455 0.304850 31.696343
4.00_0.2286_31.7 2.113023 15.0 4.001627 0.228600 31.682930
9.90_0.1524_31.7 1.895252 16.0 9.900994 0.152411 31.714249
3.00_0.1016_71.3 1.803011 12.0 3.311753 0.101572 71.263363
0.00_0.0254_39.6 1.517054 11.0 -0.008083 0.025397 39.627032
0.00_0.1524_39.6 1.406635 14.0 -0.006416 0.152387 39.622576
5.30_0.0254_39.6 1.303249 14.0 4.799941 0.025402 39.581086
0.00_0.0508_55.5 1.200071 13.0 -0.002772 0.050792 55.497693
worst rows:
frequency attack_angle chord_length free_stream_velocity scaled_sound_pressure_level pred err
67 5008.303211 8.422113 0.050759 55.529703 117.250270 123.678292 6.428022
122 4006.744847 0.028491 0.025424 71.406950 138.246982 131.994460 -6.252523
143 3154.408908 0.019006 0.025430 71.402443 136.451767 130.511446 -5.940322
63 500.621679 12.311237 0.101694 55.466616 134.797789 128.957148 -5.840641
51 400.679871 12.298246 0.101717 55.446012 137.146779 131.644395 -5.502385
123 7990.733167 0.030525 0.025390 71.368089 130.098567 135.308195 5.209628
124 20017.248537 -0.015175 0.025372 71.250436 123.640576 128.760670 5.120095
79 499.364466 17.428098 0.025404 39.596497 118.166382 123.256349 5.089967
27 998.333265 12.334777 0.101554 55.537434 127.659047 122.627273 -5.031775
157 199.676136 0.032548 0.305164 31.689631 116.745986 121.516772 4.770786cd /app/scratch && cat > gam.py << 'EOF'
import numpy as np, pandas as pd
from sklearn.base import BaseEstimator, RegressorMixin
from sklearn.preprocessing import SplineTransformer, StandardScaler
from sklearn.linear_model import Ridge
C_SOUND=343.0; NU=1.5e-5
def base_vars(X):
f=X['frequency'].astype(float).values; a=X['attack_angle'].astype(float).values
c=X['chord_length'].astype(float).values; U=X['free_stream_velocity'].astype(float).values
d=X['suction_side_displacement_thickness'].astype(float).values
logf=np.log10(f); logd=np.log10(d); logc=np.log10(c); logU=np.log10(U)
M=U/C_SOUND; logM=np.log10(M); logSt=np.log10(f*d/U); logRe=np.log10(U*c/NU)
return dict(f=f,a=a,c=c,U=U,d=d,logf=logf,logd=logd,logc=logc,logU=logU,M=M,logM=logM,logSt=logSt,logRe=logRe)
class GAM(BaseEstimator, RegressorMixin):
def __init__(self, n_knots=10, degree=3, alpha=1.0, modulate=True):
self.n_knots=n_knots; self.degree=degree; self.alpha=alpha; self.modulate=modulate
def _design(self, X, fit=False):
v=base_vars(X)
logSt=v['logSt'].reshape(-1,1)
if fit:
self.spl_=SplineTransformer(n_knots=self.n_knots, degree=self.degree, extrapolation='continue')
S=self.spl_.fit_transform(logSt)
else:
S=self.spl_.transform(logSt)
a=v['a']; logU=v['logU']; logd=v['logd']; logc=v['logc']; logRe=v['logRe']; M=v['M']; logM=v['logM']
# level block (smooth overall offset per condition)
level=np.column_stack([a, a**2, a**3, logc, logU, logd, logRe, M,
a*logU, a*logc, a*logd, logc*logU, logd*logU, logc*logd, a*M])
blocks=[S, level]
if self.modulate:
# let spectral shape shift/scale with Mach, angle, Re
blocks.append(S*M.reshape(-1,1))
blocks.append(S*a.reshape(-1,1))
blocks.append(S*logRe.reshape(-1,1))
D=np.hstack(blocks)
return D
def fit(self, X, y):
D=self._design(X, fit=True)
self.sc_=StandardScaler().fit(D)
self.rid_=Ridge(alpha=self.alpha).fit(self.sc_.transform(D), y)
return self
def predict(self, X):
D=self._design(X, fit=False)
return self.rid_.predict(self.sc_.transform(D))
EOF
timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
from gam import GAM
tr,va=E.load()
for nk in [8,12,16]:
for al in [0.3,1.0,3.0]:
dv=E.val_holdout(lambda nk=nk,al=al: GAM(n_knots=nk, alpha=al), tr, va)
print(f'GAM nk={nk} a={al} | VAL rmse={dv[\"rmse\"]:.3f} mae={dv[\"mae\"]:.3f} p90={dv[\"p90_cond\"]:.3f} p95={dv[\"p95_cond\"]:.3f} max={dv[\"max_cond\"]:.3f}')
"GAM nk=8 a=0.3 | VAL rmse=3.077 mae=2.400 p90=4.331 p95=4.628 max=5.257 GAM nk=8 a=1.0 | VAL rmse=3.107 mae=2.416 p90=4.387 p95=4.780 max=5.338 GAM nk=8 a=3.0 | VAL rmse=3.191 mae=2.466 p90=4.681 p95=4.949 max=5.434 GAM nk=12 a=0.3 | VAL rmse=3.401 mae=2.482 p90=4.779 p95=5.470 max=6.449 GAM nk=12 a=1.0 | VAL rmse=3.349 mae=2.479 p90=4.910 p95=5.333 max=5.771 GAM nk=12 a=3.0 | VAL rmse=3.375 mae=2.512 p90=5.065 p95=5.288 max=5.326 GAM nk=16 a=0.3 | VAL rmse=19.794 mae=3.766 p90=4.752 p95=25.752 max=74.162 GAM nk=16 a=1.0 | VAL rmse=18.335 mae=3.671 p90=4.883 p95=24.134 max=68.549 GAM nk=16 a=3.0 | VAL rmse=17.303 mae=3.640 p90=5.040 p95=23.015 max=64.522
cd /app/scratch && timeout 260 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
from sklearn.ensemble import ExtraTreesRegressor, HistGradientBoostingRegressor, RandomForestRegressor
from mlpens import MLPEnsemble
tr,va=E.load()
ft=FunctionTransformer(feats.phys_features)
def fit_pred(m):
m.fit(tr[E.FEATURES], tr[E.TARGET].values); return m.predict(va[E.FEATURES])
et=make_pipeline(ft, ExtraTreesRegressor(n_estimators=600,max_features=1.0,min_samples_leaf=1,n_jobs=-1,random_state=0))
hgb=make_pipeline(ft, HistGradientBoostingRegressor(random_state=0,max_iter=600,learning_rate=0.03,max_leaf_nodes=31,l2_regularization=1.0))
rf=make_pipeline(ft, RandomForestRegressor(n_estimators=600,max_features=0.5,n_jobs=-1,random_state=0))
mlp=MLPEnsemble(hidden=(128,64),alpha=3e-3,n=6)
P={}
for nm,m in [('et',et),('hgb',hgb),('rf',rf),('mlp',mlp)]:
P[nm]=fit_pred(m)
y=va[E.TARGET].values; cond=E.condition_ids(va).values
def rep(nm,pred):
dfp=pd.DataFrame({'cond':cond,'err':pred-y}); d=E.metrics_from(dfp)
print(f'{nm:22s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
for nm in P: rep(nm,P[nm])
print('--- blends ---')
rep('et+hgb', 0.5*P['et']+0.5*P['hgb'])
rep('et+rf', 0.5*P['et']+0.5*P['rf'])
rep('et+hgb+rf', (P['et']+P['hgb']+P['rf'])/3)
rep('et+hgb+mlp', (P['et']+P['hgb']+P['mlp'])/3)
rep('et.5 hgb.3 mlp.2', 0.5*P['et']+0.3*P['hgb']+0.2*P['mlp'])
rep('all4', (P['et']+P['hgb']+P['rf']+P['mlp'])/4)
"et rmse=2.286 mae=1.812 p90=2.895 p95=3.354 max=4.110 hgb rmse=2.664 mae=2.119 p90=3.802 p95=3.955 max=4.075 rf rmse=2.731 mae=2.152 p90=3.878 p95=4.079 max=4.481 mlp rmse=2.705 mae=2.120 p90=4.043 p95=4.394 max=4.842 --- blends --- et+hgb rmse=2.386 mae=1.870 p90=3.308 p95=3.483 max=3.890 et+rf rmse=2.475 mae=1.961 p90=3.358 p95=3.655 max=4.213 et+hgb+rf rmse=2.482 mae=1.947 p90=3.491 p95=3.672 max=4.056 et+hgb+mlp rmse=2.353 mae=1.842 p90=3.297 p95=3.580 max=4.017 et.5 hgb.3 mlp.2 rmse=2.316 mae=1.813 p90=3.189 p95=3.486 max=3.960 all4 rmse=2.410 mae=1.892 p90=3.416 p95=3.664 max=4.088
cd /app/scratch && cat >> feats.py << 'EOF'
def phys_plus_raw(X):
out=phys_features(X)
out['r_frequency']=X['frequency'].astype(float).values
out['r_chord']=X['chord_length'].astype(float).values
out['r_U']=X['free_stream_velocity'].astype(float).values
out['r_thick']=X['suction_side_displacement_thickness'].astype(float).values
return out
EOF
cat > logo.py << 'EOF'
import numpy as np, pandas as pd, evallib as E
from sklearn.model_selection import LeaveOneGroupOut
def logo_eval(make_model, data, groups):
logo=LeaveOneGroupOut(); rows=[]
for tri,tei in logo.split(data, data[E.TARGET], groups):
Xtr=data.iloc[tri]; Xte=data.iloc[tei]
m=make_model(); m.fit(Xtr[E.FEATURES], Xtr[E.TARGET].values)
rows.append(pd.DataFrame({'cond':groups[tei],'err':m.predict(Xte[E.FEATURES])-Xte[E.TARGET].values}))
return E.metrics_from(pd.concat(rows,ignore_index=True))
EOF
timeout 280 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings, logo
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
groups=E.condition_ids(both).values
ftp=FunctionTransformer(feats.phys_features); ftr=FunctionTransformer(feats.phys_plus_raw)
for nm,ft,ms in [('phys ms1',ftp,1),('phys ms2',ftp,2),('phys+raw ms1',ftr,1)]:
def mk(ft=ft,ms=ms): return make_pipeline(ft, ExtraTreesRegressor(n_estimators=400,max_features=1.0,min_samples_leaf=ms,n_jobs=-1,random_state=0))
d=logo.logo_eval(mk, both, groups)
print(f'LOGO ET {nm:14s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f} nc={d[\"n_cond\"]}')
"LOGO ET phys ms1 rmse=2.229 mae=1.677 p90=2.938 p95=3.791 max=5.310 nc=82
cd /app/scratch && timeout 280 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import LeaveOneGroupOut
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
groups=E.condition_ids(both).values
ft=FunctionTransformer(feats.phys_features)
logo=LeaveOneGroupOut(); recs=[]
both['logSt']=np.log10(both.frequency*both.suction_side_displacement_thickness/both.free_stream_velocity)
allerr=np.zeros(len(both))
for tri,tei in logo.split(both, both[E.TARGET], groups):
m=make_pipeline(ft, ExtraTreesRegressor(n_estimators=250,max_features=1.0,n_jobs=-1,random_state=0))
m.fit(both.iloc[tri][E.FEATURES], both.iloc[tri][E.TARGET].values)
allerr[tei]=m.predict(both.iloc[tei][E.FEATURES])-both.iloc[tei][E.TARGET].values
both['err']=allerr
cr=both.groupby('cond' if 'cond' in both else E.condition_ids(both)).apply(lambda g: pd.Series({
'rmse':np.sqrt(np.mean(g.err**2)),'bias':g.err.mean(),'shapestd':g.err.std(),'n':len(g),
'ang':g.attack_angle.mean(),'chord':g.chord_length.mean(),'vel':g.free_stream_velocity.mean()}))
cr=cr.sort_values('rmse',ascending=False)
pd.set_option('display.width',220)
print('Top 12 worst conditions (LOGO):')
print(cr.head(12).to_string())
print()
print('fraction of per-cond RMSE^2 explained by bias: %.2f'%((cr.bias**2).sum()/(cr.rmse**2).sum()))
print('overall mae=%.3f rmse=%.3f'%(both.err.abs().mean(), np.sqrt((both.err**2).mean())))
" Top 12 worst conditions (LOGO):
rmse bias shapestd n ang chord vel
19.70_0.0508_71.3 5.266751 4.513275 2.817067 14.0 19.693583 0.050796 71.338290
12.60_0.1524_39.6 4.707538 3.510492 3.239318 16.0 12.601388 0.152409 39.582407
0.00_0.0254_71.3 4.348009 -0.173864 4.579538 10.0 0.016846 0.025399 71.354087
22.20_0.0254_39.6 4.091264 -3.310042 2.488988 15.0 22.203822 0.025400 39.578030
7.30_0.2286_71.3 3.784907 -2.506978 2.928586 16.0 7.301237 0.228608 71.271586
8.90_0.1016_71.3 3.592398 -0.506206 3.673193 16.0 8.900988 0.101595 71.274643
12.60_0.0254_39.6 3.167261 -0.796580 3.159797 17.0 12.700376 0.025401 39.610924
12.60_0.0254_71.3 3.020368 -0.896234 2.973104 17.0 12.700300 0.025403 71.288649
12.30_0.1016_71.3 2.941690 -2.061203 2.167637 16.0 12.301385 0.101601 71.326339
8.90_0.1016_39.6 2.940753 -2.005637 2.221219 16.0 8.901225 0.101598 39.588817
8.90_0.0508_31.7 2.867372 2.256893 1.855016 11.0 8.407879 0.050780 31.729451
8.90_0.0508_55.5 2.860792 1.431779 2.586850 12.0 8.404495 0.050814 55.542907
fraction of per-cond RMSE^2 explained by bias: 0.34
overall mae=1.677 rmse=2.232cd /app/scratch && timeout 280 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.model_selection import LeaveOneGroupOut
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both)
groups=both['cond'].values
ft=FunctionTransformer(feats.phys_features)
logo=LeaveOneGroupOut(); allpred=np.zeros(len(both))
for tri,tei in logo.split(both, both[E.TARGET], groups):
m=make_pipeline(ft, ExtraTreesRegressor(n_estimators=300,max_features=1.0,n_jobs=-1,random_state=0))
m.fit(both.iloc[tri][E.FEATURES], both.iloc[tri][E.TARGET].values)
allpred[tei]=m.predict(both.iloc[tei][E.FEATURES])
both['pred']=allpred; both['err']=both.pred-both[E.TARGET]
both['logSt']=np.log10(both.frequency*both.suction_side_displacement_thickness/both.free_stream_velocity)
both.to_csv('logo_err.csv',index=False)
print('saved. overall mae=%.3f rmse=%.3f'%(both.err.abs().mean(), np.sqrt((both.err**2).mean())))
# residual vs logSt bins
both['stbin']=pd.qcut(both.logSt,12)
print(both.groupby('stbin').err.agg(['mean','std','count']).to_string())
"saved. overall mae=1.679 rmse=2.232
mean std count
stbin
(-1.972, -1.455] 0.064218 2.613014 97
(-1.455, -1.273] -0.030475 2.187422 96
(-1.273, -1.123] -0.345577 1.920842 97
(-1.123, -0.98] -0.325503 1.579596 96
(-0.98, -0.845] -0.018651 1.796083 96
(-0.845, -0.725] -0.121455 1.816678 97
(-0.725, -0.603] -0.176119 1.945132 96
(-0.603, -0.452] 0.136524 2.520363 96
(-0.452, -0.305] 0.223502 3.067336 97
(-0.305, -0.104] 0.107929 2.379537 96
(-0.104, 0.176] 0.043937 2.035926 96
(0.176, 0.97] 0.279626 2.503514 97cd /app/scratch && python3 -c "
import numpy as np, pandas as pd
b=pd.read_csv('logo_err.csv')
print('=== residual vs attack angle bins ===')
b['abin']=pd.cut(b.attack_angle,[-1,1,3,5,8,11,14,18,23])
print(b.groupby('abin').err.agg(['mean','std','count']).to_string())
print()
print('=== residual vs velocity ===')
print(b.groupby(np.round(b.free_stream_velocity,0)).err.agg(['mean','std','count']).to_string())
print()
print('=== rmse by chord ===')
print(b.groupby(np.round(b.chord_length,4)).err.agg(lambda e:np.sqrt((e**2).mean())).to_string())
"=== residual vs attack angle bins ===
mean std count
abin
(-1, 1] -0.033732 1.855029 228
(1, 3] 0.400925 1.182346 101
(3, 5] -0.176427 1.683209 203
(5, 8] 0.014283 2.227979 142
(8, 11] -0.078549 2.335728 154
(11, 14] -0.257430 2.918074 158
(14, 18] 0.170894 2.261547 142
(18, 23] 0.483847 4.762845 29
=== residual vs velocity ===
mean std count
free_stream_velocity
32.0 0.126353 1.746944 224
40.0 -0.153245 2.290999 371
55.0 0.193812 1.841339 113
56.0 0.097010 1.922133 109
71.0 -0.056999 2.630813 340
=== rmse by chord ===
chord_length
0.0254 2.403508
0.0507 2.646462
0.0508 2.676696
0.0509 2.270826
0.1015 1.662316
0.1016 2.345226
0.1017 2.398462
0.1522 1.953479
0.1523 2.411606
0.1524 2.229082
0.1525 3.145053
0.1526 2.235597
0.2283 1.665817
0.2284 1.555614
0.2285 1.635468
0.2286 1.660600
0.2287 2.685110
0.2288 2.078694
0.2289 2.262811
0.3044 1.347848
0.3045 1.438914
0.3046 2.086008
0.3047 1.316491
0.3048 1.063478
0.3049 1.348966
0.3050 1.019681
0.3051 1.503548
0.3052 1.727675
[stderr]
<string>:6: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.cd /app/scratch && cat > fastcv.py << 'EOF'
import numpy as np, pandas as pd, evallib as E
from sklearn.model_selection import GroupKFold
def fast_eval(make_model, data, groups, n_splits=20, seed=0):
uniq=np.unique(groups); rng=np.random.RandomState(seed); perm=rng.permutation(len(uniq))
gmap={u:i for i,u in enumerate(uniq[perm])}; gidx=np.array([gmap[g] for g in groups])
gk=GroupKFold(n_splits=n_splits); rows=[]
for tri,tei in gk.split(data, data[E.TARGET], gidx):
m=make_model(); m.fit(data.iloc[tri][E.FEATURES], data.iloc[tri][E.TARGET].values)
rows.append(pd.DataFrame({'cond':groups[tei],'err':m.predict(data.iloc[tei][E.FEATURES])-data.iloc[tei][E.TARGET].values}))
return E.metrics_from(pd.concat(rows,ignore_index=True))
EOF
timeout 280 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, warnings, fastcv
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
ft=FunctionTransformer(feats.phys_features)
def mk(ms=1,ne=400,mf=1.0,boot=False):
return make_pipeline(ft, ExtraTreesRegressor(n_estimators=ne,max_features=mf,min_samples_leaf=ms,bootstrap=boot,n_jobs=-1,random_state=0))
for cfg in [dict(),dict(ne=800),dict(mf=0.7),dict(boot=True),dict(boot=True,mf=0.7)]:
d=fastcv.fast_eval(lambda cfg=cfg: mk(**cfg), both, groups, n_splits=20)
print(f'ET {str(cfg):30s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"ET {} rmse=2.256 mae=1.695 p90=3.092 p95=3.804 max=5.287
ET {'ne': 800} rmse=2.257 mae=1.697 p90=3.064 p95=3.801 max=5.339
ET {'mf': 0.7} rmse=2.264 mae=1.709 p90=3.034 p95=3.743 max=5.256
ET {'boot': True} rmse=2.343 mae=1.759 p90=3.163 p95=3.808 max=5.377
ET {'boot': True, 'mf': 0.7} rmse=2.348 mae=1.766 p90=3.142 p95=3.657 max=5.246cd /app/scratch && cat > feats2.py << 'EOF'
import numpy as np, pandas as pd
C=343.0; NU=1.5e-5
def make(X, use_angle=True, extra=True):
f=X['frequency'].astype(float).values; a=X['attack_angle'].astype(float).values
c=X['chord_length'].astype(float).values; U=X['free_stream_velocity'].astype(float).values
d=X['suction_side_displacement_thickness'].astype(float).values
logf=np.log10(f); logd=np.log10(d); logc=np.log10(c); logU=np.log10(U)
M=U/C; logSt=np.log10(f*d/U); logRe=np.log10(U*c/NU); logRed=np.log10(U*d/NU)
dc=d/c # thickness-to-chord
o=pd.DataFrame(index=X.index)
o['logf']=logf; o['logc']=logc; o['logU']=logU; o['logd']=logd; o['M']=M
o['logSt']=logSt; o['logSt2']=logSt**2; o['logSt3']=logSt**3
o['logRe']=logRe; o['logRed']=logRed; o['logdc']=np.log10(dc)
o['logSt_logM']=logSt*np.log10(M); o['logSt_logc']=logSt*logc
o['logSt_logU']=logSt*logU; o['logSt_logd']=logSt*logd; o['logSt_logRe']=logSt*logRe
o['logc_logU']=logc*logU; o['logd_logU']=logd*logU; o['logc_logd']=logc*logd
if use_angle:
o['a']=a; o['a2']=a**2; o['a3']=a**3
o['a_logU']=a*logU; o['a_logc']=a*logc; o['a_logd']=a*logd
o['logSt_a']=logSt*a
if extra:
o['logSt_logdc']=logSt*np.log10(dc)
o['logRed2']=logRed**2
return o
def with_angle(X): return make(X, True, True)
def no_angle(X): return make(X, False, True)
EOF
timeout 280 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats2, warnings, fastcv
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
for nm,fn in [('with_angle',feats2.with_angle),('no_angle',feats2.no_angle)]:
ft=FunctionTransformer(fn)
def mk(ft=ft): return make_pipeline(ft, ExtraTreesRegressor(n_estimators=500,max_features=0.7,n_jobs=-1,random_state=0))
d=fastcv.fast_eval(mk, both, groups, n_splits=20)
print(f'{nm:12s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"with_angle rmse=2.243 mae=1.691 p90=3.098 p95=3.673 max=5.299 no_angle rmse=2.348 mae=1.771 p90=3.048 p95=3.755 max=6.401
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
b=pd.read_csv('logo_err.csv') # has cond, pred (LOGO ET), logSt, target
y=b[E.TARGET].values
def metrics(pred):
dfp=pd.DataFrame({'cond':b['cond'].values,'err':pred-y}); return E.metrics_from(dfp)
def rep(nm,pred):
d=metrics(pred); print(f'{nm:22s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
rep('ET raw', b.pred.values)
# within-condition polynomial smoothing of the ET predictions
for deg in [2,3,4,5]:
sm=b.pred.values.copy()
for c,g in b.groupby('cond'):
idx=g.index.values; x=g.logSt.values; p=g.pred.values
dd=min(deg,len(g)-1)
if dd>=1:
coef=np.polyfit(x,p,dd); sm[idx]=np.polyval(coef,x)
rep(f'ET smooth poly{deg}', sm)
"ET raw rmse=2.232 mae=1.679 p90=2.954 p95=3.778 max=5.279 ET smooth poly2 rmse=2.497 mae=1.891 p90=3.498 p95=4.140 max=5.499 ET smooth poly3 rmse=2.356 mae=1.762 p90=3.386 p95=4.210 max=5.210 ET smooth poly4 rmse=2.311 mae=1.720 p90=3.200 p95=3.751 max=5.298 ET smooth poly5 rmse=2.270 mae=1.695 p90=3.158 p95=3.787 max=5.247
cd /app/scratch && cat > gen_oof.py << 'EOF'
import numpy as np, pandas as pd, evallib as E, feats, feats2, warnings
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.ensemble import ExtraTreesRegressor, HistGradientBoostingRegressor
from sklearn.model_selection import GroupKFold
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
uniq=np.unique(groups); rng=np.random.RandomState(1); perm=rng.permutation(len(uniq))
gmap={u:i for i,u in enumerate(uniq[perm])}; gidx=np.array([gmap[g] for g in groups])
gk=GroupKFold(n_splits=15)
def ET(fn,mf=0.7,ne=400,ms=1): return make_pipeline(FunctionTransformer(fn), ExtraTreesRegressor(n_estimators=ne,max_features=mf,min_samples_leaf=ms,n_jobs=-1,random_state=0))
def HGB(fn): return make_pipeline(FunctionTransformer(fn), HistGradientBoostingRegressor(random_state=0,max_iter=600,learning_rate=0.03,max_leaf_nodes=31,l2_regularization=1.0,min_samples_leaf=15))
bases={
'et_phys': lambda: ET(feats.phys_features,1.0),
'et_phys07': lambda: ET(feats.phys_features,0.7),
'et_f2': lambda: ET(feats2.with_angle,0.7),
'et_noang': lambda: ET(feats2.no_angle,0.7),
'hgb_phys': lambda: HGB(feats.phys_features),
'hgb_f2': lambda: HGB(feats2.with_angle),
}
oof={k:np.zeros(len(both)) for k in bases}
for tri,tei in gk.split(both, both[E.TARGET], gidx):
for k,mkf in bases.items():
m=mkf(); m.fit(both.iloc[tri][E.FEATURES], both.iloc[tri][E.TARGET].values)
oof[k][tei]=m.predict(both.iloc[tei][E.FEATURES])
out=pd.DataFrame(oof); out['cond']=groups; out['y']=both[E.TARGET].values
out.to_csv('oof.csv',index=False)
print('done', out.shape)
EOF
nohup timeout 290 python3 gen_oof.py > gen_oof.log 2>&1 &
echo "started"; sleep 5; cat gen_oof.logstarted
cd /app/scratch && sleep 120 && cat gen_oof.log && ls -la oof.csv 2>/dev/null
<system>Tool ran without output or errors</system>
cd /app/scratch && sleep 90 && cat gen_oof.log && echo "---" && ls -la oof.csv 2>/dev/null
done (1157, 8) --- -rw-r--r-- 1 h2tagent h2tagent 166470 Jun 21 06:01 oof.csv
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E, itertools
o=pd.read_csv('oof.csv'); y=o.y.values; cond=o['cond'].values
cols=['et_phys','et_phys07','et_f2','et_noang','hgb_phys','hgb_f2']
def metrics(pred):
return E.metrics_from(pd.DataFrame({'cond':cond,'err':pred-y}))
def rep(nm,pred):
d=metrics(pred); print(f'{nm:30s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
for c in cols: rep(c, o[c].values)
print('--- simple blends ---')
rep('all ET avg', o[['et_phys','et_phys07','et_f2']].mean(1).values)
rep('all ET+noang', o[['et_phys','et_phys07','et_f2','et_noang']].mean(1).values)
rep('ET3+HGB2', (o[['et_phys','et_phys07','et_f2']].mean(1)*0.7+o[['hgb_phys','hgb_f2']].mean(1)*0.3).values)
rep('all6 avg', o[cols].mean(1).values)
rep('ET-heavy', (o[['et_phys','et_phys07','et_f2','et_noang']].mean(1)*0.8+o[['hgb_phys','hgb_f2']].mean(1)*0.2).values)
"et_phys rmse=2.224 mae=1.674 p90=2.895 p95=3.568 max=5.338 et_phys07 rmse=2.226 mae=1.679 p90=2.959 p95=3.725 max=5.253 et_f2 rmse=2.200 mae=1.658 p90=2.916 p95=3.619 max=5.263 et_noang rmse=2.310 mae=1.744 p90=3.057 p95=3.791 max=6.350 hgb_phys rmse=2.288 mae=1.707 p90=3.234 p95=3.705 max=5.785 hgb_f2 rmse=2.224 mae=1.655 p90=3.004 p95=3.497 max=5.352 --- simple blends --- all ET avg rmse=2.211 mae=1.666 p90=2.916 p95=3.554 max=5.282 all ET+noang rmse=2.208 mae=1.663 p90=2.935 p95=3.405 max=5.514 ET3+HGB2 rmse=2.151 mae=1.609 p90=2.933 p95=3.833 max=5.208 all6 avg rmse=2.147 mae=1.607 p90=2.930 p95=3.819 max=5.349 ET-heavy rmse=2.163 mae=1.622 p90=2.951 p95=3.532 max=5.410
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o=pd.read_csv('oof.csv'); y=o.y.values; cond=o['cond'].values
cols=['et_phys','et_phys07','et_f2','et_noang','hgb_phys','hgb_f2']
pred=o[cols].mean(1).values
o['err']=pred-y
cr=o.groupby('cond').err.apply(lambda e: np.sqrt(np.mean(e**2))).sort_values(ascending=False)
print('n conditions:', len(cr))
print('percentiles of per-cond rmse: p50=%.3f p75=%.3f p90=%.3f p95=%.3f max=%.3f'%(
np.percentile(cr,50),np.percentile(cr,75),np.percentile(cr,90),np.percentile(cr,95),cr.max()))
print('# conditions > 2.35:', (cr>2.35).sum(), 'out of', len(cr))
print('# conditions > 3.0:', (cr>3.0).sum())
print()
print('worst 10:'); print(cr.head(10).to_string())
"n conditions: 82 percentiles of per-cond rmse: p50=1.812 p75=2.270 p90=2.930 p95=3.819 max=5.349 # conditions > 2.35: 20 out of 82 # conditions > 3.0: 8 worst 10: cond 19.70_0.0508_71.3 5.349295 12.60_0.1524_39.6 4.223878 0.00_0.0254_71.3 4.223847 22.20_0.0254_39.6 3.866872 7.30_0.2286_71.3 3.847916 8.90_0.1016_71.3 3.269371 12.30_0.1016_71.3 3.100570 12.60_0.0254_71.3 3.043161 12.60_0.0254_39.6 2.937647 8.90_0.0508_55.5 2.859824
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o=pd.read_csv('oof.csv')
# parse cond string a_c_v
parts=o['cond'].str.split('_',expand=True).astype(float); parts.columns=['a','c','v']
o=pd.concat([o,parts],axis=1)
condtab=o.drop_duplicates('cond')[['cond','a','c','v']]
print('conditions per nominal angle:')
print(condtab.groupby('a').size().to_string())
print()
print('conditions per (angle) with which chords exist:')
for a in sorted(condtab.a.unique()):
sub=condtab[condtab.a==a]
print(f' ang {a:5.1f}: {len(sub):2d} conds, chords={sorted(set(sub.c))}, vels={sorted(set(sub.v))}')
"conditions per nominal angle: a 0.0 17 1.5 6 3.0 6 4.0 7 5.3 5 7.3 9 8.9 5 9.9 6 12.3 6 12.6 4 15.4 3 15.6 2 17.4 4 19.7 1 22.2 1 conditions per (angle) with which chords exist: ang 0.0: 17 conds, chords=[0.0254, 0.0508, 0.1016, 0.1524, 0.2286, 0.3048], vels=[31.7, 39.6, 55.5, 71.3] ang 1.5: 6 conds, chords=[0.2286, 0.3048], vels=[31.7, 39.6, 55.5, 71.3] ang 3.0: 6 conds, chords=[0.1016, 0.3048], vels=[31.7, 39.6, 55.5, 71.3] ang 4.0: 7 conds, chords=[0.0508, 0.2286, 0.3048], vels=[31.7, 39.6, 55.5, 71.3] ang 5.3: 5 conds, chords=[0.0254, 0.1524, 0.2286], vels=[31.7, 39.6, 71.3] ang 7.3: 9 conds, chords=[0.1016, 0.1524, 0.2286], vels=[31.7, 39.6, 55.5, 71.3] ang 8.9: 5 conds, chords=[0.0508, 0.1016], vels=[31.7, 39.6, 55.5, 71.3] ang 9.9: 6 conds, chords=[0.0254, 0.1524], vels=[31.7, 39.6, 55.5, 71.3] ang 12.3: 6 conds, chords=[0.0508, 0.1016], vels=[31.7, 39.6, 55.5, 71.3] ang 12.6: 4 conds, chords=[0.0254, 0.1524], vels=[39.6, 71.3] ang 15.4: 3 conds, chords=[0.0508], vels=[39.6, 55.5, 71.3] ang 15.6: 2 conds, chords=[0.1016], vels=[39.6, 71.3] ang 17.4: 4 conds, chords=[0.0254], vels=[31.7, 39.6, 55.5, 71.3] ang 19.7: 1 conds, chords=[0.0508], vels=[71.3] ang 22.2: 1 conds, chords=[0.0254], vels=[39.6]
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o=pd.read_csv('oof.csv'); y=o.y.values
cols=['et_phys','et_phys07','et_f2','et_noang','hgb_phys','hgb_f2']
o['pred']=o[cols].mean(1).values; o['err']=o.pred-y
# per-condition stats
g=o.groupby('cond')
cr=g.err.apply(lambda e: np.sqrt(np.mean(e**2)))
rows_by_cond={c:sub.err.values for c,sub in g}
conds=list(cr.index)
T=dict(rmse=2.25,mae=1.55,p90=2.35,p95=4.75,max=5.60)
rng=np.random.RandomState(0)
for size in [15,20,25,30]:
cnt={k:0 for k in T}; passall=0; N=4000
for _ in range(N):
S=rng.choice(conds,size=size,replace=False)
errs=np.concatenate([rows_by_cond[c] for c in S])
crr=np.array([cr[c] for c in S])
m=dict(rmse=np.sqrt(np.mean(errs**2)),mae=np.mean(np.abs(errs)),
p90=np.percentile(crr,90),p95=np.percentile(crr,95),max=crr.max())
ok=True
for k in T:
if m[k]<=T[k]: cnt[k]+=1
else: ok=False
passall+=ok
print(f'subset={size}: '+' '.join(f'{k}={cnt[k]/N:.2f}' for k in T)+f' | ALL={passall/N:.3f}')
"subset=15: rmse=0.70 mae=0.39 p90=0.10 p95=1.00 max=1.00 | ALL=0.093 subset=20: rmse=0.71 mae=0.37 p90=0.07 p95=1.00 max=1.00 | ALL=0.065 subset=25: rmse=0.73 mae=0.33 p90=0.03 p95=1.00 max=1.00 | ALL=0.030 subset=30: rmse=0.76 mae=0.31 p90=0.02 p95=1.00 max=1.00 | ALL=0.016
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
from scipy.interpolate import UnivariateSpline
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
both['cond']=E.condition_ids(both)
both['logSt']=np.log10(both.frequency*both.suction_side_displacement_thickness/both.free_stream_velocity)
res=[]
res_sm=[]
for c,g in both.groupby('cond'):
if len(g)<8: continue
g=g.sort_values('logSt'); x=g.logSt.values; y=g.scaled_sound_pressure_level.values
# smoothing spline with small s -> flexible
try:
sp=UnivariateSpline(x,y,k=4,s=len(x)*0.15) # moderate smoothing
res_sm.append(y-sp(x))
except Exception as ex:
pass
# local second difference based noise estimate
d2=y[2:]-2*y[1:-1]+y[:-2]
res.append(d2/np.sqrt(6)) # scaled 2nd diff ~ noise if trend smooth
rs=np.concatenate(res); rsm=np.concatenate(res_sm)
print('2nd-diff noise estimate std: %.3f'%np.std(rs))
print('flexible spline residual std: %.3f max abs %.3f'%(np.std(rsm), np.max(np.abs(rsm))))
print('fraction |spline resid|>1.5: %.3f'%np.mean(np.abs(rsm)>1.5))
"2nd-diff noise estimate std: 0.766 flexible spline residual std: 0.367 max abs 1.154 fraction |spline resid|>1.5: 0.000
cd /app/scratch && cat > gam2.py << 'EOF'
import numpy as np, pandas as pd
from sklearn.base import BaseEstimator, RegressorMixin
from sklearn.preprocessing import SplineTransformer, StandardScaler
from sklearn.linear_model import Ridge
C=343.0; NU=1.5e-5
def bv(X):
f=X['frequency'].astype(float).values; a=X['attack_angle'].astype(float).values
c=X['chord_length'].astype(float).values; U=X['free_stream_velocity'].astype(float).values
d=X['suction_side_displacement_thickness'].astype(float).values
return dict(a=a,logc=np.log10(c),logU=np.log10(U),logd=np.log10(d),M=U/C,
logSt=np.log10(f*d/U),logRe=np.log10(U*c/NU),logRed=np.log10(U*d/NU))
class GAM2(BaseEstimator, RegressorMixin):
def __init__(self, n_knots=12, degree=3, alpha=3.0):
self.n_knots=n_knots; self.degree=degree; self.alpha=alpha
def _design(self, X, fit=False):
v=bv(X); St=v['logSt'].reshape(-1,1)
if fit:
self.spl_=SplineTransformer(n_knots=self.n_knots, degree=self.degree, extrapolation='linear')
S=self.spl_.fit_transform(St)
else: S=self.spl_.transform(St)
a=v['a']; logU=v['logU']; logd=v['logd']; logc=v['logc']; logRe=v['logRe']; M=v['M']; logRed=v['logRed']
level=np.column_stack([a,a**2,a**3,logc,logU,logd,logRe,logRed,M,
a*logU,a*logc,a*logd,logc*logU,logd*logU,logc*logd,a*M,logRe*a])
mod=np.column_stack([a,M,logRe,logc,logd,logU]) # shape modulators
blocks=[S, level]
for j in range(mod.shape[1]):
blocks.append(S*mod[:,j:j+1])
return np.hstack(blocks)
def fit(self, X, y):
D=self._design(X, True); self.sc_=StandardScaler().fit(D)
self.rid_=Ridge(alpha=self.alpha).fit(self.sc_.transform(D), y); return self
def predict(self, X):
return self.rid_.predict(self.sc_.transform(self._design(X, False)))
EOF
timeout 120 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings, fastcv
warnings.filterwarnings('ignore')
from gam2 import GAM2
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
for nk in [8,12,16]:
for al in [1.0,3.0,10.0]:
d=fastcv.fast_eval(lambda nk=nk,al=al: GAM2(n_knots=nk,alpha=al), both, groups, n_splits=15)
print(f'GAM2 nk={nk} a={al:4.1f} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"GAM2 nk=8 a= 1.0 rmse=3.507 mae=2.580 p90=4.663 p95=5.479 max=13.509 GAM2 nk=8 a= 3.0 rmse=3.497 mae=2.586 p90=4.653 p95=5.322 max=12.934 GAM2 nk=8 a=10.0 rmse=3.487 mae=2.597 p90=4.804 p95=5.158 max=11.968 GAM2 nk=12 a= 1.0 rmse=3.548 mae=2.590 p90=4.675 p95=5.518 max=13.587 GAM2 nk=12 a= 3.0 rmse=3.530 mae=2.598 p90=4.709 p95=5.359 max=13.025 GAM2 nk=12 a=10.0 rmse=3.524 mae=2.613 p90=4.967 p95=5.186 max=11.996 GAM2 nk=16 a= 1.0 rmse=8.260 mae=2.807 p90=4.666 p95=5.599 max=67.960 GAM2 nk=16 a= 3.0 rmse=7.872 mae=2.803 p90=4.845 p95=5.407 max=64.096 GAM2 nk=16 a=10.0 rmse=6.973 mae=2.787 p90=4.984 p95=5.654 max=54.895
cd /app/scratch && timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E, feats, feats2, warnings, fastcv
warnings.filterwarnings('ignore')
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
from sklearn.neighbors import KNeighborsRegressor
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
for k in [5,8,12]:
for w in ['distance']:
def mk(k=k,w=w): return make_pipeline(FunctionTransformer(feats2.with_angle), StandardScaler(), KNeighborsRegressor(n_neighbors=k,weights=w))
d=fastcv.fast_eval(mk, both, groups, n_splits=15)
print(f'KNN k={k} {w:8s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"KNN k=5 distance rmse=2.804 mae=2.080 p90=4.039 p95=4.826 max=6.870 KNN k=8 distance rmse=2.724 mae=2.035 p90=3.792 p95=4.849 max=6.151 KNN k=12 distance rmse=2.725 mae=2.047 p90=4.039 p95=4.466 max=5.882
cd /app/scratch && cat > twostage.py << 'EOF'
import numpy as np, pandas as pd
from sklearn.base import BaseEstimator, RegressorMixin, clone
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
from sklearn.linear_model import Ridge
from sklearn.ensemble import ExtraTreesRegressor
import feats, feats2
class TwoStage(BaseEstimator, RegressorMixin):
def __init__(self, feat_fn=feats2.with_angle, alpha=1.0, ne=500, mf=0.7, ms=1):
self.feat_fn=feat_fn; self.alpha=alpha; self.ne=ne; self.mf=mf; self.ms=ms
def fit(self, X, y):
self.lin_=make_pipeline(FunctionTransformer(self.feat_fn), StandardScaler(), Ridge(alpha=self.alpha))
self.lin_.fit(X,y)
r=y-self.lin_.predict(X)
self.tree_=make_pipeline(FunctionTransformer(self.feat_fn), ExtraTreesRegressor(
n_estimators=self.ne,max_features=self.mf,min_samples_leaf=self.ms,n_jobs=-1,random_state=0))
self.tree_.fit(X,r); return self
def predict(self, X):
return self.lin_.predict(X)+self.tree_.predict(X)
EOF
timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings, fastcv, feats, feats2
warnings.filterwarnings('ignore')
from twostage import TwoStage
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
for al in [0.3,1.0,3.0,10.0]:
d=fastcv.fast_eval(lambda al=al: TwoStage(feats2.with_angle, alpha=al), both, groups, n_splits=15)
print(f'TwoStage f2 a={al:5.1f} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"TwoStage f2 a= 0.3 rmse=2.445 mae=1.662 p90=2.773 p95=3.468 max=11.485 TwoStage f2 a= 1.0 rmse=2.425 mae=1.661 p90=2.879 p95=3.467 max=11.034 TwoStage f2 a= 3.0 rmse=2.397 mae=1.658 p90=3.042 p95=3.480 max=10.403 TwoStage f2 a= 10.0 rmse=2.341 mae=1.632 p90=2.964 p95=3.715 max=9.592
cd /app/scratch && cat >> twostage.py << 'EOF'
C=343.0; NU=1.5e-5
def level_feats(X):
c=X['chord_length'].astype(float).values; U=X['free_stream_velocity'].astype(float).values
d=X['suction_side_displacement_thickness'].astype(float).values
a=X['attack_angle'].astype(float).values
o=pd.DataFrame(index=X.index)
o['logc']=np.log10(c); o['logU']=np.log10(U); o['logd']=np.log10(d)
o['logRe']=np.log10(U*c/NU); o['logRed']=np.log10(U*d/NU); o['M']=U/C
o['a']=a
return o
class TwoStageSafe(BaseEstimator, RegressorMixin):
def __init__(self, tree_feat=feats2.with_angle, alpha=5.0, ne=500, mf=0.7, ms=1):
self.tree_feat=tree_feat; self.alpha=alpha; self.ne=ne; self.mf=mf; self.ms=ms
def fit(self, X, y):
self.lin_=make_pipeline(FunctionTransformer(level_feats), StandardScaler(), Ridge(alpha=self.alpha))
self.lin_.fit(X,y); r=y-self.lin_.predict(X)
self.tree_=make_pipeline(FunctionTransformer(self.tree_feat), ExtraTreesRegressor(
n_estimators=self.ne,max_features=self.mf,min_samples_leaf=self.ms,n_jobs=-1,random_state=0))
self.tree_.fit(X,r); return self
def predict(self, X):
return self.lin_.predict(X)+self.tree_.predict(X)
EOF
timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings, fastcv, feats2
warnings.filterwarnings('ignore')
from twostage import TwoStageSafe
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
for al in [1.0,3.0,10.0,30.0]:
d=fastcv.fast_eval(lambda al=al: TwoStageSafe(feats2.with_angle, alpha=al), both, groups, n_splits=15)
print(f'TwoStageSafe a={al:5.1f} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"TwoStageSafe a= 1.0 rmse=2.185 mae=1.643 p90=2.886 p95=3.966 max=5.015 TwoStageSafe a= 3.0 rmse=2.178 mae=1.636 p90=2.839 p95=3.962 max=5.028 TwoStageSafe a= 10.0 rmse=2.163 mae=1.627 p90=2.826 p95=3.877 max=5.075 TwoStageSafe a= 30.0 rmse=2.159 mae=1.622 p90=2.805 p95=3.822 max=5.154
cd /app/scratch && cat >> twostage.py << 'EOF'
from sklearn.ensemble import HistGradientBoostingRegressor
class TwoStageGen(BaseEstimator, RegressorMixin):
"""Safe linear level + arbitrary tree stage on residual."""
def __init__(self, tree_feat=feats2.with_angle, alpha=20.0, kind='et', ne=500, mf=0.7, ms=1):
self.tree_feat=tree_feat; self.alpha=alpha; self.kind=kind; self.ne=ne; self.mf=mf; self.ms=ms
def _tree(self):
if self.kind=='et':
return ExtraTreesRegressor(n_estimators=self.ne,max_features=self.mf,min_samples_leaf=self.ms,n_jobs=-1,random_state=0)
if self.kind=='hgb':
return HistGradientBoostingRegressor(random_state=0,max_iter=700,learning_rate=0.03,max_leaf_nodes=31,l2_regularization=1.0,min_samples_leaf=20)
def fit(self, X, y):
self.lin_=make_pipeline(FunctionTransformer(level_feats), StandardScaler(), Ridge(alpha=self.alpha))
self.lin_.fit(X,y); r=y-self.lin_.predict(X)
self.tree_=make_pipeline(FunctionTransformer(self.tree_feat), self._tree()); self.tree_.fit(X,r); return self
def predict(self, X):
return self.lin_.predict(X)+self.tree_.predict(X)
EOF
cat > gen_oof2.py << 'EOF'
import numpy as np, pandas as pd, evallib as E, feats, feats2, warnings
warnings.filterwarnings('ignore')
from sklearn.model_selection import GroupKFold
from twostage import TwoStageGen
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
uniq=np.unique(groups); rng=np.random.RandomState(1); perm=rng.permutation(len(uniq))
gmap={u:i for i,u in enumerate(uniq[perm])}; gidx=np.array([gmap[g] for g in groups])
gk=GroupKFold(n_splits=15)
bases={
'ts_et_f2': lambda: TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=0.7),
'ts_et_f2b': lambda: TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=1.0),
'ts_et_phys': lambda: TwoStageGen(feats.phys_features, alpha=30, kind='et', mf=0.7),
'ts_et_noang':lambda: TwoStageGen(feats2.no_angle, alpha=30, kind='et', mf=0.7),
'ts_hgb_f2': lambda: TwoStageGen(feats2.with_angle, alpha=30, kind='hgb'),
'ts_hgb_phys':lambda: TwoStageGen(feats.phys_features, alpha=30, kind='hgb'),
}
oof={k:np.zeros(len(both)) for k in bases}
for tri,tei in gk.split(both, both[E.TARGET], gidx):
for k,mkf in bases.items():
m=mkf(); m.fit(both.iloc[tri][E.FEATURES], both.iloc[tri][E.TARGET].values)
oof[k][tei]=m.predict(both.iloc[tei][E.FEATURES])
out=pd.DataFrame(oof); out['cond']=groups; out['y']=both[E.TARGET].values
out.to_csv('oof2.csv',index=False); print('done', out.shape)
EOF
nohup timeout 290 python3 gen_oof2.py > gen_oof2.log 2>&1 &
echo started; sleep 4; cat gen_oof2.logstarted
cd /app/scratch && sleep 180 && cat gen_oof2.log && ls -la oof2.csv 2>/dev/null
<system>Tool ran without output or errors</system>
cd /app/scratch && sleep 60 && cat gen_oof2.log && echo "---" && ls -la oof2.csv 2>/dev/null
done (1157, 8) --- -rw-r--r-- 1 h2tagent h2tagent 166528 Jun 21 06:13 oof2.csv
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o=pd.read_csv('oof2.csv'); y=o.y.values; cond=o['cond'].values
cols=['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']
def metrics(p): return E.metrics_from(pd.DataFrame({'cond':cond,'err':p-y}))
def rep(nm,p):
d=metrics(p); print(f'{nm:26s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
for c in cols: rep(c,o[c].values)
print('--- blends ---')
rep('all6', o[cols].mean(1).values)
rep('et4', o[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang']].mean(1).values)
rep('et4+hgb2', (o[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang']].mean(1)*0.7 + o[['ts_hgb_f2','ts_hgb_phys']].mean(1)*0.3).values)
rep('et-f2+hgb', (o[['ts_et_f2','ts_et_f2b']].mean(1)*0.6 + o[['ts_hgb_f2','ts_hgb_phys']].mean(1)*0.4).values)
rep('4way', o[['ts_et_f2','ts_et_phys','ts_hgb_f2','ts_hgb_phys']].mean(1).values)
"ts_et_f2 rmse=2.124 mae=1.603 p90=2.652 p95=3.831 max=4.652 ts_et_f2b rmse=2.111 mae=1.585 p90=2.712 p95=3.869 max=4.862 ts_et_phys rmse=2.161 mae=1.631 p90=2.861 p95=3.851 max=4.694 ts_et_noang rmse=2.227 mae=1.705 p90=3.025 p95=3.493 max=5.620 ts_hgb_f2 rmse=2.194 mae=1.611 p90=2.912 p95=3.335 max=6.781 ts_hgb_phys rmse=2.248 mae=1.662 p90=3.194 p95=3.465 max=6.869 --- blends --- all6 rmse=2.081 mae=1.561 p90=2.658 p95=3.600 max=4.947 et4 rmse=2.131 mae=1.610 p90=2.784 p95=3.807 max=4.930 et4+hgb2 rmse=2.083 mae=1.563 p90=2.670 p95=3.620 max=4.868 et-f2+hgb rmse=2.073 mae=1.544 p90=2.624 p95=3.583 max=5.341 4way rmse=2.088 mae=1.553 p90=2.668 p95=3.520 max=5.607
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o2=pd.read_csv('oof2.csv')
y=o2.y.values; cond=o2['cond'].values
# candidate blends (from two-stage models)
cand={
'all6': o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values,
'et4hgb2': (o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang']].mean(1)*0.7+o2[['ts_hgb_f2','ts_hgb_phys']].mean(1)*0.3).values,
'etf2_hgb': (o2[['ts_et_f2','ts_et_f2b']].mean(1)*0.6+o2[['ts_hgb_f2','ts_hgb_phys']].mean(1)*0.4).values,
'et_f2_only': o2[['ts_et_f2','ts_et_f2b']].mean(1).values,
}
T=dict(rmse=2.25,mae=1.55,p90=2.35,p95=4.75,max=5.60)
for nm,pred in cand.items():
o2['err']=pred-y; g=o2.groupby('cond'); cr=g.err.apply(lambda e:np.sqrt(np.mean(e**2)))
rows_by={c:s.err.values for c,s in g}; conds=list(cr.index)
rng=np.random.RandomState(0)
for size in [20,25]:
cnt={k:0 for k in T}; pa=0; N=3000
for _ in range(N):
S=rng.choice(conds,size=size,replace=False)
errs=np.concatenate([rows_by[c] for c in S]); crr=np.array([cr[c] for c in S])
m=dict(rmse=np.sqrt(np.mean(errs**2)),mae=np.mean(np.abs(errs)),p90=np.percentile(crr,90),p95=np.percentile(crr,95),max=crr.max())
ok=True
for k in T:
if m[k]<=T[k]: cnt[k]+=1
else: ok=False
pa+=ok
print(f'{nm:12s} sz={size}: '+' '.join(f'{k}={cnt[k]/N:.2f}' for k in T)+f' ALL={pa/N:.3f}')
"all6 sz=20: rmse=0.81 mae=0.48 p90=0.08 p95=0.94 max=1.00 ALL=0.081 all6 sz=25: rmse=0.83 mae=0.47 p90=0.04 p95=1.00 max=1.00 ALL=0.035 et4hgb2 sz=20: rmse=0.80 mae=0.48 p90=0.08 p95=0.94 max=1.00 ALL=0.074 et4hgb2 sz=25: rmse=0.83 mae=0.46 p90=0.03 p95=1.00 max=1.00 ALL=0.033 etf2_hgb sz=20: rmse=0.80 mae=0.54 p90=0.09 p95=0.94 max=1.00 ALL=0.089 etf2_hgb sz=25: rmse=0.82 mae=0.52 p90=0.04 p95=1.00 max=1.00 ALL=0.043 et_f2_only sz=20: rmse=0.77 mae=0.40 p90=0.04 p95=1.00 max=1.00 ALL=0.037 et_f2_only sz=25: rmse=0.79 mae=0.36 p90=0.01 p95=1.00 max=1.00 ALL=0.015
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o1=pd.read_csv('oof.csv'); o2=pd.read_csv('oof2.csv')
assert (o1['cond'].values==o2['cond'].values).all() and (o1.y.values==o2.y.values).all()
y=o2.y.values; cond=o2['cond'].values
pred=o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values
df=pd.DataFrame({'cond':cond,'err':pred-y,
'a':pd.Series(cond).str.split('_').str[0].astype(float).values,
'c':pd.Series(cond).str.split('_').str[1].astype(float).values,
'v':pd.Series(cond).str.split('_').str[2].astype(float).values})
cr=df.groupby('cond').apply(lambda g: pd.Series({'rmse':np.sqrt(np.mean(g.err**2)),'bias':g.err.mean(),'n':len(g),'a':g.a.iloc[0],'c':g.c.iloc[0],'v':g.v.iloc[0]}))
cr=cr.sort_values('rmse',ascending=False)
pd.set_option('display.width',200)
print('worst 14 (two-stage all6 blend):'); print(cr.head(14).to_string())
print('count > 2.35:', (cr.rmse>2.35).sum(), ' >2.6:', (cr.rmse>2.6).sum())
"worst 14 (two-stage all6 blend):
rmse bias n a c v
cond
22.20_0.0254_39.6 4.946832 -4.012662 15.0 22.2 0.0254 39.6
19.70_0.0508_71.3 4.865435 4.305201 14.0 19.7 0.0508 71.3
0.00_0.0254_71.3 4.127861 0.509473 10.0 0.0 0.0254 71.3
12.60_0.1524_39.6 3.738128 2.588093 16.0 12.6 0.1524 39.6
7.30_0.2286_71.3 3.617580 -2.043104 16.0 7.3 0.2286 71.3
7.30_0.1524_71.3 3.265622 1.986576 16.0 7.3 0.1524 71.3
8.90_0.1016_71.3 2.961905 -0.195427 16.0 8.9 0.1016 71.3
12.30_0.1016_71.3 2.807878 -1.703535 16.0 12.3 0.1016 71.3
12.60_0.0254_71.3 2.659144 -1.407744 17.0 12.6 0.0254 71.3
4.00_0.0508_71.3 2.649213 1.826310 10.0 4.0 0.0508 71.3
12.30_0.0508_71.3 2.635034 -2.234566 14.0 12.3 0.0508 71.3
12.30_0.0508_39.6 2.577304 0.129062 14.0 12.3 0.0508 39.6
17.40_0.0254_39.6 2.554970 0.214929 15.0 17.4 0.0254 39.6
12.60_0.1524_71.3 2.554742 1.982845 16.0 12.6 0.1524 71.3
count > 2.35: 18 >2.6: 11
[stderr]
<string>:11: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.cd /app/scratch && cat >> twostage.py << 'EOF'
from sklearn.preprocessing import SplineTransformer
def _sbv(X):
f=X['frequency'].astype(float).values; U=X['free_stream_velocity'].astype(float).values
d=X['suction_side_displacement_thickness'].astype(float).values; c=X['chord_length'].astype(float).values
a=X['attack_angle'].astype(float).values
return (np.log10(f*d/U), U/343.0, np.log10(c), np.log10(U), np.log10(d), np.log10(U*c/1.5e-5), a)
class BackboneTree(BaseEstimator, RegressorMixin):
"""Stage1: smooth extrapolating backbone (level + Strouhal spline + Mach/Re mod). Stage2: ET residual."""
def __init__(self, tree_feat=feats2.with_angle, alpha=20.0, n_knots=8, mf=0.7, ne=500):
self.tree_feat=tree_feat; self.alpha=alpha; self.n_knots=n_knots; self.mf=mf; self.ne=ne
def _design(self, X, fit=False):
logSt,M,logc,logU,logd,logRe,a=_sbv(X); St=logSt.reshape(-1,1)
if fit:
self.spl_=SplineTransformer(n_knots=self.n_knots,degree=3,extrapolation='linear')
S=self.spl_.fit_transform(St)
else: S=self.spl_.transform(St)
level=np.column_stack([logc,logU,logd,logRe,M])
blocks=[S, S*M.reshape(-1,1), S*logU.reshape(-1,1), level]
return np.hstack(blocks)
def fit(self, X, y):
D=self._design(X,True); self.sc_=StandardScaler().fit(D)
self.lin_=Ridge(alpha=self.alpha).fit(self.sc_.transform(D), y)
r=y-self.lin_.predict(self.sc_.transform(D))
self.tree_=make_pipeline(FunctionTransformer(self.tree_feat),
ExtraTreesRegressor(n_estimators=self.ne,max_features=self.mf,n_jobs=-1,random_state=0))
self.tree_.fit(X,r); return self
def predict(self, X):
D=self._design(X,False); return self.lin_.predict(self.sc_.transform(D))+self.tree_.predict(X)
EOF
timeout 220 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings, fastcv, feats2
warnings.filterwarnings('ignore')
from twostage import BackboneTree
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
for nk in [6,8]:
for al in [10.0,30.0,100.0]:
d=fastcv.fast_eval(lambda nk=nk,al=al: BackboneTree(feats2.with_angle,alpha=al,n_knots=nk), both, groups, n_splits=15)
print(f'Backbone nk={nk} a={al:5.1f} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"Backbone nk=6 a= 10.0 rmse=2.108 mae=1.563 p90=3.150 p95=3.545 max=4.598 Backbone nk=6 a= 30.0 rmse=2.088 mae=1.553 p90=3.031 p95=3.625 max=4.475 Backbone nk=6 a=100.0 rmse=2.075 mae=1.543 p90=3.021 p95=3.609 max=4.488 Backbone nk=8 a= 10.0 rmse=2.089 mae=1.547 p90=3.095 p95=3.432 max=4.395 Backbone nk=8 a= 30.0 rmse=2.071 mae=1.539 p90=3.074 p95=3.595 max=4.368 Backbone nk=8 a=100.0 rmse=2.061 mae=1.536 p90=3.040 p95=3.565 max=4.415
cd /app/scratch && cat > gen_oof3.py << 'EOF'
import numpy as np, pandas as pd, evallib as E, feats, feats2, warnings
warnings.filterwarnings('ignore')
from sklearn.model_selection import GroupKFold
from twostage import BackboneTree
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
uniq=np.unique(groups); rng=np.random.RandomState(1); perm=rng.permutation(len(uniq))
gmap={u:i for i,u in enumerate(uniq[perm])}; gidx=np.array([gmap[g] for g in groups])
gk=GroupKFold(n_splits=15)
bases={
'bb_f2': lambda: BackboneTree(feats2.with_angle, alpha=100, n_knots=8, mf=0.7),
'bb_f2b': lambda: BackboneTree(feats2.with_angle, alpha=30, n_knots=8, mf=1.0),
'bb_phys': lambda: BackboneTree(feats.phys_features, alpha=100, n_knots=8, mf=0.7),
}
oof={k:np.zeros(len(both)) for k in bases}
for tri,tei in gk.split(both, both[E.TARGET], gidx):
for k,mkf in bases.items():
m=mkf(); m.fit(both.iloc[tri][E.FEATURES], both.iloc[tri][E.TARGET].values)
oof[k][tei]=m.predict(both.iloc[tei][E.FEATURES])
out=pd.DataFrame(oof); out.to_csv('oof3.csv',index=False); print('done', out.shape)
EOF
nohup timeout 290 python3 gen_oof3.py > gen_oof3.log 2>&1 &
echo started; sleep 150; cat gen_oof3.log; ls -la oof3.csv 2>/dev/nullstarted done (1157, 3) -rw-r--r-- 1 h2tagent h2tagent 65184 Jun 21 06:19 oof3.csv
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
M=pd.concat([o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']], o3], axis=1)
cols=list(M.columns)
def metrics(p): return E.metrics_from(pd.DataFrame({'cond':cond,'err':p-y}))
def rep(nm,p):
d=metrics(p); print(f'{nm:30s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
# random search weights to minimize p90 s.t. constraints
rng=np.random.RandomState(0); X=M.values
best=None
for it in range(60000):
w=rng.dirichlet(np.ones(len(cols)))
p=X@w; o=pd.DataFrame({'cond':cond,'err':p-y}); g=o.groupby('cond').err
cr=g.apply(lambda e:np.sqrt(np.mean(e**2)))
rmse=np.sqrt(np.mean((p-y)**2)); mae=np.mean(np.abs(p-y))
p90=np.percentile(cr,90); p95=np.percentile(cr,95); mx=cr.max()
if rmse<=2.15 and mae<=1.55 and p95<=4.0 and mx<=5.0:
score=p90
if best is None or score<best[0]: best=(score,w.copy(),rmse,mae,p90,p95,mx)
print('best p90 blend:', {c:round(wv,3) for c,wv in zip(cols,best[1]) if wv>0.02})
print(f' rmse={best[2]:.3f} mae={best[3]:.3f} p90={best[4]:.3f} p95={best[5]:.3f} max={best[6]:.3f}')
rep('equal all9', M.mean(1).values)
rep('ts6 + bb3 (0.6/0.4)', (o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1)*0.6 + o3.mean(1)*0.4).values)
"[stderr] Shell command timed out. Tool interaction exceeded 300 seconds.
restart: true
<system>tool has been restarted.</system>
cd /app/scratch && timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
M=pd.concat([o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']], o3], axis=1)
cols=list(M.columns); X=M.values
# precompute condition index groups
uc, inv = np.unique(cond, return_inverse=True)
nC=len(uc); counts=np.bincount(inv)
def fast_metrics(p):
e=p-y; rmse=np.sqrt(np.mean(e**2)); mae=np.mean(np.abs(e))
sse=np.bincount(inv, weights=e*e); cr=np.sqrt(sse/counts)
return rmse,mae,np.percentile(cr,90),np.percentile(cr,95),cr.max()
def rep(nm,p):
r=fast_metrics(p); print(f'{nm:24s} rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f}')
rng=np.random.RandomState(0); best=None
for it in range(8000):
w=rng.dirichlet(np.ones(len(cols))); r=fast_metrics(X@w)
if r[0]<=2.15 and r[1]<=1.55 and r[3]<=4.2 and r[4]<=5.1:
if best is None or r[2]<best[0]: best=(r[2],w.copy(),r)
print('best p90 blend weights:', {c:round(wv,2) for c,wv in zip(cols,best[1]) if wv>0.03})
print(f' -> rmse={best[2][0]:.3f} mae={best[2][1]:.3f} p90={best[2][2]:.3f} p95={best[2][3]:.3f} max={best[2][4]:.3f}')
rep('equal all9', M.mean(1).values)
rep('ts6*.6+bb3*.4', o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values*0.6 + o3.mean(1).values*0.4)
rep('ts6*.7+bb3*.3', o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values*0.7 + o3.mean(1).values*0.3)
"best p90 blend weights: {'ts_et_f2b': 0.47, 'ts_et_phys': 0.1, 'ts_hgb_f2': 0.05, 'ts_hgb_phys': 0.1, 'bb_f2': 0.23}
-> rmse=2.037 mae=1.517 p90=2.577 p95=3.704 max=4.593
equal all9 rmse=2.022 mae=1.506 p90=2.630 p95=3.611 max=4.565
ts6*.6+bb3*.4 rmse=2.014 mae=1.498 p90=2.627 p95=3.617 max=4.507
ts6*.7+bb3*.3 rmse=2.027 mae=1.511 p90=2.631 p95=3.610 max=4.594cd /app/scratch && timeout 120 python3 -c "
import numpy as np, pandas as pd
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
ts=o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values
bb=o3.mean(1).values
pred=0.6*ts+0.4*bb
uc,inv=np.unique(cond,return_inverse=True); counts=np.bincount(inv)
e=pred-y; sse=np.bincount(inv,weights=e*e); cr=np.sqrt(sse/counts)
rows_by=[np.where(inv==i)[0] for i in range(len(uc))]
T=dict(rmse=2.25,mae=1.55,p90=2.35,p95=4.75,max=5.60)
rng=np.random.RandomState(1)
for size in [15,20,25]:
cnt={k:0 for k in T}; pa=0; N=5000
for _ in range(N):
S=rng.choice(len(uc),size=size,replace=False)
idx=np.concatenate([rows_by[i] for i in S]); ee=pred[idx]-y[idx]; crr=cr[S]
m=dict(rmse=np.sqrt(np.mean(ee**2)),mae=np.mean(np.abs(ee)),p90=np.percentile(crr,90),p95=np.percentile(crr,95),max=crr.max())
ok=True
for k in T:
if m[k]<=T[k]: cnt[k]+=1
else: ok=False
pa+=ok
print(f'sz={size}: '+' '.join(f'{k}={cnt[k]/N:.2f}' for k in T)+f' ALL={pa/N:.3f}')
"sz=15: rmse=0.86 mae=0.64 p90=0.16 p95=1.00 max=1.00 ALL=0.163 sz=20: rmse=0.88 mae=0.65 p90=0.14 p95=1.00 max=1.00 ALL=0.138 sz=25: rmse=0.93 mae=0.68 p90=0.05 p95=1.00 max=1.00 ALL=0.054
cd /app/scratch && cat > blend.py << 'EOF'
import numpy as np, pandas as pd
from sklearn.base import BaseEstimator, RegressorMixin
import feats, feats2
from twostage import TwoStageGen, BackboneTree
def build_models():
return {
'ts_et_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=0.7), 0.6/6),
'ts_et_f2b': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=1.0), 0.6/6),
'ts_et_phys': (TwoStageGen(feats.phys_features, alpha=30, kind='et', mf=0.7), 0.6/6),
'ts_et_noang':(TwoStageGen(feats2.no_angle, alpha=30, kind='et', mf=0.7), 0.6/6),
'ts_hgb_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='hgb'), 0.6/6),
'ts_hgb_phys':(TwoStageGen(feats.phys_features, alpha=30, kind='hgb'), 0.6/6),
'bb_f2': (BackboneTree(feats2.with_angle, alpha=100, n_knots=8, mf=0.7), 0.4/3),
'bb_f2b': (BackboneTree(feats2.with_angle, alpha=30, n_knots=8, mf=1.0), 0.4/3),
'bb_phys': (BackboneTree(feats.phys_features, alpha=100, n_knots=8, mf=0.7), 0.4/3),
}
class Blend(BaseEstimator, RegressorMixin):
def fit(self, X, y):
self.models_=build_models()
for k,(m,w) in self.models_.items(): m.fit(X,y)
return self
def predict(self, X):
tot=0.0
for k,(m,w) in self.models_.items(): tot=tot+w*m.predict(X)
return tot
EOF
timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
from blend import Blend
tr,va=E.load()
m=Blend().fit(tr[E.FEATURES], tr[E.TARGET].values)
pred=m.predict(va[E.FEATURES])
d=E.metrics_from(pd.DataFrame({'cond':E.condition_ids(va).values,'err':pred-va[E.TARGET].values}))
print('TRAIN->VAL holdout (15 disjoint conditions):')
print(f\" rmse={d['rmse']:.3f} mae={d['mae']:.3f} p90={d['p90_cond']:.3f} p95={d['p95_cond']:.3f} max={d['max_cond']:.3f}\")
print('Targets: rmse<=2.25 mae<=1.55 p90<=2.35 p95<=4.75 max<=5.60')
"TRAIN->VAL holdout (15 disjoint conditions): rmse=2.142 mae=1.670 p90=2.794 p95=3.296 max=4.004 Targets: rmse<=2.25 mae<=1.55 p90<=2.35 p95<=4.75 max<=5.60
cd /app/scratch && timeout 200 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
from scipy.interpolate import UnivariateSpline
from blend import Blend
tr,va=E.load()
m=Blend().fit(tr[E.FEATURES], tr[E.TARGET].values)
va=va.copy(); va['pred']=m.predict(va[E.FEATURES]); va['cond']=E.condition_ids(va)
va['logSt']=np.log10(va.frequency*va.suction_side_displacement_thickness/va.free_stream_velocity)
def metrics(col):
d=E.metrics_from(pd.DataFrame({'cond':va['cond'].values,'err':va[col].values-va[E.TARGET].values}))
return d
def rep(nm,col):
d=metrics(col); print(f'{nm:20s} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
rep('blend raw','pred')
for sfac in [0.3,0.6,1.0]:
sm=va.pred.values.copy()
for c,g in va.groupby('cond'):
if len(g)<6: continue
gg=g.sort_values('logSt'); x=gg.logSt.values; p=gg.pred.values
try:
sp=UnivariateSpline(x,p,k=3,s=len(x)*sfac); sm[gg.index]=sp(x)
except Exception: pass
va['sm']=sm; rep(f'smooth s={sfac}','sm')
"blend raw rmse=2.142 mae=1.670 p90=2.794 p95=3.296 max=4.004 smooth s=0.3 rmse=2.179 mae=1.704 p90=2.919 p95=3.273 max=3.889 smooth s=0.6 rmse=2.223 mae=1.727 p90=3.038 p95=3.345 max=3.889 smooth s=1.0 rmse=2.262 mae=1.751 p90=3.170 p95=3.417 max=3.889
cd /app/scratch && timeout 250 python3 -c "
import time, numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
from blend import Blend
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True)
t0=time.time()
m=Blend().fit(both[E.FEATURES], both[E.TARGET].values)
t1=time.time()
p=m.predict(va[E.FEATURES])
t2=time.time()
print(f'fit {t1-t0:.1f}s predict {t2-t1:.1f}s pred range [{p.min():.1f},{p.max():.1f}] finite={np.isfinite(p).all()}')
"fit 23.2s predict 1.3s pred range [109.7,138.6] finite=True
cd /app/scratch && timeout 150 python3 -c "
import numpy as np, pandas as pd
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
M=pd.concat([o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']], o3], axis=1)
cols=list(M.columns); X=M.values
uc,inv=np.unique(cond,return_inverse=True); counts=np.bincount(inv)
def fm(p):
e=p-y; sse=np.bincount(inv,weights=e*e); cr=np.sqrt(sse/counts)
return np.sqrt(np.mean(e**2)),np.mean(np.abs(e)),np.percentile(cr,90),np.percentile(cr,95),cr.max()
rng=np.random.RandomState(3); best=None
for it in range(20000):
w=rng.dirichlet(np.ones(len(cols))*0.5)
r=fm(X@w)
if r[0]<=2.20 and r[3]<=4.3 and r[4]<=5.3:
obj=max(r[2]/2.35, r[1]/1.55) # binding metrics normalized
if best is None or obj<best[0]: best=(obj,w.copy(),r)
w=best[1]
print('weights:', {c:round(v,3) for c,v in zip(cols,w) if v>0.02})
r=best[1]; rr=best[2]
print(f'obj={best[0]:.3f} rmse={rr[0]:.3f} mae={rr[1]:.3f} p90={rr[2]:.3f} p95={rr[3]:.3f} max={rr[4]:.3f}')
np.save('best_w.npy', w)
import json; json.dump(cols, open('cols.json','w'))
"weights: {'ts_et_f2': 0.23, 'ts_et_f2b': 0.375, 'ts_et_phys': 0.138, 'ts_hgb_f2': 0.094, 'ts_hgb_phys': 0.042, 'bb_f2': 0.036, 'bb_f2b': 0.075}
obj=1.096 rmse=2.065 mae=1.544 p90=2.576 p95=3.727 max=4.660command: str_replace
path: /app/scratch/blend.py
old_str: def build_models():
return {
'ts_et_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=0.7), 0.6/6),
'ts_et_f2b': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=1.0), 0.6/6),
'ts_et_phys': (TwoStageGen(feats.phys_features, alpha=30, kind='et', mf=0.7), 0.6/6),
'ts_et_noang':(TwoStageGen(feats2.no_angle, alpha=30, kind='et', mf=0.7), 0.6/6),
'ts_hgb_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='hgb'), 0.6/6),
'ts_hgb_phys':(TwoStageGen(feats.phys_features, alpha=30, kind='hgb'), 0.6/6),
'bb_f2': (BackboneTree(feats2.with_angle, alpha=100, n_knots=8, mf=0.7), 0.4/3),
'bb_f2b': (BackboneTree(feats2.with_angle, alpha=30, n_knots=8, mf=1.0), 0.4/3),
'bb_phys': (BackboneTree(feats.phys_features, alpha=100, n_knots=8, mf=0.7), 0.4/3),
}
new_str: def build_models():
return {
'ts_et_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=0.7), 0.22),
'ts_et_f2b': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=1.0), 0.30),
'ts_et_phys': (TwoStageGen(feats.phys_features, alpha=30, kind='et', mf=0.7), 0.14),
'ts_et_noang':(TwoStageGen(feats2.no_angle, alpha=30, kind='et', mf=0.7), 0.06),
'ts_hgb_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='hgb'), 0.10),
'ts_hgb_phys':(TwoStageGen(feats.phys_features, alpha=30, kind='hgb'), 0.05),
'bb_f2': (BackboneTree(feats2.with_angle, alpha=100, n_knots=8, mf=0.7), 0.08),
'bb_f2b': (BackboneTree(feats2.with_angle, alpha=30, n_knots=8, mf=1.0), 0.05),
}The file /app/scratch/blend.py has been edited successfully.
cd /app/scratch && timeout 120 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
from blend import Blend
tr,va=E.load()
w=sum(wt for _,(_,wt) in Blend().__class__.__dict__.items() if False) # noop
m=Blend().fit(tr[E.FEATURES], tr[E.TARGET].values)
pred=m.predict(va[E.FEATURES])
d=E.metrics_from(pd.DataFrame({'cond':E.condition_ids(va).values,'err':pred-va[E.TARGET].values}))
print('TRAIN->VAL (tuned weights):')
print(f\" rmse={d['rmse']:.3f} mae={d['mae']:.3f} p90={d['p90_cond']:.3f} p95={d['p95_cond']:.3f} max={d['max_cond']:.3f}\")
import blend
print('weight sum =', sum(wt for (_,wt) in blend.build_models().values()))
"[stderr] Traceback (most recent call last): File "<string>", line 6, in <module> File "<string>", line 6, in <genexpr> ValueError: too many values to unpack (expected 2)
cd /app/scratch && timeout 120 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings
warnings.filterwarnings('ignore')
import blend
from blend import Blend
tr,va=E.load()
m=Blend().fit(tr[E.FEATURES], tr[E.TARGET].values)
pred=m.predict(va[E.FEATURES])
d=E.metrics_from(pd.DataFrame({'cond':E.condition_ids(va).values,'err':pred-va[E.TARGET].values}))
print('TRAIN->VAL (tuned weights):')
print(f\" rmse={d['rmse']:.3f} mae={d['mae']:.3f} p90={d['p90_cond']:.3f} p95={d['p95_cond']:.3f} max={d['max_cond']:.3f}\")
print('weight sum =', round(sum(wt for (_,wt) in blend.build_models().values()),4))
"TRAIN->VAL (tuned weights): rmse=2.179 mae=1.711 p90=2.785 p95=3.336 max=4.123 weight sum = 1.0
command: str_replace
path: /app/scratch/blend.py
old_str: def build_models():
return {
'ts_et_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=0.7), 0.22),
'ts_et_f2b': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=1.0), 0.30),
'ts_et_phys': (TwoStageGen(feats.phys_features, alpha=30, kind='et', mf=0.7), 0.14),
'ts_et_noang':(TwoStageGen(feats2.no_angle, alpha=30, kind='et', mf=0.7), 0.06),
'ts_hgb_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='hgb'), 0.10),
'ts_hgb_phys':(TwoStageGen(feats.phys_features, alpha=30, kind='hgb'), 0.05),
'bb_f2': (BackboneTree(feats2.with_angle, alpha=100, n_knots=8, mf=0.7), 0.08),
'bb_f2b': (BackboneTree(feats2.with_angle, alpha=30, n_knots=8, mf=1.0), 0.05),
}
new_str: def build_models():
ts = 0.6 / 6.0
bb = 0.4 / 3.0
return {
'ts_et_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=0.7), ts),
'ts_et_f2b': (TwoStageGen(feats2.with_angle, alpha=30, kind='et', mf=1.0), ts),
'ts_et_phys': (TwoStageGen(feats.phys_features, alpha=30, kind='et', mf=0.7), ts),
'ts_et_noang':(TwoStageGen(feats2.no_angle, alpha=30, kind='et', mf=0.7), ts),
'ts_hgb_f2': (TwoStageGen(feats2.with_angle, alpha=30, kind='hgb'), ts),
'ts_hgb_phys':(TwoStageGen(feats.phys_features, alpha=30, kind='hgb'), ts),
'bb_f2': (BackboneTree(feats2.with_angle, alpha=100, n_knots=8, mf=0.7), bb),
'bb_f2b': (BackboneTree(feats2.with_angle, alpha=30, n_knots=8, mf=1.0), bb),
'bb_phys': (BackboneTree(feats.phys_features, alpha=100, n_knots=8, mf=0.7), bb),
}The file /app/scratch/blend.py has been edited successfully.
command: create
path: /app/train_and_predict.py
file_text: #!/usr/bin/env python3
"""Airfoil Self-Noise surrogate.
Trains a physics-informed blended regressor on the labeled public data
(train.csv + validation.csv) and writes predictions for the grader-staged
feature-only rows at /app/data/test_features.csv.
Design notes
------------
The split holds out whole aerodynamic condition groups
``(attack_angle, chord_length, free_stream_velocity)``, so the model must
generalize to unseen physical regimes. Key ideas that make this work:
* Physics features derived from the Brooks-Pope-Marcolini (BPM) trailing-edge
noise model: the Strouhal number ``St = f * delta* / U`` collapses the
spectral shape, while log scalings of velocity / chord / thickness capture
the overall level.
* A two-stage "safe" structure: a linear model on *frequency-independent*
log-level features predicts the overall condition level (this extrapolates
gracefully to grid-boundary conditions such as the highest velocity),
and tree ensembles fit the remaining smooth spectral residual.
* A smooth spline "backbone" variant that extrapolates the Strouhal spectral
curve with linear tails, taming the single worst held-out condition.
* A diversified blend of these models to reduce per-condition tail errors
(the p90 / p95 / max condition-RMSE metrics).
"""
from pathlib import Path
import warnings
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, RegressorMixin
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler, SplineTransformer
from sklearn.linear_model import Ridge
from sklearn.ensemble import ExtraTreesRegressor, HistGradientBoostingRegressor
warnings.filterwarnings("ignore")
DATA_DIR = Path("/app/data")
OUTPUT_DIR = Path("/app/output")
TARGET = "scaled_sound_pressure_level"
PREDICTION = "predicted_scaled_sound_pressure_level"
FEATURES = [
"frequency",
"attack_angle",
"chord_length",
"free_stream_velocity",
"suction_side_displacement_thickness",
]
C_SOUND = 343.0 # speed of sound (m/s)
NU = 1.5e-5 # kinematic viscosity of air (m^2/s)
# Safety clip: training targets span ~105-140 dB; guard against any wild
# extrapolation while never touching physically plausible predictions.
CLIP_LO, CLIP_HI = 95.0, 148.0
# --------------------------------------------------------------------------- #
# Feature engineering #
# --------------------------------------------------------------------------- #
def _cols(X):
f = np.maximum(X["frequency"].astype(float).values, 1.0)
a = X["attack_angle"].astype(float).values
c = np.maximum(X["chord_length"].astype(float).values, 1e-6)
U = np.maximum(X["free_stream_velocity"].astype(float).values, 1e-3)
d = np.maximum(X["suction_side_displacement_thickness"].astype(float).values, 1e-9)
return f, a, c, U, d
def phys_features(X):
"""Rich physics feature set including raw attack angle."""
f, a, c, U, d = _cols(X)
logf, logd, logc, logU = np.log10(f), np.log10(d), np.log10(c), np.log10(U)
M = U / C_SOUND
logSt = np.log10(f * d / U)
logRe = np.log10(U * c / NU)
logRed = np.log10(U * d / NU)
o = pd.DataFrame(index=X.index)
o["logf"] = logf; o["logc"] = logc; o["logU"] = logU; o["logd"] = logd; o["M"] = M
o["logSt"] = logSt; o["logSt2"] = logSt ** 2; o["logSt3"] = logSt ** 3
o["logRe"] = logRe; o["logRed"] = logRed; o["logdc"] = np.log10(d / c)
o["a"] = a; o["a2"] = a ** 2; o["a3"] = a ** 3
o["logSt_logM"] = logSt * np.log10(M); o["logSt_logc"] = logSt * logc
o["logSt_logU"] = logSt * logU; o["logSt_logd"] = logSt * logd
o["logSt_logRe"] = logSt * logRe; o["logSt_a"] = logSt * a
o["a_logU"] = a * logU; o["a_logc"] = a * logc; o["a_logd"] = a * logd
o["logc_logU"] = logc * logU; o["logd_logU"] = logd * logU; o["logc_logd"] = logc * logd
return o
d
…[truncated]Existing file was successfully overwritten at /app/train_and_predict.py
cd /app && rm -f output/predictions.csv && time python3 train_and_predict.py && echo "---HEAD---" && head -5 output/predictions.csv && echo "---VALIDATE---" && python3 -c "
import pandas as pd, numpy as np
pred=pd.read_csv('output/predictions.csv'); test=pd.read_csv('data/test_features.csv')
assert list(pred.columns)==['sample_id','predicted_scaled_sound_pressure_level'], pred.columns.tolist()
assert len(pred)==len(test), (len(pred),len(test))
assert set(pred.sample_id.astype(str))==set(test.sample_id.astype(str)), 'id mismatch'
assert np.isfinite(pred.predicted_scaled_sound_pressure_level).all()
assert pred.predicted_scaled_sound_pressure_level.nunique()>5, 'not constant'
print('rows', len(pred), 'unique preds', pred.predicted_scaled_sound_pressure_level.nunique())
print('OK: columns, alignment, finiteness, non-constant all pass')
"Wrote 40 predictions to /app/output/predictions.csv Prediction range: [114.758, 138.134] ---HEAD--- sample_id,predicted_scaled_sound_pressure_level af_val_0000,115.92521123791185 af_val_0001,124.22470327787353 af_val_0002,133.61888865861053 af_val_0003,123.58848655998126 ---VALIDATE--- rows 40 unique preds 40 OK: columns, alignment, finiteness, non-constant all pass [stderr] real 0m26.870s user 0m33.630s sys 0m1.070s
cd /app && timeout 150 python3 -c "
import numpy as np, pandas as pd, warnings, importlib.util
warnings.filterwarnings('ignore')
spec=importlib.util.spec_from_file_location('tap','/app/train_and_predict.py')
tap=importlib.util.module_from_spec(spec); spec.loader.exec_module(tap)
tr=pd.read_csv('data/train.csv'); va=pd.read_csv('data/validation.csv')
# nominal condition ids for val (for per-condition metrics)
CH=[0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]; VE=[31.7,39.6,55.5,71.3]
AN=np.array([0.0,1.5,3.0,4.0,5.3,7.3,8.9,9.9,12.3,12.6,15.4,15.6,17.4,19.7,22.2])
def nr(v,a): a=np.array(a); return a[np.argmin(np.abs(a[None,:]-np.array(v)[:,None]),axis=1)]
cond=[f'{x:.2f}_{y:.4f}_{z:.1f}' for x,y,z in zip(nr(va.attack_angle,AN),nr(va.chord_length,CH),nr(va.free_stream_velocity,VE))]
X=tr[tap.FEATURES]; y=tr[tap.TARGET].astype(float).values
preds=np.zeros(len(va)); ws=0.0
for m,w in tap.build_blend():
m.fit(X,y); preds+=w*m.predict(va[tap.FEATURES]); ws+=w
preds=np.clip(preds/ws, tap.CLIP_LO, tap.CLIP_HI)
err=preds-va[tap.TARGET].values
d=pd.DataFrame({'cond':cond,'err':err})
cr=d.groupby('cond').err.apply(lambda e:np.sqrt(np.mean(e**2)))
print('CONSOLIDATED script, TRAIN->VAL (honest, disjoint conditions):')
print(f' rmse={np.sqrt(np.mean(err**2)):.3f} mae={np.mean(np.abs(err)):.3f} p90={np.percentile(cr,90):.3f} p95={np.percentile(cr,95):.3f} max={cr.max():.3f}')
print(' targets: rmse<=2.25 mae<=1.55 p90<=2.35 p95<=4.75 max<=5.60')
"CONSOLIDATED script, TRAIN->VAL (honest, disjoint conditions): rmse=2.141 mae=1.664 p90=2.797 p95=3.309 max=4.035 targets: rmse<=2.25 mae<=1.55 p90<=2.35 p95<=4.75 max<=5.60
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd, evallib as E
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
ts=o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values
bb=o3.mean(1).values
pred=0.6*ts+0.4*bb
df=pd.DataFrame({'cond':cond,'err':pred-y})
df['ang']=pd.Series(cond).str.split('_').str[0].astype(float).values
# count conditions per nominal angle
ang_counts=df.drop_duplicates('cond').groupby('ang').size()
singleton_angles=set(ang_counts[ang_counts<=1].index)
print('singleton angles (in only 1 condition):', sorted(singleton_angles))
def metrics(sub):
g=sub.groupby('cond').err; cr=g.apply(lambda e:np.sqrt(np.mean(e**2)))
return (np.sqrt(np.mean(sub.err**2)),np.mean(np.abs(sub.err)),np.percentile(cr,90),np.percentile(cr,95),cr.max(),len(cr))
for nm,sub in [('ALL 82 conditions',df),('excl singleton-angle conds',df[~df.ang.isin(singleton_angles)])]:
r=metrics(sub)
print(f'{nm:32s} rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f} (nc={r[5]})')
"singleton angles (in only 1 condition): [19.7, 22.2] ALL 82 conditions rmse=2.014 mae=1.498 p90=2.627 p95=3.617 max=4.507 (nc=82) excl singleton-angle conds rmse=1.911 mae=1.441 p90=2.576 p95=3.046 max=4.032 (nc=80)
cd /app/scratch && timeout 120 python3 -c "
import numpy as np, pandas as pd
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
pred=0.6*o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values+0.4*o3.mean(1).values
df=pd.DataFrame({'cond':cond,'err':pred-y}); df['ang']=pd.Series(cond).str.split('_').str[0].astype(float).values
ac=df.drop_duplicates('cond').groupby('ang').size(); singles=set(ac[ac<=1].index)
sub=df[~df.ang.isin(singles)].reset_index(drop=True)
uc,inv=np.unique(sub['cond'].values,return_inverse=True); counts=np.bincount(inv)
e=sub.err.values; cr=np.sqrt(np.bincount(inv,weights=e*e)/counts)
rows_by=[np.where(inv==i)[0] for i in range(len(uc))]
T=dict(rmse=2.25,mae=1.55,p90=2.35,p95=4.75,max=5.60); rng=np.random.RandomState(7)
for size in [15,20,25]:
cnt={k:0 for k in T}; pa=0; N=5000
for _ in range(N):
S=rng.choice(len(uc),size=min(size,len(uc)),replace=False)
idx=np.concatenate([rows_by[i] for i in S]); ee=sub.err.values[idx]; crr=cr[S]
m=dict(rmse=np.sqrt(np.mean(ee**2)),mae=np.mean(np.abs(ee)),p90=np.percentile(crr,90),p95=np.percentile(crr,95),max=crr.max())
ok=True
for k in T:
if m[k]<=T[k]: cnt[k]+=1
else: ok=False
pa+=ok
print(f'sz={size}: '+' '.join(f'{k}={cnt[k]/N:.2f}' for k in T)+f' ALL={pa/N:.3f}')
"sz=15: rmse=0.97 mae=0.80 p90=0.21 p95=1.00 max=1.00 ALL=0.214 sz=20: rmse=0.99 mae=0.83 p90=0.20 p95=1.00 max=1.00 ALL=0.200 sz=25: rmse=1.00 mae=0.88 p90=0.10 p95=1.00 max=1.00 ALL=0.101
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
cols2=['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']
A=np.column_stack([o2[cols2].values, o3.values]) # 9 models
w=np.array([0.1]*6+[0.4/3]*3)
uc,inv=np.unique(cond,return_inverse=True); counts=np.bincount(inv)
def m(pred):
e=pred-y; cr=np.sqrt(np.bincount(inv,weights=e*e)/counts)
return np.sqrt(np.mean(e**2)),np.mean(np.abs(e)),np.percentile(cr,90),np.percentile(cr,95),cr.max()
def rep(nm,pred):
r=m(pred); print(f'{nm:22s} rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f}')
rep('weighted mean', A@w)
rep('plain mean', A.mean(1))
rep('median', np.median(A,1))
# trimmed mean (drop min & max per row)
As=np.sort(A,1); rep('trimmed mean(1)', As[:,1:-1].mean(1))
# mean of the 4 ET-f2/phys two-stage (best individuals) + median
rep('ET-ts mean', o2[['ts_et_f2','ts_et_f2b','ts_et_phys']].mean(1).values)
"weighted mean rmse=2.014 mae=1.498 p90=2.627 p95=3.617 max=4.507 plain mean rmse=2.022 mae=1.506 p90=2.630 p95=3.611 max=4.565 median rmse=2.039 mae=1.523 p90=2.675 p95=3.739 max=4.398 trimmed mean(1) rmse=2.024 mae=1.508 p90=2.657 p95=3.655 max=4.497 ET-ts mean rmse=2.127 mae=1.603 p90=2.710 p95=3.848 max=4.733
cd /app && echo "=== /app contents ===" && ls -la && echo "=== *.py in /app (top level) ===" && ls *.py && echo "=== confirm script imports only std/sklearn ===" && grep -n "^import\|^from" train_and_predict.py | grep -iv "sklearn\|numpy\|pandas\|pathlib\|warnings" || echo "(no non-standard imports)"
=== /app contents === total 36 drwxrws--- 6 h2tagent h2tagent 4096 Jun 21 06:31 . drwxr-xr-x 26 root root 4096 Jun 21 05:25 .. drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 21 06:31 __pycache__ drwxrws--- 2 h2tagent h2tagent 4096 Jun 21 05:20 data drwxrws--- 2 h2tagent h2tagent 4096 Jun 21 06:30 output drwxr-sr-x 3 h2tagent h2tagent 4096 Jun 21 06:29 scratch -rwxrwx--- 1 h2tagent h2tagent 12116 Jun 21 06:30 train_and_predict.py === *.py in /app (top level) === train_and_predict.py === confirm script imports only std/sklearn === (no non-standard imports)
cd /app/scratch && timeout 260 python3 -c "
import numpy as np, pandas as pd, evallib as E, warnings, fastcv, feats, feats2
warnings.filterwarnings('ignore')
from sklearn.base import BaseEstimator, RegressorMixin
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
from sklearn.linear_model import Ridge
from sklearn.neural_network import MLPRegressor
from twostage import level_feats
class TSMLP(BaseEstimator, RegressorMixin):
def __init__(self, feat_fn=feats2.with_angle, alpha=30.0, hidden=(64,64), nn_alpha=1e-2, n=4):
self.feat_fn=feat_fn; self.alpha=alpha; self.hidden=hidden; self.nn_alpha=nn_alpha; self.n=n
def fit(self,X,y):
y=np.asarray(y,float)
self.lin_=make_pipeline(FunctionTransformer(level_feats),StandardScaler(),Ridge(alpha=self.alpha)); self.lin_.fit(X,y)
r=y-self.lin_.predict(X)
self.ms_=[]
for s in range(self.n):
m=make_pipeline(FunctionTransformer(self.feat_fn),StandardScaler(),
MLPRegressor(hidden_layer_sizes=self.hidden,alpha=self.nn_alpha,activation='tanh',solver='lbfgs',max_iter=1200,random_state=s))
m.fit(X,r); self.ms_.append(m)
return self
def predict(self,X):
return self.lin_.predict(X)+np.mean([m.predict(X) for m in self.ms_],axis=0)
tr,va=E.load(); both=pd.concat([tr,va],ignore_index=True).reset_index(drop=True)
both['cond']=E.condition_ids(both); groups=both['cond'].values
for hid in [(48,48),(64,64)]:
d=fastcv.fast_eval(lambda hid=hid: TSMLP(feats2.with_angle,hidden=hid,nn_alpha=1e-2,n=4), both, groups, n_splits=12)
print(f'TS-MLP {hid} rmse={d[\"rmse\"]:.3f} mae={d[\"mae\"]:.3f} p90={d[\"p90_cond\"]:.3f} p95={d[\"p95_cond\"]:.3f} max={d[\"max_cond\"]:.3f}')
"TS-MLP (48, 48) rmse=2.671 mae=1.931 p90=3.733 p95=4.769 max=7.539
cd /app && rm -rf __pycache__ && rm -f output/predictions.csv && echo "=== FINAL RUN (as grader invokes) ===" && python3 train_and_predict.py && echo && echo "=== OUTPUT CHECK ===" && python3 -c "
import pandas as pd, numpy as np
p=pd.read_csv('output/predictions.csv'); t=pd.read_csv('data/test_features.csv')
assert list(p.columns)==['sample_id','predicted_scaled_sound_pressure_level']
assert len(p)==len(t) and set(p.sample_id.astype(str))==set(t.sample_id.astype(str))
v=p['predicted_scaled_sound_pressure_level']
assert np.isfinite(v).all() and v.nunique()>5
assert v.round(6).duplicated().mean()<0.5 # not implausibly exact/constant
print('rows:',len(p),'| range: [%.2f, %.2f]'%(v.min(),v.max()),'| unique:',v.nunique())
print('ALL OUTPUT CHECKS PASSED')
"=== FINAL RUN (as grader invokes) === Wrote 40 predictions to /app/output/predictions.csv Prediction range: [114.758, 138.134] === OUTPUT CHECK === rows: 40 | range: [114.76, 138.13] | unique: 40 ALL OUTPUT CHECKS PASSED
cd /app/scratch && python3 -c "
import numpy as np, pandas as pd
o2=pd.read_csv('oof2.csv'); o3=pd.read_csv('oof3.csv')
y=o2.y.values; cond=o2['cond'].values
ts=o2[['ts_et_f2','ts_et_f2b','ts_et_phys','ts_et_noang','ts_hgb_f2','ts_hgb_phys']].mean(1).values
bb=o3.mean(1).values
uc,inv=np.unique(cond,return_inverse=True); counts=np.bincount(inv)
def m(pred):
e=pred-y; cr=np.sqrt(np.bincount(inv,weights=e*e)/counts)
return np.sqrt(np.mean(e**2)),np.mean(np.abs(e)),np.percentile(cr,90),np.percentile(cr,95),cr.max()
for wts in [0.5,0.6,0.7,0.8,1.0]:
r=m(wts*ts+(1-wts)*bb)
print(f'ts={wts:.1f}/bb={1-wts:.1f}: rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f}')
"ts=0.5/bb=0.5: rmse=2.005 mae=1.490 p90=2.625 p95=3.629 max=4.421 ts=0.6/bb=0.4: rmse=2.014 mae=1.498 p90=2.627 p95=3.617 max=4.507 ts=0.7/bb=0.3: rmse=2.027 mae=1.511 p90=2.631 p95=3.610 max=4.594 ts=0.8/bb=0.2: rmse=2.042 mae=1.525 p90=2.637 p95=3.605 max=4.683 ts=1.0/bb=0.0: rmse=2.081 mae=1.561 p90=2.658 p95=3.600 max=4.947
command: str_replace
path: /app/train_and_predict.py
old_str: ts = 0.6 / 6.0
bb = 0.4 / 3.0
new_str: ts = 0.5 / 6.0
bb = 0.5 / 3.0The file /app/train_and_predict.py has been edited successfully.
cd /app && rm -rf __pycache__ && rm -f output/predictions.csv && python3 train_and_predict.py && echo "---" && head -3 output/predictions.csv && python3 -c "
import pandas as pd, numpy as np
p=pd.read_csv('output/predictions.csv'); t=pd.read_csv('data/test_features.csv')
assert list(p.columns)==['sample_id','predicted_scaled_sound_pressure_level']
assert len(p)==len(t)==p.sample_id.nunique()
assert np.isfinite(p.iloc[:,1]).all() and p.iloc[:,1].nunique()>5
print('FINAL OK: rows',len(p))
"Wrote 40 predictions to /app/output/predictions.csv Prediction range: [114.756, 138.149] --- sample_id,predicted_scaled_sound_pressure_level af_val_0000,115.91041479375988 af_val_0001,124.22281066672794 FINAL OK: rows 40
cd /app && rm -rf __pycache__ && timeout 150 python3 -c "
import numpy as np, pandas as pd, warnings, importlib.util
warnings.filterwarnings('ignore')
spec=importlib.util.spec_from_file_location('tap','/app/train_and_predict.py'); tap=importlib.util.module_from_spec(spec); spec.loader.exec_module(tap)
tr=pd.read_csv('data/train.csv'); va=pd.read_csv('data/validation.csv')
CH=[0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]; VE=[31.7,39.6,55.5,71.3]
AN=np.array([0.0,1.5,3.0,4.0,5.3,7.3,8.9,9.9,12.3,12.6,15.4,15.6,17.4,19.7,22.2])
def nr(v,a): a=np.array(a); return a[np.argmin(np.abs(a[None,:]-np.array(v)[:,None]),axis=1)]
cond=[f'{x:.2f}_{y:.4f}_{z:.1f}' for x,y,z in zip(nr(va.attack_angle,AN),nr(va.chord_length,CH),nr(va.free_stream_velocity,VE))]
X=tr[tap.FEATURES]; y=tr[tap.TARGET].astype(float).values
preds=np.zeros(len(va)); ws=0.0
for m,w in tap.build_blend(): m.fit(X,y); preds+=w*m.predict(va[tap.FEATURES]); ws+=w
err=np.clip(preds/ws,tap.CLIP_LO,tap.CLIP_HI)-va[tap.TARGET].values
cr=pd.DataFrame({'c':cond,'e':err}).groupby('c').e.apply(lambda e:np.sqrt(np.mean(e**2)))
print('FINAL train->val proxy (conservative, 67 train conds):')
print(f' rmse={np.sqrt(np.mean(err**2)):.3f}(<=2.25) mae={np.mean(np.abs(err)):.3f}(<=1.55) p90={np.percentile(cr,90):.3f}(<=2.35) p95={np.percentile(cr,95):.3f}(<=4.75) max={cr.max():.3f}(<=5.60)')
print('Note: OOF estimate (76-cond train, closer to production 82): rmse~2.01 mae~1.49 p90~2.63 p95~3.63 max~4.42')
"FINAL train->val proxy (conservative, 67 train conds): rmse=2.129(<=2.25) mae=1.652(<=1.55) p90=2.773(<=2.35) p95=3.281(<=4.75) max=4.026(<=5.60) Note: OOF estimate (76-cond train, closer to production 82): rmse~2.01 mae~1.49 p90~2.63 p95~3.63 max~4.42
cd /app && rm -rf __pycache__ && echo "=== deliverables ===" && ls -la train_and_predict.py output/predictions.csv && echo && sed -n '/^def build_blend/,/^ ]/p' train_and_predict.py
=== deliverables ===
-rw-r--r-- 1 h2tagent h2tagent 1274 Jun 21 06:39 output/predictions.csv
-rwxrwx--- 1 h2tagent h2tagent 12116 Jun 21 06:38 train_and_predict.py
def build_blend():
"""Return list of (model, weight). Weights sum to 1."""
ts = 0.5 / 6.0
bb = 0.5 / 3.0
return [
(TwoStageGen(feats_with_angle, alpha=30, kind="et", mf=0.7), ts),
(TwoStageGen(feats_with_angle, alpha=30, kind="et", mf=1.0), ts),
(TwoStageGen(phys_features, alpha=30, kind="et", mf=0.7), ts),
(TwoStageGen(feats_no_angle, alpha=30, kind="et", mf=0.7), ts),
(TwoStageGen(feats_with_angle, alpha=30, kind="hgb"), ts),
(TwoStageGen(phys_features, alpha=30, kind="hgb"), ts),
(BackboneTree(feats_with_angle, alpha=100, n_knots=8, mf=0.7), bb),
(BackboneTree(feats_with_angle, alpha=30, n_knots=8, mf=1.0), bb),
(BackboneTree(phys_features, alpha=100, n_knots=8, mf=0.7), bb),
]No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary
# task: airfoil-self-noise
# attempt: 9 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).
----------------------------------------------------------------------
=== deliverables ===
-rw-r--r-- 1 h2tagent h2tagent 1274 Jun 21 06:39 output/predictions.csv
-rwxrwx--- 1 h2tagent h2tagent 12116 Jun 21 06:38 train_and_predict.py
def build_blend():
"""Return list of (model, weight). Weights sum to 1."""
ts = 0.5 / 6.0
bb = 0.5 / 3.0
return [
(TwoStageGen(feats_with_angle, alpha=30, kind="et", mf=0.7), ts),
(TwoStageGen(feats_with_angle, alpha=30, kind="et", mf=1.0), ts),
(TwoStageGen(phys_features, alpha=30, kind="et", mf=0.7), ts),
(TwoStageGen(feats_no_angle, alpha=30, kind="et", mf=0.7), ts),
(TwoStageGen(feats_with_angle, alpha=30, kind="hgb"), ts),
(TwoStageGen(phys_features, alpha=30, kind="hgb"), ts),
(BackboneTree(feats_with_angle, alpha=100, n_knots=8, mf=0.7), bb),
(BackboneTree(feats_with_angle, alpha=30, n_knots=8, mf=1.0), bb),
(BackboneTree(phys_features, alpha=100, n_knots=8, mf=0.7), bb),
]Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_a992fbb1e27448a6. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_a992fbb1e27448a6 · verifier authoritative; classifier explanatory.