{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":101849,"databundleVersionId":13093295,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# --- Configuration des chemins (à adapter selon votre environnement Kaggle ou local) ---\nROOT = \"/kaggle/input/ariel-data-challenge-2025\"\n\n# --- 1. Chargement des Fichiers Clés ---\n\nprint(\"## 🚀 Chargement et Inspection des Fichiers Clés (Métadonnées)\\n\")\n\n# Charger les métadonnées de base\ntry:\n    df_train = pd.read_csv(os.path.join(ROOT, \"train.csv\"))\n    df_star_info = pd.read_csv(os.path.join(ROOT, \"train_star_info.csv\"))\n    df_wavelengths = pd.read_csv(os.path.join(ROOT, \"wavelengths.csv\"))\n    df_adc_info = pd.read_csv(os.path.join(ROOT, \"adc_info.csv\"))\nexcept FileNotFoundError as e:\n    print(f\"Erreur: Fichier non trouvé. Assurez-vous que ROOT est correct. Détails: {e}\")\n    exit()\n\n# --- 2. CORRECTION et Inspection des Longueurs d'Onde ---\n\n# Correction: Transposer le DataFrame et nommer la colonne 'wavelength'\ndf_wavelengths_corrected = df_wavelengths.T.reset_index(drop=True).rename(columns={0: 'wavelength'})\n# S'assurer que les valeurs sont numériques (float)\ndf_wavelengths_corrected['wavelength'] = pd.to_numeric(df_wavelengths_corrected['wavelength'])\n\nwavelengths = df_wavelengths_corrected['wavelength'].values\nnum_wl = len(wavelengths)\n\nprint(\"### 🌈 1. wavelengths.csv (Grille de Longueur d'Onde - Corrigé)\")\nprint(f\"Dimensions corrigées : {df_wavelengths_corrected.shape}\")\nprint(f\"Nombre de points spectraux : {num_wl}\")\nprint(f\"Plage de longueurs d'onde : {wavelengths.min():.4f} à {wavelengths.max():.4f} µm\")\ndisplay(df_wavelengths_corrected.head())\n\n# --- 3. Préparation des Données Spectrales (Spectraux) ---\n\n# Vérifier si df_train a le bon nombre de colonnes spectrales (N_colonnes = 1 + num_wl)\nexpected_cols = num_wl + 1 \nif df_train.shape[1] != expected_cols:\n    print(f\"\\n[ATTENTION] train.csv a {df_train.shape[1]} colonnes mais {num_wl} points spectraux sont attendus (+ planet_id).\")\n\n# Extraire les données spectrales (colonnes wl_1 à wl_283)\nspectral_data = df_train.iloc[:, 1:] \nprint(f\"\\nDonnées Spectrales: {spectral_data.shape} (N_planètes, N_longueurs_onde)\")\n\n\n# --- 4. STATISTIQUES DESCRIPTIVES DU SPECTRE ---\n\nprint(\"\\n### 📊 2. Statistiques sur la Profondeur de Transit (Rp²/Rs²)\")\n\n# Calculer la profondeur de transit moyenne et l'écart-type pour chaque planète\ndf_star_info['mean_depth'] = spectral_data.mean(axis=1)\ndf_star_info['std_depth'] = spectral_data.std(axis=1)\n\nprint(\"Distribution de la Profondeur de Transit (Moyenne sur toutes les longueurs d'onde):\")\ndisplay(df_star_info[['mean_depth', 'std_depth']].describe())\n\n# Calculer la profondeur de transit moyenne par longueur d'onde\nmean_spectrum = spectral_data.mean(axis=0).values\n\nplt.figure(figsize=(12, 5))\nplt.plot(wavelengths, mean_spectrum, color='purple', linewidth=2)\nplt.title('Spectre Moyen de Profondeur de Transit sur le Jeu d\\'Entraînement')\nplt.xlabel('Longueur d\\'Onde (µm)')\nplt.ylabel('Profondeur de Transit Moyenne (Rp²/Rs²)')\nplt.axvline(x=0.8, color='gray', linestyle='--', label='Séparation FGS1 / AIRS-CH0 (approx)')\nplt.axvline(x=1.95, color='gray', linestyle='--', )\nplt.legend()\nplt.grid(True, linestyle=':', alpha=0.6)\nplt.show()\n\n\n# --- 5. ANALYSE DE CORRÉLATION ---\n\nprint(\"\\n### 📉 3. Analyse des Corrélations entre Paramètres Physiques et Profondeur de Transit\")\n\n# Fusionner les stats spectrales avec les infos étoile/planète\ndf_merged = df_star_info.copy()\n\n# Calculer la matrice de corrélation\ncorrelation_matrix = df_merged[['Rs', 'Ms', 'Ts', 'Mp', 'P', 'sma', 'i', 'mean_depth', 'std_depth']].corr()\n\nplt.figure(figsize=(10, 8))\nsns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=\".2f\", linewidths=.5, linecolor='black')\nplt.title('Matrice de Corrélation (Paramètres Physiques vs Profondeur de Transit Moyenne/Écart-type)')\nplt.show()\n\n# --- 6. Inspection des Paramètres ADC ---\n\nprint(\"\\n### 🔧 4. adc_info.csv (Correction du Signal Brut)\")\nprint(f\"Dimensions : {df_adc_info.shape}\")\nprint(\"Contenu :\")\ndisplay(df_adc_info)\nprint(\"Note : 'gain' et 'offset' sont utilisés pour convertir les uint16 bruts en float64 de gamme dynamique complète.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\n\nprint(\"### 🌌 5. Signal Physique et Calibré par Instrument\")\n\n# Paramètres principaux du système\nplanet_ids = df_train['planet_id']\n\n# --- Fonction de calibration ADC ---\ndef adc_calibrate(raw_signal, gain, offset):\n    \"\"\"\n    Convertit un signal brut ADC en valeurs physiques.\n    \"\"\"\n    return raw_signal * gain + offset\n\n# Récupérer les gains et offsets\nfgs1_gain = df_adc_info['FGS1_adc_gain'].values[0]\nfgs1_offset = df_adc_info['FGS1_adc_offset'].values[0]\nairs_gain = df_adc_info['AIRS-CH0_adc_gain'].values[0]\nairs_offset = df_adc_info['AIRS-CH0_adc_offset'].values[0]\n\n# --- Déterminisme 1 : Segmentation Spectrale ---\n# IMPORTANT : L'indexation ci-dessous est une HYPOTHÈSE (1-60 et 61-283). \n# Pour un déterminisme parfait, vous DEVEZ utiliser 'wavelengths.csv' pour trouver les vrais indices \n# de colonnes (wl_i) pour la couverture spectrale de chaque instrument.\nfgs1_cols = [f'wl_{i}' for i in range(1, 61)]    # Bande FGS1 (Visible/Proche-IR)\nairs_cols = [f'wl_{i}' for i in range(61, 284)]  # Bande AIRS-CH0 (IR)\n\n# Moyenne spectrale BRUTE par planète (signal ADC)\nsignal_fgs1_brut = df_train[fgs1_cols].mean(axis=1)\nsignal_airs_brut = df_train[airs_cols].mean(axis=1)\n\n# --- Calibration ADC ---\nsignal_fgs1 = adc_calibrate(signal_fgs1_brut, fgs1_gain, fgs1_offset)\nsignal_airs = adc_calibrate(signal_airs_brut, airs_gain, airs_offset)\n\n# --- Contrainte Physique (Clipping) ---\n# Limite inférieure à 0, limite supérieure typique à 0.1 (10%)\nsignal_fgs1_calib = np.clip(signal_fgs1, 0, 0.1) \nsignal_airs_calib = np.clip(signal_airs, 0, 0.1)\n\n# -------------------------------------------------------------\n# --- Déterminisme 2 : Ajout du Bruit (Simulation de Réalisme) ---\n# Ceci simule l'incertitude de mesure (souvent appelée \"noise floor\")\n\n# Niveau de bruit attendu (Sigma) pour chaque instrument (en Rp²/Rs²)\nNOISE_FGS1 = 5e-5 # ~50 ppm\nNOISE_AIRS = 1e-4 # ~100 ppm \n\n# Générer un bruit gaussien aléatoire centré sur zéro\nnoise_fgs1_sim = np.random.normal(loc=0, scale=NOISE_FGS1, size=len(signal_fgs1_calib))\nnoise_airs_sim = np.random.normal(loc=0, scale=NOISE_AIRS, size=len(signal_airs_calib))\n\n# Appliquer le bruit au signal calibré\nsignal_fgs1_final = signal_fgs1_calib + noise_fgs1_sim\nsignal_airs_final = signal_airs_calib + noise_airs_sim\n\n# Remplacer les signaux clippés originaux par les signaux finaux (avec bruit)\nsignal_fgs1 = signal_fgs1_final\nsignal_airs = signal_airs_final\n# -------------------------------------------------------------\n\n\n# --- Visualisation (utilise maintenant les signaux avec bruit) ---\nplt.figure(figsize=(12, 5))\nplt.plot(planet_ids, signal_fgs1, color='orange', label='FGS1 (Visible)', alpha=0.7, marker='.', linestyle='') # Ajout d'un marqueur\nplt.plot(planet_ids, signal_airs, color='purple', label='AIRS-CH0 (IR)', alpha=0.7, marker='.', linestyle='')\nplt.title(\"Signal Physique de Transit par Planète (Calibré ADC + Bruit Simulé)\")\nplt.xlabel(\"ID Planète\")\nplt.ylabel(\"Profondeur de Transit (Rp²/Rs²)\")\nplt.grid(True, linestyle=':', alpha=0.6)\nplt.legend()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom IPython.display import display\n\n# --- DÉTERMINISME DE PRÉPARATION ---\n# 1. Définir les données spectrales brutes (wl_1 à wl_283) si ce n'est pas fait\nspectral_cols = [col for col in df_train.columns if col.startswith('wl_')]\nif not spectral_cols:\n    # Cas de fallback si wl_i sont les colonnes après planet_id\n    spectral_cols = df_train.columns[1:] \nspectral_data = df_train[spectral_cols]\n\n# --- 8. Construction du Dataset Final pour Modélisation ---\n\nprint(\"### 🗂️ 6. Préparation du Dataset pour ML / Modélisation Probabiliste\")\n\n# Copier les infos étoiles / planètes (Contrainte: utilisation des données 'star_info')\ndf_dataset = df_star_info.copy()\n\n# Ajouter les signaux simulés calibrés (Contrainte: utilisation des résultats de la cellule 5)\ndf_dataset['signal_fgs1'] = signal_fgs1\ndf_dataset['signal_airs'] = signal_airs\n\n# --- Déterminisme Avancé : Rapport de Signaux (Feature Puissante) ---\n# Le rapport des signaux est souvent une meilleure feature que les signaux bruts.\n# Ajout d'un epsilon pour éviter la division par zéro si signal_fgs1 est nul (bien que peu probable avec le bruit)\nEPSILON_RATIO = 1e-12 \ndf_dataset['ratio_airs_fgs1'] = df_dataset['signal_airs'] / (df_dataset['signal_fgs1'] + EPSILON_RATIO)\n\n# Ajouter des features dérivées du spectre (Basé sur la contrainte de la variabilité spectrale)\ndf_dataset['std_depth'] = spectral_data.std(axis=1) # Écart-type du signal spectral (forme)\ndf_dataset['max_depth'] = spectral_data.max(axis=1) # Profondeur maximale\ndf_dataset['min_depth'] = spectral_data.min(axis=1) # Profondeur minimale\n\n# Afficher un aperçu du dataset final\nprint(f\"Dimensions du dataset final : {df_dataset.shape}\")\ndisplay(df_dataset.head())\n\n# Vérification des colonnes\nprint(\"\\nColonnes disponibles :\")\nprint(df_dataset.columns.tolist())\n\n# Optionnel : sauvegarde du dataset pour usage ML / Bayésien\n# df_dataset.to_csv(\"/kaggle/working/dataset_final.csv\", index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom IPython.display import display\n\n# --- DÉTERMINISME DE PRÉPARATION ---\n# Assurez-vous que ces variables sont bien définies à partir des étapes précédentes :\n# 1. Définir les données spectrales brutes (wl_1 à wl_283)\nspectral_cols = [col for col in df_train.columns if col.startswith('wl_')]\nspectral_data = df_train[spectral_cols]\n\n# 2. Récupérer les valeurs de longueur d'onde (en microns)\n# Assumer que 'wavelengths' est une liste ou un tableau NumPy des valeurs WL\n# Par exemple: wavelengths = df_wl['wavelength'].values\n# Pour cet exemple, je définis une liste simple basée sur le nombre de colonnes:\ntry:\n    # Utiliser les wavelengths provenant de l'import (si elles existent déjà)\n    len(wavelengths)\nexcept NameError:\n    # Si 'wavelengths' n'est pas définie, créer un tableau simple pour l'exemple\n    print(\"ATTENTION : 'wavelengths' non défini. Création d'un tableau placeholder.\")\n    wavelengths = np.linspace(0.5, 5.0, len(spectral_cols))\n\n\nprint(\"### 🎛️ 7. Features Spectrales par Longueur d'Onde\")\n\n# Créer un DataFrame pour stocker les features spectrales\ndf_features = df_train[['planet_id']].copy()\n\n# --- Contrainte : Normalisation par Z-score (pour standardiser la forme du spectre) ---\n# Normalisation globale (moins sensible) ou par colonne (Z-score pour chaque longueur d'onde)\nspectral_normalized = (spectral_data - spectral_data.mean()) / spectral_data.std()\n\n# Ajouter chaque longueur d'onde comme feature\nfor idx, wl in enumerate(wavelengths):\n    # Créer le nom de colonne formaté (ex: wl_0.500um)\n    col_name = f\"wl_{wl:.3f}um\"\n    \n    # Récupérer la colonne normalisée (la numérotation des colonnes pandas est basée sur l'index)\n    df_features[col_name] = spectral_normalized.iloc[:, idx]\n\n# Vérification des dimensions et aperçu\nprint(f\"Dimensions du dataset features : {df_features.shape}\")\ndisplay(df_features.head())\n\n# --- Fusion des datasets ---\n# Fusionner les features physiques/déterministes (df_dataset) avec les features spectrales (df_features)\ndf_ml_ready = df_dataset.merge(df_features, on='planet_id')\n\nprint(f\"\\nDimensions du dataset final ML-ready : {df_ml_ready.shape}\")\ndisplay(df_ml_ready.head())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom IPython.display import display\n# df_ml_ready est le résultat de la cellule précédente\n\n# --- 10. Préparation des Targets μ et σ par Instrument ---\n\nprint(\"### 🎯 8. Targets Probabilistes Déterministes et Robustes (μ et σ)\")\n\n# Cible μ : moyenne des signaux calibrés par instrument\ndf_ml_ready['mu_fgs1'] = df_ml_ready['signal_fgs1']\ndf_ml_ready['mu_airs'] = df_ml_ready['signal_airs']\n\n# μ globale (moyenne FGS1 + AIRS)\ndf_ml_ready['mu'] = df_ml_ready[['mu_fgs1', 'mu_airs']].mean(axis=1)\n\n# Cible σ : écart-type combinant variabilité physique + bruit stochastique minimal\n# Ce niveau de bruit est une contrainte de modélisation (noise floor)\nnoise_level = 1e-4  # bruit stochastique minimal (~100 ppm)\nepsilon = 1e-8      # sécurité pour éviter sigma = 0\n\n# σ pour FGS1\ndf_ml_ready['sigma_fgs1'] = np.sqrt(\n    # Écart par rapport à la moyenne globale (variabilité inter-instrument)\n    (df_ml_ready['signal_fgs1'] - df_ml_ready['mu'])**2 + \n    # Variabilité du spectre (variabilité intrinsèque)\n    df_ml_ready['std_depth']**2\n) + noise_level + epsilon\n\n# σ pour AIRS\ndf_ml_ready['sigma_airs'] = np.sqrt(\n    # Écart par rapport à la moyenne globale (variabilité inter-instrument)\n    (df_ml_ready['signal_airs'] - df_ml_ready['mu'])**2 + \n    # Variabilité du spectre (variabilité intrinsèque)\n    df_ml_ready['std_depth']**2\n) + noise_level + epsilon\n\n# Vérification rapide\nprint(\"Aperçu des targets μ et σ par instrument :\")\ndisplay(df_ml_ready[['planet_id', 'mu_fgs1', 'sigma_fgs1', 'mu_airs', 'sigma_airs', 'mu']].head())\n\n# Optionnel : sauvegarde pour usage ML / Bayésien ou soumission\n# df_ml_ready.to_csv(\"/kaggle/working/dataset_ml_bayes_robust.csv\", index=False)\n\nprint(\"\\n✅ Targets probabilistes préparées avec séparation instrument, variabilité physique et bruit stochastique.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom sklearn.multioutput import MultiOutputRegressor\nfrom sklearn.ensemble import GradientBoostingRegressor\nfrom sklearn.metrics import mean_squared_error\nimport numpy as np\n\nprint(\"### 🤖 Régression Multi-Output pour μ et σ par Wavelength\")\n\n# --- Préparer les features ---\nfeature_cols = [col for col in df_ml_ready.columns if 'wl_' in col] + ['Rs', 'Ms', 'Ts', 'Mp', 'P', 'sma', 'i']\nX = df_ml_ready[feature_cols].values\n\n# --- Préparer les targets μ et σ par wavelength ---\nwl_cols = [col for col in df_train.columns if col.startswith('wl_')]\n\n# μ_target : utiliser directement les valeurs wl_i comme μ (ou moyenne calibrée si dispo)\ny_mu = df_train[wl_cols].values  # shape = (n_planets, 283)\n\n# σ_target : générer une estimation du sigma par wavelength\n# Approche simple : écart par rapport à μ + std_depth + noise minimal\nstd_depth = df_ml_ready['std_depth'].values.reshape(-1, 1)  # shape = (n_planets, 1)\nnoise_floor = 1e-4  # ~100ppm\ny_sigma = np.sqrt((y_mu - y_mu.mean(axis=1).reshape(-1, 1))**2 + std_depth**2) + noise_floor\n\nprint(f\"Shape μ : {y_mu.shape}, Shape σ : {y_sigma.shape}\")\n\n# --- Split train/validation ---\nX_train, X_val, y_train_mu, y_val_mu, y_train_sigma, y_val_sigma = train_test_split(\n    X, y_mu, y_sigma, test_size=0.2, random_state=42\n)\n\n# -----------------------------------------------------------------\n## Modèle Multi-Output pour μ\n# -----------------------------------------------------------------\nmodel_mu = MultiOutputRegressor(\n    GradientBoostingRegressor(n_estimators=500, max_depth=4, learning_rate=0.05, random_state=42)\n)\nprint(\"\\nEntraînement du modèle μ multi-output...\")\nmodel_mu.fit(X_train, y_train_mu)\n\n# Prédictions sur validation\ny_pred_mu = model_mu.predict(X_val)\nprint(f\"MSE global sur μ : {mean_squared_error(y_val_mu, y_pred_mu):.6f}\")\n\n# -----------------------------------------------------------------\n## Modèle Multi-Output pour σ\n# -----------------------------------------------------------------\nmodel_sigma = MultiOutputRegressor(\n    GradientBoostingRegressor(n_estimators=500, max_depth=4, learning_rate=0.05, random_state=42)\n)\nprint(\"\\nEntraînement du modèle σ multi-output...\")\nmodel_sigma.fit(X_train, y_train_sigma)\n\n# Prédictions sur validation\ny_pred_sigma = model_sigma.predict(X_val)\n\n# Contrainte : σ doit rester positif\ny_pred_sigma = np.clip(y_pred_sigma, 1e-6, None) \nprint(f\"MSE global sur σ : {mean_squared_error(y_val_sigma, y_pred_sigma):.6f}\")\nprint(f\"σ prédit moyen : {y_pred_sigma.mean():.6f}\")\n\nprint(\"\\n✅ Modèles μ et σ multi-output entraînés pour toutes les wavelengths\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\n\nprint(\"### 🚀 Inférence Multi-Output et Submission Optimisée\")\n\n# ------------------------------------------------------------\n# 🔹 1) Charger le vrai nombre de longueurs d'onde (safe)\n# ------------------------------------------------------------\ndf_wavelengths = pd.read_csv(\"/kaggle/input/ariel-data-challenge-2025/wavelengths.csv\")\nNUM_SPECTRA = df_wavelengths.shape[1]\nprint(f\"Nombre de longueurs d'onde détecté : {NUM_SPECTRA}\")\n\n# ------------------------------------------------------------\n# 🔹 2) Planet list\n# ------------------------------------------------------------\nplanet_ids_test = [\n    f for f in os.listdir(\"/kaggle/input/ariel-data-challenge-2025/test\")\n    if os.path.isdir(os.path.join(\"/kaggle/input/ariel-data-challenge-2025/test\", f))\n]\nn_planets = len(planet_ids_test)\n\n# Allocate matrix dynamically\nspectra_matrix = np.zeros((n_planets, NUM_SPECTRA))\n\nRs_vec, Ms_vec, Ts_vec, Mp_vec, P_vec, sma_vec, i_vec = [], [], [], [], [], [], []\n\n# ------------------------------------------------------------\n# 🔹 3) Lecture + calibration signaux\n# ------------------------------------------------------------\nfor idx, pid in enumerate(planet_ids_test):\n    planet_path = os.path.join(\"/kaggle/input/ariel-data-challenge-2025/test\", pid)\n\n    # --- FGS1 ---\n    fgs1_files = [\n        f for f in os.listdir(planet_path)\n        if f.startswith(\"FGS1_signal_\") and f.endswith(\".parquet\")\n    ]\n    if fgs1_files:\n        fgs1_signal = (\n            pd.concat([pd.read_parquet(os.path.join(planet_path, f)) for f in fgs1_files], axis=0)\n            .mean(axis=0)\n            .values\n        )\n    else:\n        fgs1_signal = np.zeros(60)  # FGS1 est toujours 60 bins\n\n    # --- AIRS ---\n    airs_files = [\n        f for f in os.listdir(planet_path)\n        if f.startswith(\"AIRS-CH0_signal_\") and f.endswith(\".parquet\")\n    ]\n    if airs_files:\n        airs_signal = (\n            pd.concat([pd.read_parquet(os.path.join(planet_path, f)) for f in airs_files], axis=0)\n            .mean(axis=0)\n            .values\n        )\n    else:\n        airs_signal = np.zeros(NUM_SPECTRA - 60)\n\n    # --- Calibration ADC ---\n    fgs1_signal_calib = np.clip(\n        fgs1_signal * df_adc_info['FGS1_adc_gain'].values[0] +\n        df_adc_info['FGS1_adc_offset'].values[0],\n        0, 0.1\n    )\n\n    airs_signal_calib = np.clip(\n        airs_signal * df_adc_info['AIRS-CH0_adc_gain'].values[0] +\n        df_adc_info['AIRS-CH0_adc_offset'].values[0],\n        0, 0.1\n    )\n\n    # Assemblage spectral complet\n    spectra_matrix[idx, :60] = fgs1_signal_calib\n    spectra_matrix[idx, 60:] = airs_signal_calib\n\n    # --- Lecture paramètres physiques ---\n    try:\n        star_info_row = df_test_star_info.loc[\n            df_test_star_info[\"planet_id\"] == int(pid)\n        ].iloc[0]\n    except:\n        star_info_row = {\"Rs\": 1.0, \"Ms\": 1.0, \"Ts\": 5500.0,\n                         \"Mp\": 1.0, \"P\": 1.0, \"sma\": 1.0, \"i\": 90.0}\n\n    Rs_vec.append(star_info_row[\"Rs\"])\n    Ms_vec.append(star_info_row[\"Ms\"])\n    Ts_vec.append(star_info_row[\"Ts\"])\n    Mp_vec.append(star_info_row[\"Mp\"])\n    P_vec.append(star_info_row[\"P\"])\n    sma_vec.append(star_info_row[\"sma\"])\n    i_vec.append(star_info_row[\"i\"])\n\n# ------------------------------------------------------------\n# 🔹 4) Normalisation spectrale (basée sur train)\n# ------------------------------------------------------------\nspectra_matrix_norm = (\n    spectra_matrix - spectral_data.mean().values\n) / spectral_data.std().values\n\n# ------------------------------------------------------------\n# 🔹 5) Construction matrice X_test\n# ------------------------------------------------------------\nX_test = np.hstack([\n    spectra_matrix_norm,\n    np.array([Rs_vec, Ms_vec, Ts_vec, Mp_vec, P_vec, sma_vec, i_vec]).T\n])\n\n# ------------------------------------------------------------\n# 🔹 6) Inférence multi-output\n# ------------------------------------------------------------\nmu_preds = model_mu.predict(X_test.astype(np.float64))\nsigma_preds = np.clip(\n    model_sigma.predict(X_test.astype(np.float64)),\n    1e-6, None\n)\n\n# ------------------------------------------------------------\n# 🔹 7) Construire submission\n# ------------------------------------------------------------\ncolumns = (\n    [\"planet_id\"]\n    + [f\"wl_{i+1}\" for i in range(NUM_SPECTRA)]\n    + [f\"err_{i+1}\" for i in range(NUM_SPECTRA)]\n)\n\ndf_submission = pd.DataFrame(\n    np.hstack([np.array(planet_ids_test).reshape(-1, 1), mu_preds, sigma_preds]),\n    columns=columns\n)\n\n# ------------------------------------------------------------\n# 🔹 8) Sauvegarde\n# ------------------------------------------------------------\nsubmission_path = \"/kaggle/working/submission.csv\"\ndf_submission.to_csv(submission_path, index=False)\n\nprint(f\"\\n✅ Submission prête : {submission_path}\")\ndf_submission.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T18:26:29.641515Z","iopub.execute_input":"2025-12-06T18:26:29.641891Z","iopub.status.idle":"2025-12-06T18:26:30.62527Z","shell.execute_reply.started":"2025-12-06T18:26:29.641856Z","shell.execute_reply":"2025-12-06T18:26:30.624076Z"}},"outputs":[],"execution_count":null}]}