tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---" && cat train_and_predict.py
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
---
#!/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/data && ls -la && echo "=== train.csv ===" && head -5 train.csv && echo "..." && wc -l train.csv && echo "=== validation.csv ===" && head -5 validation.csv && wc -l validation.csv && echo "=== test_features.csv ===" && head -5 test_features.csv && wc -l test_features.csv
total 128 drwxrws--- 2 h2tagent h2tagent 4096 Jun 21 05:20 . drwxrws--- 4 h2tagent h2tagent 4096 Jun 21 05:20 .. -rw-rw---- 1 h2tagent h2tagent 3595 Jun 21 05:18 test_features.csv -rw-rw---- 1 h2tagent h2tagent 97417 Jun 21 05:18 train.csv -rw-rw---- 1 h2tagent h2tagent 20351 Jun 21 05:18 validation.csv === train.csv === 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 train.csv === validation.csv === 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 validation.csv === test_features.csv === 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 41 test_features.csv
cd /app/data && python3 -c "
import pandas as pd, numpy as np
train = pd.read_csv('train.csv')
val = pd.read_csv('validation.csv')
test = pd.read_csv('test_features.csv')
print('train shape', train.shape)
print('val shape', val.shape)
print('test shape', test.shape)
print()
print('=== train describe ===')
print(train.describe().T)
print()
print('=== target stats ===')
print('train target:', train['scaled_sound_pressure_level'].describe())
print('val target:', val['scaled_sound_pressure_level'].describe())
"train shape (956, 7)
val shape (201, 7)
test shape (40, 6)
=== 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 stats ===
train target: count 956.000000
mean 125.402453
std 6.704146
min 104.935002
25% 121.083472
50% 126.104163
75% 130.491660
max 140.289040
Name: scaled_sound_pressure_level, dtype: float64
val target: count 201.000000
mean 126.483017
std 6.979769
min 109.788714
25% 122.161017
50% 127.178549
75% 131.746459
max 138.661407
Name: scaled_sound_pressure_level, dtype: float64cd /app/data && python3 -c "
import pandas as pd, numpy as np
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 200)
train = pd.read_csv('train.csv')
val = pd.read_csv('validation.csv')
test = pd.read_csv('test_features.csv')
for name, df in [('train', train), ('val', val), ('test', test)]:
print(f'=== {name} feature ranges ===')
for c in ['frequency','attack_angle','chord_length','free_stream_velocity','suction_side_displacement_thickness']:
print(f' {c}: min={df[c].min():.5g} max={df[c].max():.5g} nunique={df[c].nunique()}')
print()
"=== train feature ranges === frequency: min=199.66 max=20026 nunique=956 attack_angle: min=-0.035 max=22.235 nunique=956 chord_length: min=0.02537 max=0.30516 nunique=956 free_stream_velocity: min=31.652 max=71.407 nunique=956 suction_side_displacement_thickness: min=0.00041129 max=0.058556 nunique=956 === val feature ranges === frequency: min=199.66 max=20017 nunique=201 attack_angle: min=-0.034726 max=17.435 nunique=201 chord_length: min=0.02537 max=0.30516 nunique=201 free_stream_velocity: min=31.653 max=71.407 nunique=201 suction_side_displacement_thickness: min=0.00039969 max=0.040928 nunique=201 === test feature ranges === frequency: min=314.99 max=12483 nunique=40 attack_angle: min=-0.034726 max=17.418 nunique=40 chord_length: min=0.02537 max=0.30495 nunique=40 free_stream_velocity: min=31.687 max=71.387 nunique=40 suction_side_displacement_thickness: min=0.00040062 max=0.040833 nunique=40
cd /app/data && python3 -c "
import pandas as pd, numpy as np
train = pd.read_csv('train.csv')
val = pd.read_csv('validation.csv')
alld = pd.concat([train,val], ignore_index=True)
# original UCI levels
print('chord rounded (3dp) value counts:')
print(alld['chord_length'].round(3).value_counts().sort_index())
print()
print('velocity rounded (0dp) value counts:')
print(alld['free_stream_velocity'].round(0).value_counts().sort_index())
print()
# attack angle: original discrete set
print('attack_angle rounded to nearest 0.5:')
print((alld['attack_angle'].round(1)).describe())
print('num unique rounded angle(0):', alld['attack_angle'].round(0).nunique())
"chord rounded (3dp) value counts: chord_length 0.025 235 0.051 185 0.101 50 0.102 200 0.152 123 0.153 46 0.228 74 0.229 119 0.304 24 0.305 101 Name: count, dtype: int64 velocity rounded (0dp) value counts: free_stream_velocity 32.0 224 40.0 371 55.0 113 56.0 109 71.0 340 Name: count, dtype: int64 attack_angle rounded to nearest 0.5: count 1157.000000 mean 7.125238 std 5.826320 min -0.000000 25% 2.000000 50% 6.700000 75% 12.300000 max 22.200000 Name: attack_angle, dtype: float64 num unique rounded angle(0): 18
cd /app/data && python3 -c "
import pandas as pd, numpy as np
train = pd.read_csv('train.csv')
val = pd.read_csv('validation.csv')
alld = pd.concat([train,val], ignore_index=True)
chord_levels = np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048])
vel_levels = np.array([31.7,39.6,55.5,71.3])
def snap(x, levels):
return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
alld['chord_s']=snap(alld['chord_length'],chord_levels)
alld['vel_s']=snap(alld['free_stream_velocity'],vel_levels)
# relative jitter
alld['chord_relerr']=np.abs(alld['chord_length']-alld['chord_s'])/alld['chord_s']
alld['vel_relerr']=np.abs(alld['free_stream_velocity']-alld['vel_s'])/alld['vel_s']
print('chord relerr max', alld['chord_relerr'].max())
print('vel relerr max', alld['vel_relerr'].max())
print('chord_s counts'); print(alld['chord_s'].value_counts().sort_index())
print('vel_s counts'); print(alld['vel_s'].value_counts().sort_index())
# attack angle canonical
angle_levels=np.array([0.0,1.5,2.0,3.0,4.0,5.3,5.4,6.7,7.3,7.4,8.9,9.5,9.9,11.2,12.3,12.6,13.3,15.4,15.6,16.2,17.4,19.7,22.2])
alld['angle_s']=snap(alld['attack_angle'],angle_levels)
alld['angle_relerr']=np.abs(alld['attack_angle']-alld['angle_s'])
print('angle abs err max', alld['angle_relerr'].max())
grp=alld.groupby(['angle_s','chord_s','vel_s'])
print('num condition groups:', grp.ngroups)
print('group size distribution:'); print(grp.size().describe())
"chord relerr max 0.0011999999980314066 vel relerr max 0.0014999996691918498 chord_s counts chord_s 0.0254 235 0.0508 185 0.1016 250 0.1524 169 0.2286 193 0.3048 125 Name: count, dtype: int64 vel_s counts vel_s 31.7 224 39.6 371 55.5 222 71.3 340 Name: count, dtype: int64 angle abs err max 0.5349990604400006 num condition groups: 82 group size distribution: count 82.000000 mean 14.109756 std 2.325416 min 8.000000 25% 13.000000 50% 14.000000 75% 16.000000 max 18.000000 dtype: float64
cd /app/data && python3 -c "
import pandas as pd, numpy as np
train = pd.read_csv('train.csv'); val = pd.read_csv('validation.csv')
alld = pd.concat([train,val], ignore_index=True)
a = np.sort(alld['attack_angle'].values)
# find gaps
gaps = np.where(np.diff(a) > 0.3)[0]
clusters=[]
start=0
for g in gaps:
clusters.append((a[start],a[g]))
start=g+1
clusters.append((a[start],a[-1]))
print('num angle clusters:', len(clusters))
for lo,hi in clusters:
mid=(lo+hi)/2
print(f' [{lo:.3f},{hi:.3f}] center~{mid:.2f} width={hi-lo:.3f}')
"num angle clusters: 19 [-0.035,0.035] center~0.00 width=0.070 [1.465,1.535] center~1.50 width=0.070 [1.965,2.035] center~2.00 width=0.070 [2.965,3.335] center~3.15 width=0.370 [3.965,4.235] center~4.10 width=0.270 [4.765,4.835] center~4.80 width=0.070 [5.268,5.435] center~5.35 width=0.167 [6.665,6.735] center~6.70 width=0.070 [7.165,7.335] center~7.25 width=0.170 [8.365,8.435] center~8.40 width=0.070 [8.865,8.935] center~8.90 width=0.070 [9.465,9.535] center~9.50 width=0.070 [9.865,9.935] center~9.90 width=0.070 [11.165,11.235] center~11.20 width=0.070 [12.265,12.735] center~12.50 width=0.470 [15.365,15.635] center~15.50 width=0.270 [17.365,17.435] center~17.40 width=0.070 [19.665,19.728] center~19.70 width=0.063 [22.168,22.235] center~22.20 width=0.067
cd /app/data && python3 -c "
import pandas as pd, numpy as np
train = pd.read_csv('train.csv'); val = pd.read_csv('validation.csv')
alld = pd.concat([train,val], ignore_index=True)
a = np.sort(alld['attack_angle'].values)
gaps = np.where(np.diff(a) > 0.12)[0]
clusters=[]; start=0
for g in gaps:
clusters.append((a[start],a[g])); start=g+1
clusters.append((a[start],a[-1]))
print('num angle clusters:', len(clusters))
centers=[]
for lo,hi in clusters:
centers.append((lo+hi)/2)
print(f' [{lo:.3f},{hi:.3f}] center~{(lo+hi)/2:.2f} n_width={hi-lo:.3f}')
"num angle clusters: 23 [-0.035,0.035] center~0.00 n_width=0.070 [1.465,1.535] center~1.50 n_width=0.070 [1.965,2.035] center~2.00 n_width=0.070 [2.965,3.035] center~3.00 n_width=0.070 [3.265,3.335] center~3.30 n_width=0.070 [3.965,4.035] center~4.00 n_width=0.070 [4.165,4.235] center~4.20 n_width=0.069 [4.765,4.835] center~4.80 n_width=0.070 [5.268,5.435] center~5.35 n_width=0.167 [6.665,6.735] center~6.70 n_width=0.070 [7.165,7.335] center~7.25 n_width=0.170 [8.365,8.435] center~8.40 n_width=0.070 [8.865,8.935] center~8.90 n_width=0.070 [9.465,9.535] center~9.50 n_width=0.070 [9.865,9.935] center~9.90 n_width=0.070 [11.165,11.235] center~11.20 n_width=0.070 [12.265,12.335] center~12.30 n_width=0.070 [12.565,12.735] center~12.65 n_width=0.170 [15.365,15.435] center~15.40 n_width=0.070 [15.565,15.635] center~15.60 n_width=0.070 [17.365,17.435] center~17.40 n_width=0.070 [19.665,19.728] center~19.70 n_width=0.063 [22.168,22.235] center~22.20 n_width=0.067
cd /app && python3 -c "
import sklearn; print('sklearn', sklearn.__version__)
import numpy, scipy, pandas
print('numpy', numpy.__version__, 'scipy', scipy.__version__, 'pandas', pandas.__version__)
for m in ['xgboost','lightgbm','catboost']:
try:
mod=__import__(m); print(m, getattr(mod,'__version__','?'))
except Exception as e:
print(m, 'NOT available')
"sklearn 1.4.2 numpy 1.26.4 scipy 1.17.1 pandas 2.2.2 xgboost NOT available lightgbm NOT available catboost NOT available
cd /app && cat > /tmp/bench.py << 'EOF'
import numpy as np, pandas as pd
from pathlib import Path
from sklearn.model_selection import GroupKFold
from sklearn.ensemble import HistGradientBoostingRegressor, RandomForestRegressor, ExtraTreesRegressor
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
alld=pd.concat([train,val],ignore_index=True)
FEATURES=['frequency','attack_angle','chord_length','free_stream_velocity','suction_side_displacement_thickness']
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048])
vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels):
return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
def make_groups(df):
cs=snap(df['chord_length'],chord_levels)
vs=snap(df['free_stream_velocity'],vel_levels)
ang=np.round(df['attack_angle'].values,1)
return pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=df.index)
groups=make_groups(alld)
print('total groups:', groups.nunique(), 'rows', len(alld))
def feat_eng(df):
f=df[FEATURES].astype(float).copy()
f['log_freq']=np.log10(df['frequency'])
f['log_thick']=np.log10(df['suction_side_displacement_thickness'])
f['strouhal']=df['frequency']*df['suction_side_displacement_thickness']/df['free_stream_velocity']
f['log_strouhal']=np.log10(f['strouhal'])
return f
def eval_model(make_model, use_fe=True, n_splits=8):
gkf=GroupKFold(n_splits=n_splits)
X=feat_eng(alld) if use_fe else alld[FEATURES].astype(float)
y=alld[TARGET].values
oof=np.zeros(len(alld))
for tr,te in gkf.split(X,y,groups):
m=make_model(); m.fit(X.iloc[tr],y[tr]); oof[te]=m.predict(X.iloc[te])
err=oof-y
rmse=np.sqrt(np.mean(err**2)); mae=np.mean(np.abs(err))
dfc=pd.DataFrame({'g':groups.values,'e':err})
cond_rmse=dfc.groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
p90=np.percentile(cond_rmse,90); p95=np.percentile(cond_rmse,95); mx=cond_rmse.max()
return dict(rmse=rmse,mae=mae,p90=p90,p95=p95,max=mx)
models={
'ridge_base': (lambda: make_pipeline(StandardScaler(),Ridge(alpha=10.0)), False),
'hgb_default': (lambda: HistGradientBoostingRegressor(random_state=0), True),
'rf': (lambda: RandomForestRegressor(n_estimators=300,random_state=0,n_jobs=-1), True),
'extra': (lambda: ExtraTreesRegressor(n_estimators=300,random_state=0,n_jobs=-1), True),
}
for name,(mk,fe) in models.items():
r=eval_model(mk,fe)
print(f'{name:14s} rmse={r["rmse"]:.3f} mae={r["mae"]:.3f} p90={r["p90"]:.3f} p95={r["p95"]:.3f} max={r["max"]:.3f}')
EOF
python3 /tmp/bench.pytotal groups: 99 rows 1157 ridge_base rmse=5.025 mae=3.916 p90=7.307 p95=8.783 max=10.573 hgb_default rmse=2.427 mae=1.782 p90=3.278 p95=4.500 max=5.362 rf rmse=2.510 mae=1.871 p90=3.471 p95=4.471 max=5.519 extra rmse=2.169 mae=1.598 p90=2.859 p95=3.301 max=6.277
cd /app && python3 -c "
import numpy as np, pandas as pd
from pathlib import Path
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
alld=pd.concat([train,val],ignore_index=True)
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048])
vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
alld['cs']=snap(alld['chord_length'],chord_levels)
alld['vs']=snap(alld['free_stream_velocity'],vel_levels)
alld['ang']=np.round(alld['attack_angle'],1)
g=alld.groupby(['ang','cs','vs'])
# pick a few groups and show freq vs SPL sorted
for key in list(g.groups.keys())[:3]:
sub=alld.loc[g.groups[key]].sort_values('frequency')
print('condition',key,'n=',len(sub))
print(sub[['frequency','scaled_sound_pressure_level','suction_side_displacement_thickness']].to_string(index=False))
print()
" | head -60condition (0.0, 0.0254, 31.7) n= 13 frequency scaled_sound_pressure_level suction_side_displacement_thickness 998.303735 122.809946 0.000441 1248.515389 125.830615 0.000441 1599.884447 127.397325 0.000440 2002.160302 129.062749 0.000440 2504.201453 131.498874 0.000439 3154.480114 134.063850 0.000439 4001.748728 136.107513 0.000438 4996.143559 137.852122 0.000438 6290.010461 137.943474 0.000439 7987.281966 136.743349 0.000439 9992.184744 133.846483 0.000440 12505.325642 128.638849 0.000440 16022.654756 125.284622 0.000441 condition (0.0, 0.0254, 39.6) n= 11 frequency scaled_sound_pressure_level suction_side_displacement_thickness 1600.327555 123.534534 0.000430 1998.051464 129.673033 0.000429 2495.861447 131.746459 0.000429 3145.304366 133.448012 0.000428 3997.743123 135.769430 0.000428 5003.252735 137.169676 0.000428 6309.657227 137.646249 0.000427 8013.055622 136.838517 0.000428 10008.971079 134.447751 0.000428 12496.298172 130.241072 0.000429 15978.590818 126.113347 0.000429 condition (0.0, 0.0254, 55.5) n= 11 frequency scaled_sound_pressure_level suction_side_displacement_thickness 2003.240274 127.974223 0.000413 2503.874951 130.693118 0.000413 3152.166589 131.388405 0.000413 3997.896690 134.363174 0.000412 4992.646147 137.158076 0.000412 6289.515723 137.654522 0.000411 7991.942369 137.883574 0.000411 10001.644300 136.404970 0.000412 12515.650531 133.375099 0.000412 16027.198067 127.638106 0.000413 20025.582040 123.867534 0.000413
cd /app && cat > /tmp/bench2.py << 'EOF'
import numpy as np, pandas as pd
from pathlib import Path
from sklearn.model_selection import GroupKFold
from sklearn.ensemble import HistGradientBoostingRegressor, ExtraTreesRegressor, RandomForestRegressor
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
alld=pd.concat([train,val],ignore_index=True)
FEATURES=['frequency','attack_angle','chord_length','free_stream_velocity','suction_side_displacement_thickness']
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048])
vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
def make_groups(df):
cs=snap(df['chord_length'],chord_levels); vs=snap(df['free_stream_velocity'],vel_levels)
ang=np.round(df['attack_angle'].values,1)
return pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=df.index)
groups=make_groups(alld)
def fe(df):
c=1.225*20.05*np.sqrt(1) # not needed
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); ang=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float)
th=df['suction_side_displacement_thickness'].astype(float)
f['frequency']=freq; f['attack_angle']=ang; f['chord_length']=chord
f['free_stream_velocity']=vel; f['thickness']=th
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel)
f['log_chord']=np.log10(chord)
St=freq*th/vel
f['log_St']=np.log10(St)
f['log_St2']=f['log_St']**2
f['mach']=vel/340.3
f['log_mach']=np.log10(f['mach'])
f['re_c']=vel*chord/1.5e-5
f['log_rec']=np.log10(f['re_c'])
f['re_delta']=vel*th/1.5e-5
f['log_redelta']=np.log10(f['re_delta'])
f['angle_rad']=np.deg2rad(ang)
f['freq_thick']=freq*th
return f
def evalm(make_model, featfn, n_splits=8, seeds=(0,)):
X=featfn(alld); y=alld[TARGET].values
gkf=GroupKFold(n_splits=n_splits)
oofs=[]
for seed in seeds:
oof=np.zeros(len(alld))
for tr,te in gkf.split(X,y,groups):
m=make_model(seed); m.fit(X.iloc[tr],y[tr]); oof[te]=m.predict(X.iloc[te])
oofs.append(oof)
oof=np.mean(oofs,axis=0)
err=oof-y; rmse=np.sqrt(np.mean(err**2)); mae=np.mean(np.abs(err))
dfc=pd.DataFrame({'g':groups.values,'e':err})
cr=dfc.groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=rmse,mae=mae,p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max())
def pr(name,r):
flag=''
if r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60: flag=' <-- PASS'
print(f'{name:28s} rmse={r["rmse"]:.3f} mae={r["mae"]:.3f} p90={r["p90"]:.3f} p95={r["p95"]:.3f} max={r["max"]:.3f}{flag}')
pr('hgb_fe', evalm(lambda s:HistGradientBoostingRegressor(random_state=s),fe))
pr('hgb_tuned', evalm(lambda s:HistGradientBoostingRegressor(learning_rate=0.05,max_iter=600,max_leaf_nodes=31,min_samples_leaf=20,l2_regularization=1.0,random_state=s),fe))
pr('hgb_deep', evalm(lambda s:HistGradientBoostingRegressor(learning_rate=0.03,max_iter=1000,max_leaf_nodes=63,min_samples_leaf=15,l2_regularization=2.0,random_state=s),fe))
pr('extra_fe', evalm(lambda s:ExtraTreesRegressor(n_estimators=500,random_state=s,n_jobs=-1),fe))
pr('rf_fe', evalm(lambda s:RandomForestRegressor(n_estimators=500,random_state=s,n_jobs=-1),fe))
EOF
python3 /tmp/bench2.pyhgb_fe rmse=2.365 mae=1.756 p90=3.313 p95=3.920 max=5.119 hgb_tuned rmse=2.323 mae=1.717 p90=3.346 p95=4.088 max=5.024 hgb_deep rmse=2.242 mae=1.678 p90=3.251 p95=3.664 max=4.233 extra_fe rmse=2.196 mae=1.629 p90=2.851 p95=3.378 max=5.731 rf_fe rmse=2.413 mae=1.812 p90=3.370 p95=3.837 max=5.085
cd /app && cat > /tmp/diag.py << 'EOF'
import numpy as np, pandas as pd
from pathlib import Path
from sklearn.model_selection import GroupKFold
from sklearn.ensemble import ExtraTreesRegressor, HistGradientBoostingRegressor
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
alld=pd.concat([train,val],ignore_index=True)
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048])
vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(alld['chord_length'],chord_levels); vs=snap(alld['free_stream_velocity'],vel_levels)
ang=np.round(alld['attack_angle'].values,1)
groups=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=alld.index)
def fe(df):
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
f['frequency']=freq; f['attack_angle']=a; f['chord_length']=chord; f['free_stream_velocity']=vel; f['thickness']=th
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel); f['log_chord']=np.log10(chord)
f['log_St']=np.log10(freq*th/vel); f['freq_thick']=freq*th
return f
X=fe(alld); y=alld[TARGET].values
gkf=GroupKFold(8); oof=np.zeros(len(alld))
for tr,te in gkf.split(X,y,groups):
m=ExtraTreesRegressor(n_estimators=500,random_state=0,n_jobs=-1); m.fit(X.iloc[tr],y[tr]); oof[te]=m.predict(X.iloc[te])
err=oof-y
df=pd.DataFrame({'g':groups.values,'ang':ang,'cs':cs,'vs':vs,'e':err})
cr=df.groupby('g').agg(rmse=('e',lambda s:np.sqrt(np.mean(s**2))),bias=('e','mean'),n=('e','size'))
cr['ang']=[float(g.split('_')[0]) for g in cr.index]
cr['cs']=[float(g.split('_')[1]) for g in cr.index]
cr['vs']=[float(g.split('_')[2]) for g in cr.index]
print('worst 15 conditions by RMSE:')
print(cr.sort_values('rmse',ascending=False).head(15).to_string())
print()
print('corr of condition rmse with angle:', cr['rmse'].corr(cr['ang']))
print('mean rmse by chord:'); print(cr.groupby('cs')['rmse'].mean())
print('mean rmse by vel:'); print(cr.groupby('vs')['rmse'].mean())
print('mean rmse by angle bucket:')
cr['angb']=pd.cut(cr['ang'],[-1,1,5,10,15,25])
print(cr.groupby('angb')['rmse'].mean())
EOF
python3 /tmp/diag.pyworst 15 conditions by RMSE:
rmse bias n ang cs vs
g
19.7_0.0508_71.3 5.932045 5.544687 14 19.7 0.0508 71.3
12.6_0.1524_39.6 4.772514 3.449714 16 12.6 0.1524 39.6
0.0_0.0254_71.3 4.228137 0.188566 8 0.0 0.0254 71.3
22.2_0.0254_39.6 3.859800 -1.620425 15 22.2 0.0254 39.6
7.3_0.2286_71.3 3.605476 -2.660267 16 7.3 0.2286 71.3
17.4_0.0254_39.6 3.208366 0.054136 15 17.4 0.0254 39.6
17.4_0.0254_31.7 3.114495 -0.406112 15 17.4 0.0254 31.7
12.7_0.0254_71.3 3.021497 -0.947733 17 12.7 0.0254 71.3
11.2_0.0508_39.6 2.994085 0.270081 14 11.2 0.0508 39.6
12.3_0.1016_71.3 2.779258 -1.946509 16 12.3 0.1016 71.3
11.2_0.0508_71.3 2.769484 -2.261324 14 11.2 0.0508 71.3
12.3_0.1016_31.7 2.710464 2.514468 16 12.3 0.1016 31.7
4.0_0.3048_71.3 2.607377 -1.844945 18 4.0 0.3048 71.3
-0.0_0.0508_31.7 2.546049 2.037852 7 -0.0 0.0508 31.7
7.2_0.1524_71.3 2.509726 0.579689 16 7.2 0.1524 71.3
corr of condition rmse with angle: 0.5910792315406085
mean rmse by chord:
cs
0.0254 2.083581
0.0508 2.181101
0.1016 1.721052
0.1524 1.726308
0.2286 1.448189
0.3048 1.275327
Name: rmse, dtype: float64
mean rmse by vel:
vs
31.7 1.585958
39.6 1.782898
55.5 1.453560
71.3 2.210939
Name: rmse, dtype: float64
mean rmse by angle bucket:
angb
(-1, 1] 1.421955
(1, 5] 1.303707
(5, 10] 1.937299
(10, 15] 2.737317
(15, 25] 2.756336
Name: rmse, dtype: float64
[stderr]
/tmp/diag.py:41: 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.
print(cr.groupby('angb')['rmse'].mean())cd /app && cat > /tmp/bench3.py << 'EOF'
import numpy as np, pandas as pd
from pathlib import Path
from sklearn.model_selection import GroupKFold
from sklearn.ensemble import HistGradientBoostingRegressor, ExtraTreesRegressor, RandomForestRegressor
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
alld=pd.concat([train,val],ignore_index=True)
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(alld['chord_length'],chord_levels); vs=snap(alld['free_stream_velocity'],vel_levels)
ang=np.round(alld['attack_angle'].values,1)
groups=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=alld.index)
def fe(df):
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
f['frequency']=freq; f['attack_angle']=a; f['chord_length']=chord; f['free_stream_velocity']=vel; f['thickness']=th
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel); f['log_chord']=np.log10(chord)
f['log_St']=np.log10(freq*th/vel); f['freq_thick']=freq*th
f['angle_thick']=a*th; f['log_St2']=f['log_St']**2
return f
X=fe(alld); y=alld[TARGET].values
def oof_for(make_model,seeds=(0,)):
gkf=GroupKFold(8); acc=np.zeros(len(alld))
for seed in seeds:
oof=np.zeros(len(alld))
for tr,te in gkf.split(X,y,groups):
m=make_model(seed); m.fit(X.iloc[tr],y[tr]); oof[te]=m.predict(X.iloc[te])
acc+=oof
return acc/len(seeds)
def metrics(oof):
err=oof-y; dfc=pd.DataFrame({'g':groups.values,'e':err})
cr=dfc.groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max())
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:30s} rmse={r["rmse"]:.3f} mae={r["mae"]:.3f} p90={r["p90"]:.3f} p95={r["p95"]:.3f} max={r["max"]:.3f}{f}')
oof_extra=oof_for(lambda s:ExtraTreesRegressor(n_estimators=700,min_samples_leaf=1,random_state=s,n_jobs=-1),seeds=(0,1,2))
oof_hgb=oof_for(lambda s:HistGradientBoostingRegressor(learning_rate=0.03,max_iter=1000,max_leaf_nodes=63,min_samples_leaf=15,l2_regularization=2.0,random_state=s),seeds=(0,1))
oof_rf=oof_for(lambda s:RandomForestRegressor(n_estimators=700,random_state=s,n_jobs=-1),seeds=(0,1))
pr('extra',metrics(oof_extra)); pr('hgb',metrics(oof_hgb)); pr('rf',metrics(oof_rf))
for w in [0.3,0.4,0.5,0.6,0.7]:
pr(f'blend extra{w:.1f}/hgb',metrics(w*oof_extra+(1-w)*oof_hgb))
pr('blend 0.5e/0.25h/0.25rf',metrics(0.5*oof_extra+0.25*oof_hgb+0.25*oof_rf))
pr('blend 0.4e/0.4h/0.2rf',metrics(0.4*oof_extra+0.4*oof_hgb+0.2*oof_rf))
np.save('/tmp/oof_extra.npy',oof_extra); np.save('/tmp/oof_hgb.npy',oof_hgb); np.save('/tmp/oof_rf.npy',oof_rf)
EOF
python3 /tmp/bench3.pyextra rmse=2.114 mae=1.553 p90=2.763 p95=3.167 max=5.921 hgb rmse=2.241 mae=1.691 p90=3.240 p95=3.680 max=5.032 rf rmse=2.382 mae=1.772 p90=3.290 p95=4.003 max=4.894 blend extra0.3/hgb rmse=2.136 mae=1.606 p90=2.951 p95=3.533 max=4.654 blend extra0.4/hgb rmse=2.113 mae=1.583 p90=2.871 p95=3.480 max=4.772 blend extra0.5/hgb rmse=2.096 mae=1.565 p90=2.839 p95=3.480 max=4.954 blend extra0.6/hgb rmse=2.086 mae=1.550 p90=2.771 p95=3.483 max=5.140 blend extra0.7/hgb rmse=2.083 mae=1.542 p90=2.720 p95=3.341 max=5.330 blend 0.5e/0.25h/0.25rf rmse=2.136 mae=1.585 p90=2.841 p95=3.501 max=5.142 blend 0.4e/0.4h/0.2rf rmse=2.134 mae=1.591 p90=2.899 p95=3.573 max=4.919
cd /app && python3 -c "
import numpy as np, pandas as pd
from pathlib import Path
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
def grp(df):
cs=snap(df['chord_length'],chord_levels); vs=snap(df['free_stream_velocity'],vel_levels); ang=np.round(df['attack_angle'].values,1)
return set(f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs))
gt=grp(train); gv=grp(val)
print('train groups',len(gt),'val groups',len(gv))
print('overlap',len(gt&gv),'val-only',len(gv-gt),'train-only',len(gt-gv))
"train groups 79 val groups 20 overlap 0 val-only 20 train-only 79
cd /app && cat > /tmp/tv.py << 'EOF'
import numpy as np, pandas as pd
from pathlib import Path
from sklearn.ensemble import HistGradientBoostingRegressor, ExtraTreesRegressor, RandomForestRegressor
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
def grp(df):
cs=snap(df['chord_length'],chord_levels); vs=snap(df['free_stream_velocity'],vel_levels); ang=np.round(df['attack_angle'].values,1)
return pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=df.index)
vg=grp(val)
def fe(df):
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
f['frequency']=freq; f['attack_angle']=a; f['chord_length']=chord; f['free_stream_velocity']=vel; f['thickness']=th
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel); f['log_chord']=np.log10(chord)
f['log_St']=np.log10(freq*th/vel); f['freq_thick']=freq*th; f['log_St2']=f['log_St']**2; f['angle_thick']=a*th
return f
Xtr=fe(train); ytr=train[TARGET].values; Xv=fe(val); yv=val[TARGET].values
def metrics(pred):
err=pred-yv; dfc=pd.DataFrame({'g':vg.values,'e':err})
cr=dfc.groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max(),ncond=len(cr))
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:22s} rmse={r["rmse"]:.3f} mae={r["mae"]:.3f} p90={r["p90"]:.3f} p95={r["p95"]:.3f} max={r["max"]:.3f} nc={r["ncond"]}{f}')
def predavg(make,seeds):
p=np.zeros(len(val))
for s in seeds:
m=make(s); m.fit(Xtr,ytr); p+=m.predict(Xv)
return p/len(seeds)
pe=predavg(lambda s:ExtraTreesRegressor(n_estimators=700,random_state=s,n_jobs=-1),(0,1,2))
ph=predavg(lambda s:HistGradientBoostingRegressor(learning_rate=0.03,max_iter=1000,max_leaf_nodes=63,min_samples_leaf=15,l2_regularization=2.0,random_state=s),(0,1))
prf=predavg(lambda s:RandomForestRegressor(n_estimators=700,random_state=s,n_jobs=-1),(0,1))
pr('extra',metrics(pe)); pr('hgb',metrics(ph)); pr('rf',metrics(prf))
for w in [0.4,0.5,0.6,0.7]:
pr(f'blend e{w}/h',metrics(w*pe+(1-w)*ph))
np.save('/tmp/pe.npy',pe); np.save('/tmp/ph.npy',ph); np.save('/tmp/prf.npy',prf); np.save('/tmp/yv.npy',yv)
EOF
python3 /tmp/tv.pyextra rmse=2.075 mae=1.596 p90=2.743 p95=3.011 max=4.158 nc=20 hgb rmse=2.378 mae=1.856 p90=3.394 p95=3.430 max=3.538 nc=20 rf rmse=2.499 mae=1.947 p90=3.464 p95=3.727 max=3.731 nc=20 blend e0.4/h rmse=2.195 mae=1.716 p90=3.044 p95=3.220 max=3.650 nc=20 blend e0.5/h rmse=2.161 mae=1.688 p90=2.987 p95=3.161 max=3.722 nc=20 blend e0.6/h rmse=2.133 mae=1.663 p90=2.932 p95=3.112 max=3.799 nc=20 blend e0.7/h rmse=2.110 mae=1.642 p90=2.880 p95=3.072 max=3.882 nc=20
cd /app && python3 -c "
import numpy as np, pandas as pd
from pathlib import Path
DATA=Path('/app/data'); val=pd.read_csv(DATA/'validation.csv')
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(val['chord_length'],chord_levels); vs=snap(val['free_stream_velocity'],vel_levels); ang=np.round(val['attack_angle'].values,1)
g=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)])
pe=np.load('/tmp/pe.npy'); yv=np.load('/tmp/yv.npy')
err=pe-yv
df=pd.DataFrame({'g':g.values,'e':err,'ang':ang,'cs':cs,'vs':vs})
cr=df.groupby('g').agg(rmse=('e',lambda s:np.sqrt(np.mean(s**2))),bias=('e','mean'),n=('e','size')).sort_values('rmse',ascending=False)
print(cr.to_string())
print('p90',np.percentile(cr['rmse'],90))
"rmse bias n g 0.0_0.0254_71.3 4.158199 -0.202348 8 17.4_0.0254_39.6 2.951119 -0.131138 15 12.3_0.1016_55.5 2.719593 -2.087879 16 12.3_0.1016_39.6 2.474273 -2.079393 16 -0.0_0.0254_71.3 2.285053 1.040894 2 0.0_0.3048_31.7 2.135662 1.342807 10 0.0_0.0254_39.6 2.098703 0.271393 4 6.7_0.1016_55.5 1.968148 -0.928467 8 4.0_0.2286_31.7 1.967475 -0.833708 15 8.4_0.0508_55.5 1.921646 0.388163 12 4.8_0.0254_71.3 1.754542 -1.072253 11 0.0_0.1524_39.6 1.665585 0.543886 6 3.3_0.1016_71.3 1.576397 0.502142 12 4.8_0.0254_39.6 1.564003 0.561527 14 -0.0_0.3048_31.7 1.562260 1.407279 8 -0.0_0.0508_55.5 1.231668 0.387106 7 9.9_0.1524_31.7 1.064199 -0.435642 16 -0.0_0.0254_39.6 0.754482 0.436518 7 0.0_0.0508_55.5 0.721334 -0.593625 6 -0.0_0.1524_39.6 0.624463 -0.347815 8 p90 2.7427452165433177
cd /app && cat > /tmp/smooth.py << 'EOF'
import numpy as np, pandas as pd, warnings
warnings.filterwarnings('ignore')
from pathlib import Path
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler, SplineTransformer
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.kernel_ridge import KernelRidge
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ConstantKernel, WhiteKernel, Matern
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(val['chord_length'],chord_levels); vs=snap(val['free_stream_velocity'],vel_levels); ang=np.round(val['attack_angle'].values,1)
vg=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=val.index)
def fe(df):
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel); f['log_chord']=np.log10(chord)
f['attack_angle']=a; f['log_St']=np.log10(freq*th/vel)
return f
Xtr=fe(train); ytr=train[TARGET].values; Xv=fe(val); yv=val[TARGET].values
def metrics(pred):
err=pred-yv; cr=pd.DataFrame({'g':vg.values,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max())
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:26s} rmse={r["rmse"]:.3f} mae={r["mae"]:.3f} p90={r["p90"]:.3f} p95={r["p95"]:.3f} max={r["max"]:.3f}{f}')
# MLP
def mlp(s): return make_pipeline(StandardScaler(), MLPRegressor(hidden_layer_sizes=(128,128,64),activation='relu',alpha=1e-3,learning_rate_init=3e-3,max_iter=3000,early_stopping=False,random_state=s))
pm=np.mean([mlp(s).fit(Xtr,ytr).predict(Xv) for s in range(5)],axis=0)
pr('mlp_x5',metrics(pm))
# Spline + Ridge
spl=make_pipeline(SplineTransformer(n_knots=8,degree=3,include_bias=False),StandardScaler(),Ridge(alpha=1.0))
spl.fit(Xtr,ytr); pr('spline_ridge',metrics(spl.predict(Xv)))
# Kernel ridge RBF
kr=make_pipeline(StandardScaler(),KernelRidge(alpha=0.1,kernel='rbf',gamma=0.1))
kr.fit(Xtr,ytr); pr('kernelridge',metrics(kr.predict(Xv)))
# GP
k=ConstantKernel(1.0)*Matern(length_scale=[1.0]*6,nu=2.5)+WhiteKernel(0.1)
sc=StandardScaler().fit(Xtr)
gp=GaussianProcessRegressor(kernel=k,alpha=1e-6,normalize_y=True,n_restarts_optimizer=0)
gp.fit(sc.transform(Xtr),ytr); pr('gp_matern',metrics(gp.predict(sc.transform(Xv))))
np.save('/tmp/pm.npy',pm)
EOF
python3 /tmp/smooth.pymlp_x5 rmse=2.712 mae=2.079 p90=4.075 p95=4.211 max=6.280 spline_ridge rmse=3.664 mae=2.828 p90=4.546 p95=4.800 max=7.765 kernelridge rmse=4.256 mae=3.062 p90=6.590 p95=12.029 max=12.189 gp_matern rmse=1.850 mae=1.386 p90=2.429 p95=3.236 max=4.277
cd /app && cat > /tmp/gp.py << 'EOF'
import numpy as np, pandas as pd, warnings, time
warnings.filterwarnings('ignore')
from pathlib import Path
from sklearn.preprocessing import StandardScaler
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ConstantKernel, WhiteKernel, Matern
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(val['chord_length'],chord_levels); vs=snap(val['free_stream_velocity'],vel_levels); ang=np.round(val['attack_angle'].values,1)
vg=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=val.index)
def fe(df):
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel); f['log_chord']=np.log10(chord)
f['attack_angle']=a; f['log_St']=np.log10(freq*th/vel)
return f
Xtr=fe(train); ytr=train[TARGET].values; Xv=fe(val); yv=val[TARGET].values
nf=Xtr.shape[1]
def metrics(pred):
err=pred-yv; cr=pd.DataFrame({'g':vg.values,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max())
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:30s} rmse={r["rmse"]:.3f} mae={r["mae"]:.3f} p90={r["p90"]:.3f} p95={r["p95"]:.3f} max={r["max"]:.3f}{f}')
sc=StandardScaler().fit(Xtr); Xs=sc.transform(Xtr); Xvs=sc.transform(Xv)
def fitgp(kernel,alpha=1e-6,nr=2):
gp=GaussianProcessRegressor(kernel=kernel,alpha=alpha,normalize_y=True,n_restarts_optimizer=nr,random_state=0)
gp.fit(Xs,ytr); return gp
for nu in [1.5,2.5]:
for wk in [0.05,0.1,0.2]:
k=ConstantKernel(1.0)*Matern(length_scale=[1.0]*nf,nu=nu)+WhiteKernel(wk,(1e-3,1.0))
t=time.time(); gp=fitgp(k); p=gp.predict(Xvs)
pr(f'matern nu={nu} wk0={wk}',metrics(p))
# RBF ARD
k=ConstantKernel(1.0)*RBF(length_scale=[1.0]*nf)+WhiteKernel(0.1,(1e-3,1.0))
gp=fitgp(k); pr('rbf_ard',metrics(gp.predict(Xvs)))
# save best matern nu2.5 wk0.1 pred and its learned kernel
k=ConstantKernel(1.0)*Matern(length_scale=[1.0]*nf,nu=2.5)+WhiteKernel(0.1,(1e-3,1.0))
gp=fitgp(k); np.save('/tmp/pgp.npy',gp.predict(Xvs)); print('learned:',gp.kernel_)
EOF
python3 /tmp/gp.py[stderr] matern nu=1.5 wk0=0.05 rmse=1.822 mae=1.339 p90=2.337 p95=3.247 max=4.255 PASS matern nu=1.5 wk0=0.1 rmse=1.822 mae=1.339 p90=2.337 p95=3.247 max=4.255 PASSShell command timed out. Tool interaction exceeded 300 seconds.
restart: true
<system>tool has been restarted.</system>
cd /app && cat > /tmp/gp2.py << 'EOF'
import numpy as np, pandas as pd, warnings, time
warnings.filterwarnings('ignore')
from pathlib import Path
from sklearn.preprocessing import StandardScaler
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel, WhiteKernel, Matern
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(val['chord_length'],chord_levels); vs=snap(val['free_stream_velocity'],vel_levels); ang=np.round(val['attack_angle'].values,1)
vg=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=val.index)
def fe(df):
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel); f['log_chord']=np.log10(chord)
f['attack_angle']=a; f['log_St']=np.log10(freq*th/vel)
return f
Xtr=fe(train); ytr=train[TARGET].values; Xv=fe(val); yv=val[TARGET].values
nf=Xtr.shape[1]
def metrics(pred):
err=pred-yv; cr=pd.DataFrame({'g':vg.values,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max())
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:26s} rmse={r["rmse"]:.3f} mae={r["mae"]:.3f} p90={r["p90"]:.3f} p95={r["p95"]:.3f} max={r["max"]:.3f}{f}')
sc=StandardScaler().fit(Xtr); Xs=sc.transform(Xtr); Xvs=sc.transform(Xv)
t=time.time()
k=ConstantKernel(1.0)*Matern(length_scale=[1.0]*nf,nu=1.5)+WhiteKernel(0.1,(1e-3,1.0))
gp=GaussianProcessRegressor(kernel=k,alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(Xs,ytr)
p=gp.predict(Xvs)
pr('matern1.5 nr=0',metrics(p)); print('time',round(time.time()-t,1),'kernel',gp.kernel_)
np.save('/tmp/pgp.npy',p)
EOF
timeout 120 python3 /tmp/gp2.pymatern1.5 nr=0 rmse=1.822 mae=1.339 p90=2.337 p95=3.247 max=4.255 PASS time 13.9 kernel 1.34**2 * Matern(length_scale=[1.39, 2.38, 6.29, 1.18, 2.06, 3.24], nu=1.5) + WhiteKernel(noise_level=0.00117)
cd /app && ls -la /tmp/*.npy 2>/dev/null; python3 -c "
import numpy as np, pandas as pd
pe=np.load('/tmp/pe.npy'); pgp=np.load('/tmp/pgp.npy'); yv=np.load('/tmp/yv.npy')
from pathlib import Path
val=pd.read_csv('/app/data/validation.csv')
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(val['chord_length'],chord_levels); vs=snap(val['free_stream_velocity'],vel_levels); ang=np.round(val['attack_angle'].values,1)
vg=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)])
def metrics(pred):
err=pred-yv; cr=pd.DataFrame({'g':vg.values,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max())
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:20s} rmse={r[\"rmse\"]:.3f} mae={r[\"mae\"]:.3f} p90={r[\"p90\"]:.3f} p95={r[\"p95\"]:.3f} max={r[\"max\"]:.3f}{f}')
pr('gp',metrics(pgp)); pr('extra',metrics(pe))
for w in [0.6,0.7,0.8,0.85,0.9]:
pr(f'gp{w}/extra',metrics(w*pgp+(1-w)*pe))
"-rw-r--r-- 1 h2tagent h2tagent 9384 Jun 21 05:36 /tmp/oof_extra.npy -rw-r--r-- 1 h2tagent h2tagent 9384 Jun 21 05:36 /tmp/oof_hgb.npy -rw-r--r-- 1 h2tagent h2tagent 9384 Jun 21 05:36 /tmp/oof_rf.npy -rw-r--r-- 1 h2tagent h2tagent 1736 Jun 21 05:37 /tmp/pe.npy -rw-r--r-- 1 h2tagent h2tagent 1736 Jun 21 05:44 /tmp/pgp.npy -rw-r--r-- 1 h2tagent h2tagent 1736 Jun 21 05:37 /tmp/ph.npy -rw-r--r-- 1 h2tagent h2tagent 1736 Jun 21 05:38 /tmp/pm.npy -rw-r--r-- 1 h2tagent h2tagent 1736 Jun 21 05:37 /tmp/prf.npy -rw-r--r-- 1 h2tagent h2tagent 1736 Jun 21 05:37 /tmp/yv.npy gp rmse=1.822 mae=1.339 p90=2.337 p95=3.247 max=4.255 PASS extra rmse=2.075 mae=1.596 p90=2.743 p95=3.011 max=4.158 gp0.6/extra rmse=1.810 mae=1.365 p90=2.123 p95=3.108 max=4.214 PASS gp0.7/extra rmse=1.798 mae=1.352 p90=2.069 p95=3.138 max=4.224 PASS gp0.8/extra rmse=1.796 mae=1.342 p90=2.076 p95=3.171 max=4.234 PASS gp0.85/extra rmse=1.799 mae=1.341 p90=2.140 p95=3.188 max=4.239 PASS gp0.9/extra rmse=1.804 mae=1.339 p90=2.204 p95=3.207 max=4.245 PASS
cd /app && cat > /tmp/cv_gp.py << 'EOF'
import numpy as np, pandas as pd, warnings, time
warnings.filterwarnings('ignore')
from pathlib import Path
from sklearn.model_selection import GroupKFold
from sklearn.preprocessing import StandardScaler
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel, WhiteKernel, Matern
from sklearn.ensemble import ExtraTreesRegressor
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
alld=pd.concat([train,val],ignore_index=True)
TARGET='scaled_sound_pressure_level'
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(alld['chord_length'],chord_levels); vs=snap(alld['free_stream_velocity'],vel_levels); ang=np.round(alld['attack_angle'].values,1)
groups=pd.Series([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)],index=alld.index)
def fe(df, extra=False):
f=pd.DataFrame(index=df.index)
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
f['log_freq']=np.log10(freq); f['log_thick']=np.log10(th); f['log_vel']=np.log10(vel); f['log_chord']=np.log10(chord)
f['attack_angle']=a; f['log_St']=np.log10(freq*th/vel)
if extra:
f['frequency']=freq; f['chord_length']=chord; f['free_stream_velocity']=vel; f['thickness']=th; f['freq_thick']=freq*th
return f
Xg=fe(alld); Xe=fe(alld,extra=True); y=alld[TARGET].values; nf=Xg.shape[1]
gkf=GroupKFold(8)
oof_gp=np.zeros(len(alld)); oof_ex=np.zeros(len(alld))
t=time.time()
for i,(tr,te) in enumerate(gkf.split(Xg,y,groups)):
sc=StandardScaler().fit(Xg.iloc[tr]); Xs=sc.transform(Xg.iloc[tr]); Xts=sc.transform(Xg.iloc[te])
k=ConstantKernel(1.0)*Matern(length_scale=[1.0]*nf,nu=1.5)+WhiteKernel(0.1,(1e-3,1.0))
gp=GaussianProcessRegressor(kernel=k,alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(Xs,y[tr])
oof_gp[te]=gp.predict(Xts)
em=ExtraTreesRegressor(n_estimators=700,random_state=0,n_jobs=-1).fit(Xe.iloc[tr],y[tr])
oof_ex[te]=em.predict(Xe.iloc[te])
print(f'fold {i} done t={time.time()-t:.0f}s',flush=True)
np.save('/tmp/oof_gp.npy',oof_gp); np.save('/tmp/oof_ex2.npy',oof_ex); np.save('/tmp/y_all.npy',y)
groups.to_csv('/tmp/groups_all.csv',index=False)
print('saved')
EOF
nohup python3 /tmp/cv_gp.py > /tmp/cv_gp.log 2>&1 &
echo "started pid $!"started pid 1108
sleep 60 && cat /tmp/cv_gp.log
fold 0 done t=17s fold 1 done t=34s fold 2 done t=50s fold 3 done t=65s
sleep 75 && cat /tmp/cv_gp.log
fold 0 done t=17s fold 1 done t=34s fold 2 done t=50s fold 3 done t=65s fold 4 done t=80s fold 5 done t=96s fold 6 done t=112s fold 7 done t=127s saved
cd /app && python3 -c "
import numpy as np, pandas as pd
oof_gp=np.load('/tmp/oof_gp.npy'); oof_ex=np.load('/tmp/oof_ex2.npy'); y=np.load('/tmp/y_all.npy')
groups=pd.read_csv('/tmp/groups_all.csv').iloc[:,0].values
def metrics(pred):
err=pred-y; cr=pd.DataFrame({'g':groups,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max(),nworst=cr.sort_values(ascending=False).head(5).round(2).to_dict())
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:16s} rmse={r[\"rmse\"]:.3f} mae={r[\"mae\"]:.3f} p90={r[\"p90\"]:.3f} p95={r[\"p95\"]:.3f} max={r[\"max\"]:.3f}{f}')
pr('gp',metrics(oof_gp)); pr('extra',metrics(oof_ex))
for w in [0.6,0.65,0.7,0.75,0.8,0.85,0.9]:
pr(f'gp{w}/ex',metrics(w*oof_gp+(1-w)*oof_ex))
print('GP worst conds:',metrics(oof_gp)['nworst'])
print('blend0.75 worst:',metrics(0.75*oof_gp+0.25*oof_ex)['nworst'])
"gp rmse=2.040 mae=1.382 p90=3.023 p95=3.774 max=6.705
extra rmse=2.124 mae=1.561 p90=2.787 p95=3.285 max=5.937
gp0.6/ex rmse=1.939 mae=1.348 p90=2.658 p95=3.270 max=5.509
gp0.65/ex rmse=1.942 mae=1.344 p90=2.680 p95=3.298 max=5.654
gp0.7/ex rmse=1.947 mae=1.341 p90=2.703 p95=3.412 max=5.800
gp0.75/ex rmse=1.956 mae=1.342 p90=2.735 p95=3.529 max=5.948
gp0.8/ex rmse=1.967 mae=1.346 p90=2.810 p95=3.635 max=6.097
gp0.85/ex rmse=1.981 mae=1.352 p90=2.886 p95=3.678 max=6.247
gp0.9/ex rmse=1.998 mae=1.359 p90=2.963 p95=3.720 max=6.399
GP worst conds: {'22.2_0.0254_39.6': 6.7, '19.7_0.0508_71.3': 4.61, '0.0_0.0254_71.3': 4.29, '12.7_0.0254_39.6': 4.18, '12.6_0.1524_39.6': 3.79}
blend0.75 worst: {'22.2_0.0254_39.6': 5.95, '19.7_0.0508_71.3': 4.84, '0.0_0.0254_71.3': 4.29, '12.6_0.1524_39.6': 4.04, '17.4_0.0254_31.7': 3.6}cd /app && python3 -c "
import numpy as np, pandas as pd
from pathlib import Path
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv')
alld=pd.concat([train,val],ignore_index=True)
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(alld['chord_length'],chord_levels); vs=snap(alld['free_stream_velocity'],vel_levels); ang=np.round(alld['attack_angle'].values,1)
df=pd.DataFrame({'ang':ang,'cs':cs,'vs':vs})
conds=df.drop_duplicates().sort_values(['ang','cs','vs'])
print('total conditions:',len(conds))
# count conditions per angle
print('conditions per angle level:')
print(conds.groupby('ang').size())
print()
print('angle x chord coverage (count of velocities):')
print(conds.groupby(['ang']).agg(nchord=('cs','nunique'),nvel=('vs','nunique'),ncond=('cs','size')))
"total conditions: 82
conditions per angle level:
ang
0.0 17
1.5 2
2.0 4
3.0 2
3.3 4
4.0 5
4.2 2
4.8 3
5.3 1
5.4 1
6.7 4
7.2 2
7.3 3
8.4 3
8.9 2
9.5 3
9.9 3
11.2 2
12.3 4
12.6 2
12.7 2
15.4 3
15.6 2
17.4 4
19.7 1
22.2 1
dtype: int64
angle x chord coverage (count of velocities):
nchord nvel ncond
ang
0.0 6 4 17
1.5 1 2 2
2.0 1 4 4
3.0 1 2 2
3.3 1 4 4
4.0 2 4 5
4.2 1 2 2
4.8 1 3 3
5.3 1 1 1
5.4 1 1 1
6.7 1 4 4
7.2 1 2 2
7.3 1 3 3
8.4 1 3 3
8.9 1 2 2
9.5 1 3 3
9.9 1 3 3
11.2 1 2 2
12.3 1 4 4
12.6 1 2 2
12.7 1 2 2
15.4 1 3 3
15.6 1 2 2
17.4 1 4 4
19.7 1 1 1
22.2 1 1 1cd /app && python3 -c "
import numpy as np, pandas as pd
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv')
def angset(df): return set(np.round(df['attack_angle'].values,1))
ta=angset(train); va=angset(val)
print('train angles:',sorted(ta))
print('val angles:',sorted(va))
print('val angles NOT in train:',sorted(va-ta))
# chord/vel
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
def cvset(df): return set(zip(snap(df['chord_length'],chord_levels),snap(df['free_stream_velocity'],vel_levels)))
print('val (chord,vel) NOT in train:', cvset(val)-cvset(train))
"train 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]
val angles: [0.0, 3.3, 4.0, 4.8, 6.7, 8.4, 9.9, 12.3, 17.4]
val angles NOT in train: []
val (chord,vel) NOT in train: {(0.3048, 31.7)}cd /app && python3 -c "
import numpy as np, pandas as pd
from sklearn.model_selection import GroupKFold
oof_gp=np.load('/tmp/oof_gp.npy'); oof_ex=np.load('/tmp/oof_ex2.npy'); y=np.load('/tmp/y_all.npy')
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv')
alld=pd.concat([train,val],ignore_index=True)
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(alld['chord_length'],chord_levels); vs=snap(alld['free_stream_velocity'],vel_levels); ang=np.round(alld['attack_angle'].values,1)
groups=np.array([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)])
# recover fold assignment (GroupKFold deterministic)
Xg=np.zeros((len(alld),1)); fold=np.full(len(alld),-1)
for i,(tr,te) in enumerate(GroupKFold(8).split(Xg,y,groups)): fold[te]=i
# covered: condition's angle present in training portion of its fold
covered=np.zeros(len(alld),dtype=bool)
for idx in range(len(alld)):
f=fold[idx]; a=ang[idx]
covered[idx]= np.any((ang==a)&(fold!=f))
def metrics(pred,mask):
err=(pred-y)[mask]; g=groups[mask]
cr=pd.DataFrame({'g':g,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return dict(rmse=np.sqrt(np.mean(err**2)),mae=np.mean(np.abs(err)),p90=np.percentile(cr,90),p95=np.percentile(cr,95),max=cr.max(),nc=len(cr))
def pr(n,r):
f=' PASS' if (r['rmse']<=2.25 and r['mae']<=1.55 and r['p90']<=2.35 and r['p95']<=4.75 and r['max']<=5.60) else ''
print(f'{n:14s} rmse={r[\"rmse\"]:.3f} mae={r[\"mae\"]:.3f} p90={r[\"p90\"]:.3f} p95={r[\"p95\"]:.3f} max={r[\"max\"]:.3f} nc={r[\"nc\"]}{f}')
print('=== realistic filter: only conditions with angle covered in training (nc conds) ===')
print('covered fraction:',covered.mean())
for w in [0.55,0.6,0.65,0.7,0.75]:
pr(f'gp{w}/ex',metrics(w*oof_gp+(1-w)*oof_ex,covered))
print()
print('=== all conditions (pessimistic) ===')
allm=np.ones(len(alld),dtype=bool)
for w in [0.6,0.7]:
pr(f'gp{w}/ex',metrics(w*oof_gp+(1-w)*oof_ex,allm))
"=== realistic filter: only conditions with angle covered in training (nc conds) === covered fraction: 0.9248055315471045 gp0.55/ex rmse=1.792 mae=1.296 p90=2.479 p95=3.064 max=4.288 nc=93 gp0.6/ex rmse=1.788 mae=1.288 p90=2.476 p95=3.103 max=4.288 nc=93 gp0.65/ex rmse=1.787 mae=1.283 p90=2.520 p95=3.143 max=4.287 nc=93 gp0.7/ex rmse=1.788 mae=1.280 p90=2.595 p95=3.149 max=4.287 nc=93 gp0.75/ex rmse=1.793 mae=1.279 p90=2.663 p95=3.156 max=4.287 nc=93 === all conditions (pessimistic) === gp0.6/ex rmse=1.939 mae=1.348 p90=2.658 p95=3.270 max=5.509 nc=99 gp0.7/ex rmse=1.947 mae=1.341 p90=2.703 p95=3.412 max=5.800 nc=99
cd /app && python3 -c "
import numpy as np, pandas as pd
from sklearn.model_selection import GroupKFold
oof_gp=np.load('/tmp/oof_gp.npy'); oof_ex=np.load('/tmp/oof_ex2.npy'); oof_hgb=np.load('/tmp/oof_hgb.npy'); y=np.load('/tmp/y_all.npy')
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv')
alld=pd.concat([train,val],ignore_index=True)
chord_levels=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vel_levels=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
cs=snap(alld['chord_length'],chord_levels); vs=snap(alld['free_stream_velocity'],vel_levels); ang=np.round(alld['attack_angle'].values,1)
groups=np.array([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)])
fold=np.full(len(alld),-1)
for i,(tr,te) in enumerate(GroupKFold(8).split(np.zeros((len(alld),1)),y,groups)): fold[te]=i
covered=np.array([np.any((ang==ang[i])&(fold!=fold[i])) for i in range(len(alld))])
def condrmse(pred,mask):
err=(pred-y)[mask]; g=groups[mask]
return pd.DataFrame({'g':g,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
# worst conditions for gp0.6/ex on covered set
cr=condrmse(0.6*oof_gp+0.4*oof_ex,covered).sort_values(ascending=False)
print('worst 12 (covered, gp0.6/ex):'); print(cr.head(12).round(3).to_string())
print('p90',round(np.percentile(cr,90),3))
" worst 12 (covered, gp0.6/ex): g 0.0_0.0254_71.3 4.288 12.6_0.1524_39.6 4.186 17.4_0.0254_31.7 3.502 7.3_0.2286_71.3 3.244 12.7_0.0254_39.6 3.152 11.2_0.0508_39.6 3.070 17.4_0.0254_39.6 3.065 7.2_0.1524_71.3 2.842 4.2_0.0508_71.3 2.612 -0.0_0.0508_31.7 2.476 8.9_0.1016_71.3 2.475 0.0_0.0508_39.6 2.454 p90 2.476
cd /app && python3 -c "
import numpy as np, pandas as pd
from sklearn.model_selection import GroupKFold
# CV oofs
oof_gp=np.load('/tmp/oof_gp.npy'); oof_ex=np.load('/tmp/oof_ex2.npy'); oof_hgb=np.load('/tmp/oof_hgb.npy'); y=np.load('/tmp/y_all.npy')
# val preds
pgp=np.load('/tmp/pgp.npy'); pe=np.load('/tmp/pe.npy'); ph=np.load('/tmp/ph.npy'); yv=np.load('/tmp/yv.npy')
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv'); alld=pd.concat([train,val],ignore_index=True)
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
def gp_(df): return np.array([f'{a}_{c}_{v}' for a,c,v in zip(np.round(df['attack_angle'].values,1),snap(df['chord_length'],cl),snap(df['free_stream_velocity'],vl))])
groups=gp_(alld); vg=gp_(val)
ang=np.round(alld['attack_angle'].values,1)
fold=np.full(len(alld),-1)
for i,(tr,te) in enumerate(GroupKFold(8).split(np.zeros((len(alld),1)),y,groups)): fold[te]=i
covered=np.array([np.any((ang==ang[i])&(fold!=fold[i])) for i in range(len(alld))])
def m(pred,yy,g,mask=None):
if mask is None: mask=np.ones(len(yy),bool)
err=(pred-yy)[mask]; gg=g[mask]
cr=pd.DataFrame({'g':gg,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return np.sqrt(np.mean(err**2)),np.mean(np.abs(err)),np.percentile(cr,90),np.percentile(cr,95),cr.max()
best=[]
for wg in np.arange(0.4,0.96,0.05):
for we in np.arange(0.0,1-wg+1e-9,0.05):
wh=1-wg-we
if wh< -1e-9: continue
cvp=wg*oof_gp+we*oof_ex+wh*oof_hgb
vp=wg*pgp+we*pe+wh*ph
r1=m(cvp,y,groups,covered); r2=m(vp,yv,vg)
# require both pass-ish; score by worst-case p90
score=max(r1[2],r2[2])
best.append((score,wg,we,wh,r1,r2))
best.sort()
print('top configs by worst-case p90 (cvFiltered, val):')
for s,wg,we,wh,r1,r2 in best[:8]:
print(f'wg={wg:.2f} we={we:.2f} wh={wh:.2f} | CVf rmse={r1[0]:.2f} mae={r1[1]:.2f} p90={r1[2]:.2f} p95={r1[3]:.2f} max={r1[4]:.2f} | VAL rmse={r2[0]:.2f} mae={r2[1]:.2f} p90={r2[2]:.2f} p95={r2[3]:.2f} max={r2[4]:.2f}')
"top configs by worst-case p90 (cvFiltered, val): wg=0.55 we=0.40 wh=0.05 | CVf rmse=1.79 mae=1.29 p90=2.46 p95=3.05 max=4.25 | VAL rmse=1.83 mae=1.38 p90=2.17 p95=3.11 max=4.16 wg=0.60 we=0.40 wh=0.00 | CVf rmse=1.79 mae=1.29 p90=2.48 p95=3.10 max=4.29 | VAL rmse=1.81 mae=1.36 p90=2.12 p95=3.11 max=4.21 wg=0.50 we=0.45 wh=0.05 | CVf rmse=1.79 mae=1.30 p90=2.48 p95=3.04 max=4.25 | VAL rmse=1.84 mae=1.39 p90=2.21 p95=3.09 max=4.16 wg=0.55 we=0.45 wh=-0.00 | CVf rmse=1.79 mae=1.30 p90=2.48 p95=3.06 max=4.29 | VAL rmse=1.82 mae=1.37 p90=2.16 p95=3.09 max=4.21 wg=0.50 we=0.40 wh=0.10 | CVf rmse=1.79 mae=1.30 p90=2.48 p95=3.01 max=4.21 | VAL rmse=1.85 mae=1.40 p90=2.23 p95=3.11 max=4.11 wg=0.40 we=0.45 wh=0.15 | CVf rmse=1.80 mae=1.32 p90=2.49 p95=2.99 max=4.23 | VAL rmse=1.88 mae=1.44 p90=2.33 p95=3.10 max=4.06 wg=0.55 we=0.35 wh=0.10 | CVf rmse=1.78 mae=1.29 p90=2.49 p95=3.04 max=4.21 | VAL rmse=1.83 mae=1.40 p90=2.19 p95=3.12 max=4.12 wg=0.40 we=0.40 wh=0.20 | CVf rmse=1.80 mae=1.33 p90=2.49 p95=2.96 max=4.18 | VAL rmse=1.89 mae=1.45 p90=2.35 p95=3.12 max=4.01
cd /app && cat > /tmp/gpexp.py << 'EOF'
import numpy as np, pandas as pd, warnings, time, sys
warnings.filterwarnings('ignore')
from pathlib import Path
from sklearn.preprocessing import StandardScaler
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel as C, WhiteKernel as W, Matern, RBF
DATA=Path('/app/data')
train=pd.read_csv(DATA/'train.csv'); val=pd.read_csv(DATA/'validation.csv')
TARGET='scaled_sound_pressure_level'
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
vg=np.array([f'{a}_{c}_{v}' for a,c,v in zip(np.round(val['attack_angle'].values,1),snap(val['chord_length'],cl),snap(val['free_stream_velocity'],vl))])
def fe(df,cols):
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
d={}
d['log_freq']=np.log10(freq); d['log_thick']=np.log10(th); d['log_vel']=np.log10(vel); d['log_chord']=np.log10(chord)
d['attack_angle']=a; d['log_St']=np.log10(freq*th/vel)
d['log_rec']=np.log10(vel*chord/1.5e-5); d['log_redelta']=np.log10(vel*th/1.5e-5)
d['mach']=vel/340.3
return pd.DataFrame({c:d[c] for c in cols},index=df.index)
yv=val[TARGET].values; ytr=train[TARGET].values
def metrics(pred):
err=pred-yv; cr=pd.DataFrame({'g':vg,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return (np.sqrt(np.mean(err**2)),np.mean(np.abs(err)),np.percentile(cr,90),np.percentile(cr,95),cr.max())
def run(cols,kernelfn,nu=1.5,tag=''):
Xtr=fe(train,cols); Xv=fe(val,cols); nf=len(cols)
sc=StandardScaler().fit(Xtr); Xs=sc.transform(Xtr); Xvs=sc.transform(Xv)
gp=GaussianProcessRegressor(kernel=kernelfn(nf),alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(Xs,ytr)
r=metrics(gp.predict(Xvs))
print(f'{tag:34s} rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f}')
return gp.predict(Xvs)
base=['log_freq','log_thick','log_vel','log_chord','attack_angle','log_St']
run(base, lambda nf: C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),tag='base matern1.5')
run(base, lambda nf: C(1.0)*Matern([1.0]*nf,nu=2.5)+W(0.1,(1e-3,1.0)),tag='base matern2.5')
run(base+['log_rec','log_redelta'], lambda nf: C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),tag='+reynolds matern1.5')
run(base+['log_rec','log_redelta'], lambda nf: C(1.0)*Matern([1.0]*nf,nu=2.5)+W(0.1,(1e-3,1.0)),tag='+reynolds matern2.5')
# composite: amplitude(RBF long) + shape(Matern on all)
run(base, lambda nf: C(1.0)*RBF([5.0]*nf)+C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),tag='RBF+Matern1.5')
run(base+['log_rec'], lambda nf: C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),tag='+logrec matern1.5')
run(base+['mach'], lambda nf: C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),tag='+mach matern1.5')
EOF
timeout 200 python3 /tmp/gpexp.pybase matern1.5 rmse=1.822 mae=1.339 p90=2.337 p95=3.247 max=4.255 base matern2.5 rmse=1.850 mae=1.386 p90=2.429 p95=3.236 max=4.277 +reynolds matern1.5 rmse=1.837 mae=1.348 p90=2.444 p95=3.228 max=4.268 +reynolds matern2.5 rmse=1.872 mae=1.400 p90=2.583 p95=3.219 max=4.292
cd /app && cat > /tmp/mc.py << 'EOF'
import numpy as np, pandas as pd, warnings, time
warnings.filterwarnings('ignore')
from pathlib import Path
from sklearn.preprocessing import StandardScaler
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel as C, WhiteKernel as W, Matern
from sklearn.ensemble import ExtraTreesRegressor
DATA=Path('/app/data')
alld=pd.concat([pd.read_csv(DATA/'train.csv'),pd.read_csv(DATA/'validation.csv')],ignore_index=True)
TARGET='scaled_sound_pressure_level'
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
ang=np.round(alld['attack_angle'].values,1); cs=snap(alld['chord_length'],cl); vs=snap(alld['free_stream_velocity'],vl)
cond=np.array([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)])
uconds=np.array(sorted(set(cond)))
cond_ang={cc:float(cc.split('_')[0]) for cc in uconds}
def fe(df):
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float)
chord=df['chord_length'].astype(float); vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
g=pd.DataFrame(index=df.index); g['log_freq']=np.log10(freq); g['log_thick']=np.log10(th)
g['log_vel']=np.log10(vel); g['log_chord']=np.log10(chord); g['attack_angle']=a; g['log_St']=np.log10(freq*th/vel)
e=g.copy(); e['frequency']=freq; e['chord_length']=chord; e['free_stream_velocity']=vel; e['thickness']=th; e['freq_thick']=freq*th
return g,e
Gall,Eall=fe(alld); y=alld[TARGET].values; nf=Gall.shape[1]
def condmetrics(pred,yy,g):
err=pred-yy; cr=pd.DataFrame({'g':g,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return np.sqrt(np.mean(err**2)),np.mean(np.abs(err)),np.percentile(cr,90),np.percentile(cr,95),cr.max()
rng=np.random.default_rng(12345)
# angle -> list of conditions
from collections import defaultdict
ang2c=defaultdict(list)
for cc in uconds: ang2c[cond_ang[cc]].append(cc)
res=[]
NT=12
for t in range(NT):
# hold out ~20 conditions, but only from angles that have >=2 conditions (so training keeps angle coverage)
hold=[]
for a,ccs in ang2c.items():
if len(ccs)>=2:
k=rng.integers(0,len(ccs)) # hold out up to len-1
sel=rng.choice(ccs,size=min(k, len(ccs)-1),replace=False) if len(ccs)>1 else []
hold.extend(sel)
hold=list(rng.choice(hold,size=min(20,len(hold)),replace=False))
holdset=set(hold)
te=np.isin(cond,list(holdset)); tr=~te
sc=StandardScaler().fit(Gall[tr]); Xs=sc.transform(Gall[tr]); Xts=sc.transform(Gall[te])
gp=GaussianProcessRegressor(kernel=C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(Xs,y[tr])
pgp=gp.predict(Xts)
ex=ExtraTreesRegressor(n_estimators=600,random_state=0,n_jobs=-1).fit(Eall[tr],y[tr]); pex=ex.predict(Eall[te])
pred=0.6*pgp+0.4*pex
r=condmetrics(pred,y[te],cond[te])
res.append(r); print(f'trial {t} nhold={len(holdset)} rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f}',flush=True)
res=np.array(res)
print('MEAN ',np.round(res.mean(0),3))
print('P90pct',np.round(np.percentile(res,90,axis=0),3))
print('MAX ',np.round(res.max(0),3))
print('frac pass p90<=2.35:',np.mean(res[:,2]<=2.35))
EOF
nohup python3 /tmp/mc.py > /tmp/mc.log 2>&1 &
echo "started $!"started 1269
sleep 90 && cat /tmp/mc.log
trial 0 nhold=20 rmse=1.764 mae=1.381 p90=2.418 p95=2.513 max=2.730 trial 1 nhold=20 rmse=2.023 mae=1.429 p90=3.245 p95=3.311 max=4.388 trial 2 nhold=20 rmse=2.207 mae=1.622 p90=3.240 p95=4.249 max=4.436 trial 3 nhold=20 rmse=1.759 mae=1.291 p90=2.512 p95=2.627 max=3.048 trial 4 nhold=20 rmse=1.904 mae=1.455 p90=2.804 p95=3.123 max=3.480 trial 5 nhold=20 rmse=1.825 mae=1.434 p90=2.433 p95=2.500 max=3.126
sleep 60 && cat /tmp/mc.log
trial 0 nhold=20 rmse=1.764 mae=1.381 p90=2.418 p95=2.513 max=2.730 trial 1 nhold=20 rmse=2.023 mae=1.429 p90=3.245 p95=3.311 max=4.388 trial 2 nhold=20 rmse=2.207 mae=1.622 p90=3.240 p95=4.249 max=4.436 trial 3 nhold=20 rmse=1.759 mae=1.291 p90=2.512 p95=2.627 max=3.048 trial 4 nhold=20 rmse=1.904 mae=1.455 p90=2.804 p95=3.123 max=3.480 trial 5 nhold=20 rmse=1.825 mae=1.434 p90=2.433 p95=2.500 max=3.126 trial 6 nhold=20 rmse=1.705 mae=1.207 p90=2.389 p95=2.873 max=3.255 trial 7 nhold=20 rmse=1.881 mae=1.374 p90=2.465 p95=2.629 max=3.577 trial 8 nhold=20 rmse=1.889 mae=1.441 p90=2.470 p95=2.679 max=2.698 trial 9 nhold=20 rmse=1.857 mae=1.319 p90=2.215 p95=2.887 max=4.422 trial 10 nhold=20 rmse=1.847 mae=1.302 p90=2.824 p95=3.363 max=4.240 trial 11 nhold=20 rmse=1.757 mae=1.291 p90=2.633 p95=2.922 max=2.990 MEAN [1.868 1.379 2.637 2.973 3.532] P90pct [2.011 1.454 3.199 3.358 4.419] MAX [2.207 1.622 3.245 4.249 4.436] frac pass p90<=2.35: 0.08333333333333333
cd /app && python3 -c "
import numpy as np, pandas as pd
from pathlib import Path
alld=pd.concat([pd.read_csv('/app/data/train.csv'),pd.read_csv('/app/data/validation.csv')],ignore_index=True)
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
ang=np.round(alld['attack_angle'].values,1); cs=snap(alld['chord_length'],cl); vs=snap(alld['free_stream_velocity'],vl)
cond=np.array([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)])
alld['cond']=cond; alld['lf']=np.log10(alld['frequency'])
y='scaled_sound_pressure_level'
resid=[]
for c,sub in alld.groupby('cond'):
if len(sub)<5: continue
x=sub['lf'].values; yy=sub[y].values
# fit cubic poly
for deg in [3]:
co=np.polyfit(x,yy,deg); pr=np.polyval(co,x)
resid.append((c,len(sub),np.sqrt(np.mean((yy-pr)**2))))
r=pd.DataFrame(resid,columns=['cond','n','rmse_within'])
print('within-condition smooth-fit (cubic) residual RMSE:')
print(r['rmse_within'].describe())
print('worst:'); print(r.sort_values('rmse_within',ascending=False).head(8).to_string(index=False))
"within-condition smooth-fit (cubic) residual RMSE:
count 94.000000
mean 0.796232
std 0.725420
min 0.017303
25% 0.348148
50% 0.614368
75% 1.021066
max 4.045634
Name: rmse_within, dtype: float64
worst:
cond n rmse_within
17.4_0.0254_39.6 15 4.045634
17.4_0.0254_31.7 15 3.622839
22.2_0.0254_39.6 15 3.200307
12.3_0.1016_71.3 16 2.495427
15.6_0.1016_71.3 14 2.171586
12.3_0.1016_55.5 16 2.007536
7.2_0.1524_71.3 16 1.777851
7.2_0.1524_39.6 17 1.680399cd /app && python3 -c "
import numpy as np, pandas as pd
alld=pd.concat([pd.read_csv('/app/data/train.csv'),pd.read_csv('/app/data/validation.csv')],ignore_index=True)
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
ang=np.round(alld['attack_angle'].values,1); cs=snap(alld['chord_length'],cl); vs=snap(alld['free_stream_velocity'],vl)
alld['cond']=[f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)]; alld['lf']=np.log10(alld['frequency'])
sub=alld[alld['cond']=='17.4_0.0254_39.6'].sort_values('frequency')
print(sub[['frequency','scaled_sound_pressure_level']].to_string(index=False))
x=sub['lf'].values; yy=sub['scaled_sound_pressure_level'].values
for deg in [3,4,5,6]:
pr=np.polyval(np.polyfit(x,yy,deg),x); print(f'deg{deg} resid rmse={np.sqrt(np.mean((yy-pr)**2)):.3f}')
"frequency scaled_sound_pressure_level 200.199479 114.496812 249.955871 115.353270 314.603028 115.939956 399.320008 116.261990 499.364466 118.166382 629.878625 124.975834 800.787358 135.688462 1001.658142 138.661407 1251.856000 131.892239 1600.884394 128.217284 1998.676495 127.083611 2496.154736 124.292580 3144.870197 123.476696 3996.452931 122.466916 5001.540499 119.192741 deg3 resid rmse=4.046 deg4 resid rmse=2.863 deg5 resid rmse=2.849 deg6 resid rmse=1.776
cd /app && cat > /tmp/mc2.py << 'EOF'
import numpy as np, pandas as pd, warnings
warnings.filterwarnings('ignore')
from pathlib import Path
from sklearn.preprocessing import StandardScaler
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel as C, WhiteKernel as W, Matern
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
from collections import defaultdict
alld=pd.concat([pd.read_csv('/app/data/train.csv'),pd.read_csv('/app/data/validation.csv')],ignore_index=True)
TARGET='scaled_sound_pressure_level'
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
ang=np.round(alld['attack_angle'].values,1); cs=snap(alld['chord_length'],cl); vs=snap(alld['free_stream_velocity'],vl)
cond=np.array([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)])
uconds=sorted(set(cond)); cond_ang={cc:float(cc.split('_')[0]) for cc in uconds}
def fe(df):
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float); chord=df['chord_length'].astype(float)
vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
g=pd.DataFrame(index=df.index); g['log_freq']=np.log10(freq); g['log_thick']=np.log10(th)
g['log_vel']=np.log10(vel); g['log_chord']=np.log10(chord); g['attack_angle']=a; g['log_St']=np.log10(freq*th/vel)
e=g.copy(); e['frequency']=freq; e['chord_length']=chord; e['free_stream_velocity']=vel; e['thickness']=th; e['freq_thick']=freq*th
return g,e
Gall,Eall=fe(alld); y=alld[TARGET].values; nf=Gall.shape[1]
ang2c=defaultdict(list)
for cc in uconds: ang2c[cond_ang[cc]].append(cc)
def condrmse(pred,yy,g):
cr=pd.DataFrame({'g':g,'e':pred-yy}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return cr
rng=np.random.default_rng(7)
weights={'gp':(1,0,0),'0.7gp0.3ex':(.7,.3,0),'0.6gp0.4ex':(.6,.4,0),'0.5gp0.5ex':(.5,.5,0),
'0.6gp0.25ex0.15rf':(.6,.25,.15),'0.5gp0.3ex0.2rf':(.5,.3,.2),'0.7gp0.15ex0.15rf':(.7,.15,.15)}
agg={k:[] for k in weights}
NT=15
for t in range(NT):
hold=[]
for a,ccs in ang2c.items():
if len(ccs)>=2:
kmax=len(ccs)-1; k=rng.integers(1,kmax+1)
sel=rng.choice(ccs,size=min(k,kmax),replace=False); hold.extend(sel)
hold=list(rng.choice(hold,size=min(20,len(hold)),replace=False)); holdset=set(hold)
te=np.isin(cond,list(holdset)); tr=~te
sc=StandardScaler().fit(Gall[tr])
gp=GaussianProcessRegressor(kernel=C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(sc.transform(Gall[tr]),y[tr])
pgp=gp.predict(sc.transform(Gall[te]))
pex=ExtraTreesRegressor(n_estimators=600,random_state=0,n_jobs=-1).fit(Eall[tr],y[tr]).predict(Eall[te])
prf=RandomForestRegressor(n_estimators=600,random_state=0,n_jobs=-1).fit(Eall[tr],y[tr]).predict(Eall[te])
yt=y[te]; gt=cond[te]
for k,(a,b,c) in weights.items():
pred=a*pgp+b*pex+c*prf; err=pred-yt
cr=condrmse(pred,yt,gt)
agg[k].append((np.sqrt(np.mean(err**2)),np.mean(np.abs(err)),np.percentile(cr,90),np.percentile(cr,95),cr.max()))
print('trial',t,'done',flush=True)
print(f'{'weight':20s} {'rmse':>5s} {'mae':>5s} {'p90':>5s} {'p95':>5s} {'max':>5s} passp90')
for k in weights:
r=np.array(agg[k]); mean=r.mean(0)
print(f'{k:20s} {mean[0]:5.2f} {mean[1]:5.2f} {mean[2]:5.2f} {mean[3]:5.2f} {mean[4]:5.2f} {np.mean(r[:,2]<=2.35):.2f} p90max={r[:,2].max():.2f}')
EOF
nohup python3 /tmp/mc2.py > /tmp/mc2.log 2>&1 &
echo "started $!"started 1428
sleep 150 && cat /tmp/mc2.log
trial 0 done trial 1 done trial 2 done trial 3 done trial 4 done trial 5 done trial 6 done trial 7 done trial 8 done
sleep 120 && cat /tmp/mc2.log
trial 0 done trial 1 done trial 2 done trial 3 done trial 4 done trial 5 done trial 6 done trial 7 done trial 8 done trial 9 done trial 10 done trial 11 done trial 12 done trial 13 done trial 14 done weight rmse mae p90 p95 max passp90 gp 1.95 1.40 2.91 3.16 3.54 0.13 p90max=4.06 0.7gp0.3ex 1.90 1.37 2.77 3.05 3.59 0.13 p90max=4.27 0.6gp0.4ex 1.90 1.38 2.76 3.03 3.63 0.33 p90max=4.42 0.5gp0.5ex 1.92 1.40 2.76 3.04 3.67 0.47 p90max=4.57 0.6gp0.25ex0.15rf 1.90 1.38 2.74 3.03 3.60 0.47 p90max=4.41 0.5gp0.3ex0.2rf 1.92 1.40 2.73 3.03 3.64 0.47 p90max=4.56 0.7gp0.15ex0.15rf 1.89 1.37 2.74 3.04 3.56 0.27 p90max=4.27
cd /app && cat > /tmp/trend.py << 'EOF'
import numpy as np, pandas as pd, warnings
warnings.filterwarnings('ignore')
from sklearn.preprocessing import StandardScaler, SplineTransformer
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel as C, WhiteKernel as W, Matern
from sklearn.ensemble import ExtraTreesRegressor
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv')
T='scaled_sound_pressure_level'
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
vg=np.array([f'{a}_{c}_{v}' for a,c,v in zip(np.round(val['attack_angle'].values,1),snap(val['chord_length'],cl),snap(val['free_stream_velocity'],vl))])
def fe(df):
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float); chord=df['chord_length'].astype(float)
vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
g=pd.DataFrame(index=df.index); g['log_freq']=np.log10(freq); g['log_thick']=np.log10(th)
g['log_vel']=np.log10(vel); g['log_chord']=np.log10(chord); g['attack_angle']=a; g['log_St']=np.log10(freq*th/vel)
return g
Gtr=fe(train); Gv=fe(val); ytr=train[T].values; yv=val[T].values; nf=Gtr.shape[1]
def metrics(pred):
err=pred-yv; cr=pd.DataFrame({'g':vg,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return (np.sqrt(np.mean(err**2)),np.mean(np.abs(err)),np.percentile(cr,90),np.percentile(cr,95),cr.max())
def pr(n,r):
f=' PASS' if r[0]<=2.25 and r[1]<=1.55 and r[2]<=2.35 and r[3]<=4.75 and r[4]<=5.60 else ''
print(f'{n:26s} rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f}{f}')
sc=StandardScaler().fit(Gtr); Xs=sc.transform(Gtr); Xvs=sc.transform(Gv)
def fitgp(ytr_):
return GaussianProcessRegressor(kernel=C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(Xs,ytr_)
# plain GP
pgp=fitgp(ytr).predict(Xvs); pr('plain GP',metrics(pgp))
# Ridge-trend + GP residual (spline ridge trend)
trend=make_pipeline(SplineTransformer(n_knots=6,degree=3),Ridge(alpha=1.0)).fit(Gtr,ytr)
rtr=ytr-trend.predict(Gtr); gpr=fitgp(rtr); presid=trend.predict(Gv)+gpr.predict(Xvs)
pr('splineRidge-trend+GPres',metrics(presid))
# linear ridge trend
trend2=make_pipeline(StandardScaler(),Ridge(alpha=1.0)).fit(Gtr,ytr)
rtr2=ytr-trend2.predict(Gtr); gpr2=fitgp(rtr2); presid2=trend2.predict(Gv)+gpr2.predict(Xvs)
pr('linRidge-trend+GPres',metrics(presid2))
# blends with extra
pex=ExtraTreesRegressor(n_estimators=700,random_state=0,n_jobs=-1).fit(Gtr.assign(ft=Gtr['log_freq']),ytr).predict(Gv.assign(ft=Gv['log_freq']))
pr('0.6 GPres(spl)+0.4ex',metrics(0.6*presid+0.4*pex))
pr('0.6 plainGP+0.4ex',metrics(0.6*pgp+0.4*pex))
EOF
timeout 200 python3 /tmp/trend.pyplain GP rmse=1.822 mae=1.339 p90=2.337 p95=3.247 max=4.255 PASS splineRidge-trend+GPres rmse=1.777 mae=1.271 p90=2.075 p95=3.157 max=4.314 PASS linRidge-trend+GPres rmse=1.825 mae=1.320 p90=2.582 p95=3.233 max=4.182 0.6 GPres(spl)+0.4ex rmse=1.826 mae=1.352 p90=2.281 p95=3.225 max=4.130 PASS 0.6 plainGP+0.4ex rmse=1.829 mae=1.372 p90=2.118 p95=3.242 max=4.095 PASS
cd /app && cat > /tmp/mc3.py << 'EOF'
import numpy as np, pandas as pd, warnings
warnings.filterwarnings('ignore')
from sklearn.preprocessing import StandardScaler, SplineTransformer
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel as C, WhiteKernel as W, Matern
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
from collections import defaultdict
alld=pd.concat([pd.read_csv('/app/data/train.csv'),pd.read_csv('/app/data/validation.csv')],ignore_index=True)
T='scaled_sound_pressure_level'
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
ang=np.round(alld['attack_angle'].values,1); cs=snap(alld['chord_length'],cl); vs=snap(alld['free_stream_velocity'],vl)
cond=np.array([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)]); uconds=sorted(set(cond))
def fe(df):
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float); chord=df['chord_length'].astype(float)
vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
g=pd.DataFrame(index=df.index); g['log_freq']=np.log10(freq); g['log_thick']=np.log10(th)
g['log_vel']=np.log10(vel); g['log_chord']=np.log10(chord); g['attack_angle']=a; g['log_St']=np.log10(freq*th/vel)
e=g.copy(); e['frequency']=freq; e['chord_length']=chord; e['free_stream_velocity']=vel; e['thickness']=th; e['freq_thick']=freq*th
return g,e
Gall,Eall=fe(alld); y=alld[T].values; nf=Gall.shape[1]
ang2c=defaultdict(list)
for cc in uconds: ang2c[float(cc.split('_')[0])].append(cc)
def cr_(pred,yy,g): return pd.DataFrame({'g':g,'e':pred-yy}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
rng=np.random.default_rng(7)
methods=['gp','tgp','0.6tgp0.4ex','0.6tgp0.25ex0.15rf','0.5tgp0.3ex0.2rf','0.6gp0.4ex']
agg={k:[] for k in methods}
NT=15
for t in range(NT):
hold=[]
for a,ccs in ang2c.items():
if len(ccs)>=2:
kmax=len(ccs)-1; k=rng.integers(1,kmax+1); hold.extend(rng.choice(ccs,size=min(k,kmax),replace=False))
hold=list(rng.choice(hold,size=min(20,len(hold)),replace=False)); te=np.isin(cond,hold); tr=~te
sc=StandardScaler().fit(Gall[tr]); Xs=sc.transform(Gall[tr]); Xts=sc.transform(Gall[te])
def fitgp(yy): return GaussianProcessRegressor(kernel=C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(Xs,yy)
pgp=fitgp(y[tr]).predict(Xts)
trend=make_pipeline(SplineTransformer(n_knots=6,degree=3),Ridge(alpha=1.0)).fit(Gall[tr],y[tr])
rtr=y[tr]-trend.predict(Gall[tr]); ptgp=trend.predict(Gall[te])+fitgp(rtr).predict(Xts)
pex=ExtraTreesRegressor(n_estimators=600,random_state=0,n_jobs=-1).fit(Eall[tr],y[tr]).predict(Eall[te])
prf=RandomForestRegressor(n_estimators=600,random_state=0,n_jobs=-1).fit(Eall[tr],y[tr]).predict(Eall[te])
yt=y[te]; gt=cond[te]
preds={'gp':pgp,'tgp':ptgp,'0.6tgp0.4ex':.6*ptgp+.4*pex,'0.6tgp0.25ex0.15rf':.6*ptgp+.25*pex+.15*prf,
'0.5tgp0.3ex0.2rf':.5*ptgp+.3*pex+.2*prf,'0.6gp0.4ex':.6*pgp+.4*pex}
for k,p in preds.items():
cr=cr_(p,yt,gt); agg[k].append((np.sqrt(np.mean((p-yt)**2)),np.mean(np.abs(p-yt)),np.percentile(cr,90),np.percentile(cr,95),cr.max()))
print('trial',t,flush=True)
print(f'{'method':22s} {'rmse':>5s}{'mae':>6s}{'p90':>6s}{'p95':>6s}{'max':>6s} passALL')
for k in methods:
r=np.array(agg[k]); m=r.mean(0)
pa=np.mean((r[:,0]<=2.25)&(r[:,1]<=1.55)&(r[:,2]<=2.35)&(r[:,3]<=4.75)&(r[:,4]<=5.60))
print(f'{k:22s} {m[0]:5.2f}{m[1]:6.2f}{m[2]:6.2f}{m[3]:6.2f}{m[4]:6.2f} pass={pa:.2f} p90max={r[:,2].max():.2f} maemax={r[:,1].max():.2f}')
EOF
nohup python3 /tmp/mc3.py > /tmp/mc3.log 2>&1 &
echo "started $!"started 1773
sleep 180 && cat /tmp/mc3.log
trial 0 trial 1 trial 2 trial 3 trial 4 trial 5
sleep 220 && cat /tmp/mc3.log
trial 0 trial 1 trial 2 trial 3 trial 4 trial 5 trial 6 trial 7 trial 8 trial 9 trial 10 trial 11 trial 12
sleep 75 && cat /tmp/mc3.log
trial 0 trial 1 trial 2 trial 3 trial 4 trial 5 trial 6 trial 7 trial 8 trial 9 trial 10 trial 11 trial 12 trial 13 trial 14 method rmse mae p90 p95 max passALL gp 1.95 1.40 2.91 3.16 3.54 pass=0.13 p90max=4.06 maemax=1.76 tgp 1.97 1.40 2.88 3.28 3.65 pass=0.27 p90max=4.57 maemax=2.02 0.6tgp0.4ex 1.93 1.39 2.79 3.12 3.65 pass=0.33 p90max=4.34 maemax=1.97 0.6tgp0.25ex0.15rf 1.92 1.39 2.79 3.11 3.63 pass=0.40 p90max=4.29 maemax=1.97 0.5tgp0.3ex0.2rf 1.94 1.41 2.79 3.09 3.66 pass=0.40 p90max=4.26 maemax=1.97 0.6gp0.4ex 1.90 1.38 2.76 3.03 3.63 pass=0.33 p90max=4.42 maemax=1.81
cd /app && cat > /tmp/mc4.py << 'EOF'
import numpy as np, pandas as pd, warnings
warnings.filterwarnings('ignore')
from sklearn.preprocessing import StandardScaler
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import ConstantKernel as C, WhiteKernel as W, Matern
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
alld=pd.concat([pd.read_csv('/app/data/train.csv'),pd.read_csv('/app/data/validation.csv')],ignore_index=True)
T='scaled_sound_pressure_level'
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
ang=np.round(alld['attack_angle'].values,1); cs=snap(alld['chord_length'],cl); vs=snap(alld['free_stream_velocity'],vl)
cond=np.array([f'{a}_{c}_{v}' for a,c,v in zip(ang,cs,vs)]); uconds=np.array(sorted(set(cond)))
cond_ang=np.array([float(c.split('_')[0]) for c in uconds])
def fe(df):
freq=df['frequency'].astype(float); a=df['attack_angle'].astype(float); chord=df['chord_length'].astype(float)
vel=df['free_stream_velocity'].astype(float); th=df['suction_side_displacement_thickness'].astype(float)
g=pd.DataFrame(index=df.index); g['log_freq']=np.log10(freq); g['log_thick']=np.log10(th)
g['log_vel']=np.log10(vel); g['log_chord']=np.log10(chord); g['attack_angle']=a; g['log_St']=np.log10(freq*th/vel)
e=g.copy(); e['frequency']=freq; e['chord_length']=chord; e['free_stream_velocity']=vel; e['thickness']=th; e['freq_thick']=freq*th
return g,e
Gall,Eall=fe(alld); y=alld[T].values; nf=Gall.shape[1]
def cr_(pred,yy,g): return pd.DataFrame({'g':g,'e':pred-yy}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
rng=np.random.default_rng(2024)
methods=['gp','0.6gp0.4ex','0.6gp0.25ex0.15rf','0.5gp0.3ex0.2rf']
agg={k:[] for k in methods}
NT=25
for t in range(NT):
perm=rng.permutation(len(uconds)); holdidx=set(perm[:20].tolist())
# ensure every angle still present in training: if an angle fully held out, release one
for a in np.unique(cond_ang):
idxs=np.where(cond_ang==a)[0]
if all(i in holdidx for i in idxs):
holdidx.discard(idxs[rng.integers(len(idxs))])
hold=set(uconds[list(holdidx)]); te=np.isin(cond,list(hold)); tr=~te
sc=StandardScaler().fit(Gall[tr]); Xs=sc.transform(Gall[tr]); Xts=sc.transform(Gall[te])
pgp=GaussianProcessRegressor(kernel=C(1.0)*Matern([1.0]*nf,nu=1.5)+W(0.1,(1e-3,1.0)),alpha=1e-6,normalize_y=True,n_restarts_optimizer=0,random_state=0).fit(Xs,y[tr]).predict(Xts)
pex=ExtraTreesRegressor(n_estimators=600,random_state=0,n_jobs=-1).fit(Eall[tr],y[tr]).predict(Eall[te])
prf=RandomForestRegressor(n_estimators=600,random_state=0,n_jobs=-1).fit(Eall[tr],y[tr]).predict(Eall[te])
yt=y[te]; gt=cond[te]
preds={'gp':pgp,'0.6gp0.4ex':.6*pgp+.4*pex,'0.6gp0.25ex0.15rf':.6*pgp+.25*pex+.15*prf,'0.5gp0.3ex0.2rf':.5*pgp+.3*pex+.2*prf}
for k,p in preds.items():
cr=cr_(p,yt,gt); agg[k].append((np.sqrt(np.mean((p-yt)**2)),np.mean(np.abs(p-yt)),np.percentile(cr,90),np.percentile(cr,95),cr.max()))
print('trial',t,flush=True)
print(f'{'method':20s}{'rmse':>6s}{'mae':>6s}{'p90':>6s}{'p95':>6s}{'max':>6s} metrics: mean(q90)')
for k in methods:
r=np.array(agg[k]); m=r.mean(0); q=np.percentile(r,90,axis=0)
pa=np.mean((r[:,0]<=2.25)&(r[:,1]<=1.55)&(r[:,2]<=2.35)&(r[:,3]<=4.75)&(r[:,4]<=5.60))
print(f'{k:20s}{m[0]:6.2f}{m[1]:6.2f}{m[2]:6.2f}{m[3]:6.2f}{m[4]:6.2f} passALL={pa:.2f}')
print(f'{' q90:':20s}{q[0]:6.2f}{q[1]:6.2f}{q[2]:6.2f}{q[3]:6.2f}{q[4]:6.2f} (90th pct across trials; want <= targets)')
EOF
nohup python3 /tmp/mc4.py > /tmp/mc4.log 2>&1 &
echo started $!started 2106
command: create
path: /app/train_and_predict.py
file_text: #!/usr/bin/env python3
"""Airfoil self-noise regression surrogate.
The benchmark holds out *complete* aerodynamic condition groups -- a condition
being the tuple (attack_angle, chord_length, free_stream_velocity). The hidden
evaluation therefore tests generalisation to unseen physical regimes rather than
interpolation between nearby rows. The scoring penalises both global error and
per-condition failures (p90 / p95 / max condition RMSE).
Modelling approach
------------------
The airfoil self-noise spectrum (Brooks-Pope-Marcolini physics) is a smooth,
unimodal function of a handful of log-scaled similarity variables (Strouhal
number, displacement thickness, Mach/velocity, chord, angle). A Gaussian
Process with an anisotropic Matern(nu=1.5) kernel models this smooth surface and
generalises gracefully to unseen conditions; it is the backbone of the model.
Tree ensembles (ExtraTrees, RandomForest) are lower-variance, data-adaptive
learners whose errors are partly complementary to the GP's. Averaging the GP
with the two forests reduces per-condition bias and trims the worst-case
condition errors, which is exactly what the grouped metrics reward.
The workflow trains purely on the provided labelled public data
(train.csv + validation.csv) and writes predictions for the grader-staged
feature rows in /app/data/test_features.csv.
"""
from __future__ import annotations
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import (
ConstantKernel,
Matern,
WhiteKernel,
)
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",
]
# Blend weights (GP backbone + two complementary tree ensembles). Chosen via
# grouped cross-validation and Monte-Carlo condition-holdout simulations that
# mimic the hidden grouped split.
W_GP = 0.60
W_EXTRA = 0.25
W_RF = 0.15
# Physically plausible clamp for the calibrated SPL target (dB). Training
# labels live in ~105-140 dB; the wide guard only trips on pathological
# extrapolation and keeps every prediction finite without flattening outputs.
SPL_LOW, SPL_HIGH = 90.0, 155.0
def _col(frame: pd.DataFrame, name: str) -> pd.Series:
return pd.to_numeric(frame[name], errors="coerce").astype(float)
def gp_features(frame: pd.DataFrame) -> pd.DataFrame:
"""Log-scaled similarity variables that make the noise surface smooth."""
freq = _col(frame, "frequency")
angle = _col(frame, "attack_angle")
chord = _col(frame, "chord_length")
velocity = _col(frame, "free_stream_velocity")
thickness = _col(frame, "suction_side_displacement_thickness")
out = pd.DataFrame(index=frame.index)
out["log_freq"] = np.log10(freq)
out["log_thick"] = np.log10(thickness)
out["log_vel"] = np.log10(velocity)
out["log_chord"] = np.log10(chord)
out["attack_angle"] = angle
# Strouhal-like collapse variable f * delta* / U (drives the spectral shape)
out["log_strouhal"] = np.log10(freq * thickness / velocity)
return out
def tree_features(frame: pd.DataFrame) -> pd.DataFrame:
"""Smooth log features augmented with raw values for the tree ensembles."""
out = gp_features(frame)
out["frequency"] = _col(frame, "frequency")
out["chord_length"] = _col(frame, "chord_length")
out["free_stream_velocity"] = _col(frame, "free_stream_velocity")
out["thickness"] = _col(frame, "suction_side_displacement_thickness")
out["freq_thick"] = _col(frame, "frequency") * _col(
frame, "suction_side_displacement_thickness"
)
…[truncated]Existing file was successfully overwritten at /app/train_and_predict.py
sleep 30 && cat /tmp/mc4.log
trial 0 trial 1
sleep 240 && cat /tmp/mc4.log
trial 0 trial 1 trial 2 trial 3 trial 4 trial 5 trial 6 trial 7 trial 8 trial 9 trial 10 trial 11 trial 12 trial 13 trial 14 trial 15 trial 16
sleep 200 && cat /tmp/mc4.log
trial 0 trial 1 trial 2 trial 3 trial 4 trial 5 trial 6 trial 7 trial 8 trial 9 trial 10 trial 11 trial 12 trial 13 trial 14 trial 15 trial 16 trial 17 trial 18 trial 19 trial 20 trial 21 trial 22 trial 23 trial 24 method rmse mae p90 p95 max metrics: mean(q90) gp 1.89 1.36 2.73 3.10 3.74 passALL=0.28 q90: 2.26 1.71 3.39 4.04 4.80 (90th pct across trials; want <= targets) 0.6gp0.4ex 1.83 1.36 2.56 2.89 3.50 passALL=0.40 q90: 2.10 1.62 3.02 3.48 4.75 (90th pct across trials; want <= targets) 0.6gp0.25ex0.15rf 1.84 1.37 2.57 2.85 3.48 passALL=0.40 q90: 2.11 1.61 3.05 3.46 4.75 (90th pct across trials; want <= targets) 0.5gp0.3ex0.2rf 1.86 1.40 2.57 2.84 3.47 passALL=0.40 q90: 2.11 1.61 3.03 3.40 4.80 (90th pct across trials; want <= targets)
cd /app && rm -f output/predictions.csv && time python3 train_and_predict.py 2>&1 | tail -5 && echo "--- OUTPUT ---" && head -5 output/predictions.csv && wc -l output/predictions.csv
--- OUTPUT --- sample_id,predicted_scaled_sound_pressure_level af_val_0000,115.97350259447092 af_val_0001,124.21423378377635 af_val_0002,133.44443200877387 af_val_0003,123.98683979189822 41 output/predictions.csv [stderr] real 0m30.859s user 0m32.630s sys 0m1.770s
cd /app && cat > /tmp/verify.py << 'EOF'
import numpy as np, pandas as pd, warnings, importlib.util
warnings.filterwarnings('ignore')
spec=importlib.util.spec_from_file_location('tp','/app/train_and_predict.py')
tp=importlib.util.module_from_spec(spec); spec.loader.exec_module(tp)
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv')
T='scaled_sound_pressure_level'
# Train on TRAIN only, predict VAL (faithful proxy), using production feature/model code
y=train[T].to_numpy(float)
from sklearn.preprocessing import StandardScaler
Xg=tp.gp_features(train); Xgv=tp.gp_features(val)
sc=StandardScaler().fit(Xg.to_numpy()); gp=tp.build_gp().fit(sc.transform(Xg.to_numpy()),y)
pgp=gp.predict(sc.transform(Xgv.to_numpy()))
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
Xt=tp.tree_features(train); Xtv=tp.tree_features(val)
pex=ExtraTreesRegressor(n_estimators=800,random_state=0,n_jobs=-1).fit(Xt,y).predict(Xtv)
prf=RandomForestRegressor(n_estimators=800,random_state=0,n_jobs=-1).fit(Xt,y).predict(Xtv)
pred=tp.W_GP*pgp+tp.W_EXTRA*pex+tp.W_RF*prf
pred=np.clip(pred,tp.SPL_LOW,tp.SPL_HIGH)
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
vg=np.array([f'{a}_{c}_{v}' for a,c,v in zip(np.round(val['attack_angle'].values,1),snap(val['chord_length'],cl),snap(val['free_stream_velocity'],vl))])
yv=val[T].values; err=pred-yv
cr=pd.DataFrame({'g':vg,'e':err}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
print('=== PRODUCTION blend, train->val (faithful proxy) ===')
print(f'rmse={np.sqrt(np.mean(err**2)):.3f} (<=2.25) mae={np.mean(np.abs(err)):.3f} (<=1.55)')
print(f'p90={np.percentile(cr,90):.3f} (<=2.35) p95={np.percentile(cr,95):.3f} (<=4.75) max={cr.max():.3f} (<=5.60)')
allpass = np.sqrt(np.mean(err**2))<=2.25 and np.mean(np.abs(err))<=1.55 and np.percentile(cr,90)<=2.35 and np.percentile(cr,95)<=4.75 and cr.max()<=5.60
print('ALL PASS:', allpass)
EOF
python3 /tmp/verify.py=== PRODUCTION blend, train->val (faithful proxy) === rmse=1.842 (<=2.25) mae=1.392 (<=1.55) p90=2.209 (<=2.35) p95=3.203 (<=4.75) max=4.105 (<=5.60) ALL PASS: True
cd /app && cp data/test_features.csv /tmp/test_features.backup && md5sum data/test_features.csv > /tmp/tf.md5
# Build a simulated hidden test: take validation rows, perturb conditions slightly (new regimes), new sample_ids, drop target
python3 -c "
import pandas as pd, numpy as np
val=pd.read_csv('/app/data/validation.csv').drop(columns=['scaled_sound_pressure_level'])
rng=np.random.default_rng(99)
val=val.copy()
# perturb to create 'unseen' conditions and add a couple extreme rows
for c in ['frequency','attack_angle','chord_length','free_stream_velocity','suction_side_displacement_thickness']:
val[c]=val[c]*(1+rng.normal(0,0.01,len(val)))
val['sample_id']=['hid_%04d'%i for i in range(len(val))]
# duplicate-id check: ensure unique; shuffle row order
val=val.sample(frac=1.0,random_state=1).reset_index(drop=True)
val.to_csv('/app/data/test_features.csv',index=False)
print('staged mock hidden rows:',len(val))
"
# grader discards any existing predictions then re-runs the script
rm -f output/predictions.csv
python3 train_and_predict.py
echo "--- validate output ---"
python3 -c "
import pandas as pd, numpy as np
test=pd.read_csv('/app/data/test_features.csv'); pred=pd.read_csv('/app/output/predictions.csv')
print('columns:', list(pred.columns))
assert list(pred.columns)==['sample_id','predicted_scaled_sound_pressure_level'], 'bad columns'
assert len(pred)==len(test), f'row count {len(pred)} vs {len(test)}'
assert set(pred['sample_id'].astype(str))==set(test['sample_id'].astype(str)), 'sample_id mismatch'
assert pred['sample_id'].nunique()==len(pred), 'dup ids'
v=pred['predicted_scaled_sound_pressure_level'].to_numpy(float)
assert np.all(np.isfinite(v)), 'non-finite'
assert v.std()>1.0, 'looks constant'
print('rows:',len(pred),'pred range: %.2f..%.2f'%(v.min(),v.max()),'std=%.2f'%v.std())
print('ALL OUTPUT CHECKS PASSED')
"
# restore the real input file exactly and verify checksum
cp /tmp/test_features.backup data/test_features.csv
md5sum -c /tmp/tf.md5staged mock hidden rows: 201 --- validate output --- columns: ['sample_id', 'predicted_scaled_sound_pressure_level'] rows: 201 pred range: 109.50..137.92 std=6.81 ALL OUTPUT CHECKS PASSED data/test_features.csv: OK
command: str_replace
path: /app/train_and_predict.py
old_str: def _col(frame: pd.DataFrame, name: str) -> pd.Series:
return pd.to_numeric(frame[name], errors="coerce").astype(float)
new_str: def _col(frame: pd.DataFrame, name: str) -> pd.Series:
return pd.to_numeric(frame[name], errors="coerce").astype(float)
# Features that are strictly positive physical quantities (log-transformed).
_POSITIVE_FEATURES = [
"frequency",
"chord_length",
"free_stream_velocity",
"suction_side_displacement_thickness",
]
def feature_stats(labeled: pd.DataFrame) -> dict:
"""Per-feature medians and positive floors from the clean training data."""
stats = {"median": {}, "floor": {}}
for name in FEATURES:
col = _col(labeled, name)
stats["median"][name] = float(col.median())
if name in _POSITIVE_FEATURES:
positive = col[col > 0]
lo = float(positive.min()) if len(positive) else 1e-6
stats["floor"][name] = max(lo * 0.5, 1e-9)
return stats
def sanitize_features(frame: pd.DataFrame, stats: dict) -> pd.DataFrame:
"""Make any evaluation frame safe: impute NaNs, keep positive cols > 0.
The grader stages rows with the same schema, so this is purely a guard that
guarantees finite log-features (and therefore finite predictions) even for
pathological inputs; it leaves well-formed rows unchanged.
"""
clean = frame.copy()
for name in FEATURES:
col = _col(clean, name)
col = col.fillna(stats["median"][name])
col = col.replace([np.inf, -np.inf], stats["median"][name])
if name in _POSITIVE_FEATURES:
col = col.clip(lower=stats["floor"][name])
clean[name] = col
return cleanThe file /app/train_and_predict.py has been edited successfully.
command: str_replace
path: /app/train_and_predict.py
old_str: labeled = load_labeled_data()
evaluation = load_evaluation_features()
y = labeled[TARGET].to_numpy(dtype=float)
new_str: labeled = load_labeled_data()
evaluation = load_evaluation_features()
# Guard the (grader-staged) evaluation features against NaN / non-positive
# values so every row yields a finite prediction.
stats = feature_stats(labeled)
evaluation_clean = sanitize_features(evaluation, stats)
y = labeled[TARGET].to_numpy(dtype=float)The file /app/train_and_predict.py has been edited successfully.
command: str_replace
path: /app/train_and_predict.py
old_str: X_gp_train = gp_features(labeled)
X_gp_eval = gp_features(evaluation)
new_str: X_gp_train = gp_features(labeled)
X_gp_eval = gp_features(evaluation_clean)The file /app/train_and_predict.py has been edited successfully.
command: str_replace
path: /app/train_and_predict.py
old_str: X_tree_train = tree_features(labeled)
X_tree_eval = tree_features(evaluation)
new_str: X_tree_train = tree_features(labeled)
X_tree_eval = tree_features(evaluation_clean)The file /app/train_and_predict.py has been edited successfully.
cd /app && python3 -c "import ast; ast.parse(open('train_and_predict.py').read()); print('syntax OK')" && sed -n '185,230p' train_and_predict.pysyntax OK
stats = feature_stats(labeled)
evaluation_clean = sanitize_features(evaluation, stats)
y = labeled[TARGET].to_numpy(dtype=float)
# --- Gaussian Process backbone (smooth physical surface) ---
X_gp_train = gp_features(labeled)
X_gp_eval = gp_features(evaluation_clean)
scaler = StandardScaler().fit(X_gp_train.to_numpy())
gp = build_gp()
gp.fit(scaler.transform(X_gp_train.to_numpy()), y)
pred_gp = gp.predict(scaler.transform(X_gp_eval.to_numpy()))
# --- Complementary tree ensembles ---
X_tree_train = tree_features(labeled)
X_tree_eval = tree_features(evaluation_clean)
extra = ExtraTreesRegressor(
n_estimators=800, min_samples_leaf=1, random_state=0, n_jobs=-1
)
extra.fit(X_tree_train, y)
pred_extra = extra.predict(X_tree_eval)
rf = RandomForestRegressor(n_estimators=800, random_state=0, n_jobs=-1)
rf.fit(X_tree_train, y)
pred_rf = rf.predict(X_tree_eval)
predictions = W_GP * pred_gp + W_EXTRA * pred_extra + W_RF * pred_rf
# Guarantee finite, plausible numeric output for every row.
predictions = np.asarray(predictions, dtype=float)
if not np.all(np.isfinite(predictions)):
fill = float(np.median(y))
predictions = np.where(np.isfinite(predictions), predictions, fill)
predictions = np.clip(predictions, SPL_LOW, SPL_HIGH)
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__":cd /app && cp data/test_features.csv /tmp/tf.bak && md5sum data/test_features.csv > /tmp/tf2.md5
echo "=== Edge case 1: rows with NaN, zero, negative, and a single-row file ==="
python3 -c "
import pandas as pd, numpy as np
base=pd.read_csv('/app/data/validation.csv').drop(columns=['scaled_sound_pressure_level']).head(6).copy()
base['sample_id']=['edge_%d'%i for i in range(len(base))]
# inject pathological values
base.loc[0,'frequency']=np.nan
base.loc[1,'suction_side_displacement_thickness']=0.0
base.loc[2,'chord_length']=-1.0
base.loc[3,'free_stream_velocity']=np.nan
base.loc[4,'attack_angle']=np.nan
base.to_csv('/app/data/test_features.csv',index=False)
print(base.to_string())
"
rm -f output/predictions.csv && python3 train_and_predict.py
python3 -c "
import pandas as pd, numpy as np
p=pd.read_csv('/app/output/predictions.csv')
v=p['predicted_scaled_sound_pressure_level'].to_numpy(float)
print(p.to_string()); assert np.all(np.isfinite(v)), 'NON-FINITE!'; print('finite OK')
"
echo "=== Edge case 2: single row ==="
python3 -c "
import pandas as pd
b=pd.read_csv('/app/data/validation.csv').drop(columns=['scaled_sound_pressure_level']).head(1).copy()
b['sample_id']=['solo']; b.to_csv('/app/data/test_features.csv',index=False)
"
rm -f output/predictions.csv && python3 train_and_predict.py && cat output/predictions.csv
# restore
cp /tmp/tf.bak data/test_features.csv && md5sum -c /tmp/tf2.md5=== Edge case 1: rows with NaN, zero, negative, and a single-row file === sample_id frequency attack_angle chord_length free_stream_velocity suction_side_displacement_thickness 0 edge_0 NaN 12.325203 0.101492 39.585315 0.040727 1 edge_1 629.718431 0.018626 0.152499 39.569233 0.000000 2 edge_2 12482.966342 0.010528 -1.000000 71.311117 0.000401 3 edge_3 1600.327555 0.033823 0.025379 NaN 0.000430 4 edge_4 4991.522682 NaN 0.025403 71.338126 0.000847 5 edge_5 2495.861447 0.018494 0.025370 39.658910 0.000429 sample_id predicted_scaled_sound_pressure_level 0 edge_0 119.487638 1 edge_1 122.278427 2 edge_2 131.238716 3 edge_3 123.992293 4 edge_4 135.184760 5 edge_5 131.675481 finite OK === Edge case 2: single row === sample_id,predicted_scaled_sound_pressure_level solo,115.97350259447056 data/test_features.csv: OK
cd /app && cat > /tmp/wsweep.py << 'EOF'
import numpy as np, pandas as pd, warnings, importlib.util
warnings.filterwarnings('ignore')
spec=importlib.util.spec_from_file_location('tp','/app/train_and_predict.py'); tp=importlib.util.module_from_spec(spec); spec.loader.exec_module(tp)
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
train=pd.read_csv('/app/data/train.csv'); val=pd.read_csv('/app/data/validation.csv'); T='scaled_sound_pressure_level'
y=train[T].to_numpy(float); yv=val[T].values
Xg=tp.gp_features(train); Xgv=tp.gp_features(val); sc=StandardScaler().fit(Xg.to_numpy())
pgp=tp.build_gp().fit(sc.transform(Xg.to_numpy()),y).predict(sc.transform(Xgv.to_numpy()))
Xt=tp.tree_features(train); Xtv=tp.tree_features(val)
pex=ExtraTreesRegressor(n_estimators=800,min_samples_leaf=1,random_state=0,n_jobs=-1).fit(Xt,y).predict(Xtv)
prf=RandomForestRegressor(n_estimators=800,random_state=0,n_jobs=-1).fit(Xt,y).predict(Xtv)
cl=np.array([0.0254,0.0508,0.1016,0.1524,0.2286,0.3048]); vl=np.array([31.7,39.6,55.5,71.3])
def snap(x,levels): return levels[np.argmin(np.abs(levels[None,:]-np.asarray(x)[:,None]),axis=1)]
vg=np.array([f'{a}_{c}_{v}' for a,c,v in zip(np.round(val['attack_angle'].values,1),snap(val['chord_length'],cl),snap(val['free_stream_velocity'],vl))])
def mets(p):
e=p-yv; cr=pd.DataFrame({'g':vg,'e':e}).groupby('g')['e'].apply(lambda s:np.sqrt(np.mean(s**2)))
return np.sqrt(np.mean(e**2)),np.mean(np.abs(e)),np.percentile(cr,90),np.percentile(cr,95),cr.max()
for wg,we,wr in [(0.6,0.25,0.15),(0.6,0.4,0.0),(0.65,0.2,0.15),(0.7,0.15,0.15),(0.7,0.3,0.0),(0.55,0.3,0.15),(0.65,0.25,0.10),(0.5,0.3,0.2)]:
r=mets(wg*pgp+we*pex+wr*prf)
f='PASS' if r[0]<=2.25 and r[1]<=1.55 and r[2]<=2.35 and r[3]<=4.75 and r[4]<=5.60 else ''
print(f'gp{wg} ex{we} rf{wr}: rmse={r[0]:.3f} mae={r[1]:.3f} p90={r[2]:.3f} p95={r[3]:.3f} max={r[4]:.3f} {f}')
EOF
python3 /tmp/wsweep.pygp0.6 ex0.25 rf0.15: rmse=1.842 mae=1.392 p90=2.209 p95=3.203 max=4.105 PASS gp0.6 ex0.4 rf0.0: rmse=1.819 mae=1.364 p90=2.148 p95=3.206 max=4.147 PASS gp0.65 ex0.2 rf0.15: rmse=1.832 mae=1.385 p90=2.171 p95=3.206 max=4.118 PASS gp0.7 ex0.15 rf0.15: rmse=1.824 mae=1.380 p90=2.138 p95=3.210 max=4.132 PASS gp0.7 ex0.3 rf0.0: rmse=1.806 mae=1.352 p90=2.089 p95=3.210 max=4.174 PASS gp0.55 ex0.3 rf0.15: rmse=1.854 mae=1.399 p90=2.250 p95=3.202 max=4.091 PASS gp0.65 ex0.25 rf0.1: rmse=1.824 mae=1.376 p90=2.151 p95=3.205 max=4.132 PASS gp0.5 ex0.3 rf0.2: rmse=1.880 mae=1.419 p90=2.321 p95=3.201 max=4.064 PASS
command: str_replace path: /app/train_and_predict.py old_str: W_GP = 0.60 W_EXTRA = 0.25 W_RF = 0.15 new_str: W_GP = 0.65 W_EXTRA = 0.20 W_RF = 0.15
The file /app/train_and_predict.py has been edited successfully.
cd /app && md5sum data/*.csv > /tmp/inputs.md5 echo "=== Grader-style run (discard old predictions, re-run script) ===" && rm -f output/predictions.csv && time python3 train_and_predict.py && echo "--- output ---" && head -3 output/predictions.csv && wc -l < output/predictions.csv echo "=== verify input files unchanged ===" && md5sum -c /tmp/inputs.md5 echo "=== final blend metrics on faithful train->val proxy ===" && python3 /tmp/verify.py 2>/dev/null | sed 's/PRODUCTION blend/FINAL blend (W_GP=0.65,W_EXTRA=0.20,W_RF=0.15)/'
=== Grader-style run (discard old predictions, re-run script) === --- output --- sample_id,predicted_scaled_sound_pressure_level af_val_0000,115.97660542526957 af_val_0001,124.20947627726355 41 === verify input files unchanged === data/test_features.csv: OK data/train.csv: OK data/validation.csv: OK === final blend metrics on faithful train->val proxy === === FINAL blend (W_GP=0.65,W_EXTRA=0.20,W_RF=0.15), train->val (faithful proxy) === rmse=1.832 (<=2.25) mae=1.385 (<=1.55) p90=2.171 (<=2.35) p95=3.206 (<=4.75) max=4.118 (<=5.60) ALL PASS: True [stderr] real 0m29.760s user 0m31.190s sys 0m2.240s
cd /app && cat train_and_predict.py
#!/usr/bin/env python3
"""Airfoil self-noise regression surrogate.
The benchmark holds out *complete* aerodynamic condition groups -- a condition
being the tuple (attack_angle, chord_length, free_stream_velocity). The hidden
evaluation therefore tests generalisation to unseen physical regimes rather than
interpolation between nearby rows. The scoring penalises both global error and
per-condition failures (p90 / p95 / max condition RMSE).
Modelling approach
------------------
The airfoil self-noise spectrum (Brooks-Pope-Marcolini physics) is a smooth,
unimodal function of a handful of log-scaled similarity variables (Strouhal
number, displacement thickness, Mach/velocity, chord, angle). A Gaussian
Process with an anisotropic Matern(nu=1.5) kernel models this smooth surface and
generalises gracefully to unseen conditions; it is the backbone of the model.
Tree ensembles (ExtraTrees, RandomForest) are lower-variance, data-adaptive
learners whose errors are partly complementary to the GP's. Averaging the GP
with the two forests reduces per-condition bias and trims the worst-case
condition errors, which is exactly what the grouped metrics reward.
The workflow trains purely on the provided labelled public data
(train.csv + validation.csv) and writes predictions for the grader-staged
feature rows in /app/data/test_features.csv.
"""
from __future__ import annotations
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import (
ConstantKernel,
Matern,
WhiteKernel,
)
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",
]
# Blend weights (GP backbone + two complementary tree ensembles). Chosen via
# grouped cross-validation and Monte-Carlo condition-holdout simulations that
# mimic the hidden grouped split.
W_GP = 0.65
W_EXTRA = 0.20
W_RF = 0.15
# Physically plausible clamp for the calibrated SPL target (dB). Training
# labels live in ~105-140 dB; the wide guard only trips on pathological
# extrapolation and keeps every prediction finite without flattening outputs.
SPL_LOW, SPL_HIGH = 90.0, 155.0
def _col(frame: pd.DataFrame, name: str) -> pd.Series:
return pd.to_numeric(frame[name], errors="coerce").astype(float)
# Features that are strictly positive physical quantities (log-transformed).
_POSITIVE_FEATURES = [
"frequency",
"chord_length",
"free_stream_velocity",
"suction_side_displacement_thickness",
]
def feature_stats(labeled: pd.DataFrame) -> dict:
"""Per-feature medians and positive floors from the clean training data."""
stats = {"median": {}, "floor": {}}
for name in FEATURES:
col = _col(labeled, name)
stats["median"][name] = float(col.median())
if name in _POSITIVE_FEATURES:
positive = col[col > 0]
lo = float(positive.min()) if len(positive) else 1e-6
stats["floor"][name] = max(lo * 0.5, 1e-9)
return stats
def sanitize_features(frame: pd.DataFrame, stats: dict) -> pd.DataFrame:
"""Make any evaluation frame safe: impute NaNs, keep positive cols > 0.
The grader stages rows with the same schema, so this is purely a guard that
guarantees finite log-features (and therefore finite predictions) even for
pathological inputs; it leaves well-formed rows unchanged.
"""
clean = frame.copy()
for name in FEATURES:
col = _col(clean, name)
col = col.fillna(stats["median"][name])
col = col.replace([np.inf, -np.inf], stats["median"][name])
if name in _POSITIVE_FEATURES:
col = col.
…[truncated]cd /app && echo "=== final predictions.csv ===" && head -3 output/predictions.csv && echo "... ($(wc -l < output/predictions.csv) lines total)" && 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'].to_numpy(float)
assert np.all(np.isfinite(v)) and v.std()>1.0
print('format OK | rows=%d | range %.2f..%.2f | mean=%.2f std=%.2f'%(len(p),v.min(),v.max(),v.mean(),v.std()))
"
echo "=== background jobs ===" ; jobs -l; ps aux | grep -c "[m]c[0-9].py"=== final predictions.csv === sample_id,predicted_scaled_sound_pressure_level af_val_0000,115.97660542526957 af_val_0001,124.20947627726355 ... (41 lines total) format OK | rows=40 | range 114.82..138.08 | mean=127.96 std=6.53 === background jobs === [1] 1108 Done nohup python3 /tmp/cv_gp.py > /tmp/cv_gp.log 2>&1 [2] 1269 Done nohup python3 /tmp/mc.py > /tmp/mc.log 2>&1 [3] 1428 Done nohup python3 /tmp/mc2.py > /tmp/mc2.log 2>&1 [4] 1773 Done nohup python3 /tmp/mc3.py > /tmp/mc3.log 2>&1 [5]+ 2106 Done nohup python3 /tmp/mc4.py > /tmp/mc4.log 2>&1 0
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: airfoil-self-noise # attempt: 5 status: completed raw_score: 0.42 # reward rule: continuous: score 0.42 < 0.5 # => reward = 0.0 (FAIL) # # Note: these tasks score via an external hidden grader; no pytest/verifier # block is embedded in the agent transcript. The block below is the last # sizable tool-output captured in the run (supporting evidence). ---------------------------------------------------------------------- === final predictions.csv === sample_id,predicted_scaled_sound_pressure_level af_val_0000,115.97660542526957 af_val_0001,124.20947627726355 ... (41 lines total) format OK | rows=40 | range 114.82..138.08 | mean=127.96 std=6.53 === background jobs === [1] 1108 Done nohup python3 /tmp/cv_gp.py > /tmp/cv_gp.log 2>&1 [2] 1269 Done nohup python3 /tmp/mc.py > /tmp/mc.log 2>&1 [3] 1428 Done nohup python3 /tmp/mc2.py > /tmp/mc2.log 2>&1 [4] 1773 Done nohup python3 /tmp/mc3.py > /tmp/mc3.log 2>&1 [5]+ 2106 Done nohup python3 /tmp/mc4.py > /tmp/mc4.log 2>&1 0
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_c428b26caeaa404a. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_c428b26caeaa404a · verifier authoritative; classifier explanatory.