{"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"},{"sourceId":9629432,"sourceType":"datasetVersion","datasetId":5846888},{"sourceId":564250,"sourceType":"modelInstanceVersion","modelInstanceId":426187,"modelId":443655}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Code taken and edited from: seowoohyeon\nlink to orignal code made https://www.kaggle.com/code/seowoohyeon/resnet-for-airs-finetuned-lb0-369\n\nImprovements made:\n+0.002 lb score \nμ / airs predictions improved by adding log based validation (log model uses symmetry more has better symmetry checking and a couple other things)\n(symmetry for dips is characteristic of transits and transits also have strict symmetry, and symmetry is not common for noise or other non-transit events). \n\nTuned hyperparameters more\n\nFuture improvements you could try to add:\nRight now log model only works for cases were no transits were detected you can try increasing how much log model could validate.\nLog model is right now very strict to make sure no false postives are added. Maybe try making it less strict that could help.\nLog model also predicts for confidence however I am unsure if the way I calculate confidence would really help or hurt by fixing the way I measure confidence you could probably increase score by a lot.\n","metadata":{}},{"cell_type":"code","source":"# =========================================================\n# Ariel Data Challenge 2025 — Fixed and Ready to Submit\n# =========================================================\n\n!pip install --no-index --find-links=/kaggle/input/ariel-2024-pqdm pqdm\n\nimport os\nimport time\nimport itertools\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nfrom tqdm import tqdm\nfrom pqdm.threads import pqdm\nfrom astropy.stats import sigma_clip\nfrom scipy.optimize import minimize\nfrom torch.utils.data import DataLoader, TensorDataset, random_split\nfrom sklearn.preprocessing import StandardScaler\nfrom scipy.signal import savgol_filter\nfrom sklearn.metrics import mean_squared_error\nimport matplotlib.pyplot as plt\nimport numpy.linalg as la\n\nROOT_PATH = \"/kaggle/input/ariel-data-challenge-2025\"\nMODE = \"test\"\n__t0 = time.perf_counter()\n\nclass Config:\n    DATA_PATH = '/kaggle/input/ariel-data-challenge-2025'\n    DATASET = \"test\"\n    SCALE = 0.946\n    SIGMA = 0.00056\n    CUT_INF = 39\n    CUT_SUP = 321\n    \n    SENSOR_CONFIG = {\n        \"AIRS-CH0\": {\n            \"raw_shape\": [11250, 32, 356],\n            \"calibrated_shape\": [1, 32, CUT_SUP - CUT_INF],\n            \"linear_corr_shape\": (6, 32, 356),\n            \"dt_pattern\": (0.1, 4.5), \n            \"binning\": 30\n        },\n        \"FGS1\": {\n            \"raw_shape\": [135000, 32, 32],\n            \"calibrated_shape\": [1, 32, 32],\n            \"linear_corr_shape\": (6, 32, 32),\n            \"dt_pattern\": (0.1, 0.1),\n            \"binning\": 30 * 12\n        }\n    }\n    \n    MODEL_PHASE_DETECTION_SLICE = slice(30, 140)\n    MODEL_OPTIMIZATION_DELTA = 11\n    MODEL_POLYNOMIAL_DEGREE = 3\n    N_JOBS = 3\n\ndef log_preprocess(signal_2d, binning=3):\n    signal_1d = signal_2d[:, 1:].mean(axis=1)\n    if binning > 1:\n        n_bins = len(signal_1d) // binning\n        signal_1d = np.array([signal_1d[i*binning:(i+1)*binning].mean() for i in range(n_bins)])\n    noise_level = np.std(signal_1d)\n    window = 23 if noise_level < 0.005 else 31\n    signal_1d = savgol_filter(signal_1d, window, 2)\n    return signal_1d\n\ndef detect(points, window_size=4, eps=1e-12, strength_threshold=0.75, symmetry_tolerance=1.1):\n    arr = np.array(points)\n    flux = np.clip(arr[:, 1], eps, None)\n    if len(flux) < (2 * window_size + 1):\n        return None\n    s = np.mean(flux)\n    mad = np.median(np.abs(flux - s)) + eps\n    global_log_ref = np.median(np.log(flux))\n    cutoff = np.quantile(flux, (1-strength_threshold))\n    flux_length = len(flux)\n    window_length = 9\n    smoothed = savgol_filter(flux, window_length=window_length, polyorder=2)\n    residuals = flux - smoothed\n    noise_level = np.std(residuals)\n    noise_level_and_mad=(1-(noise_level/mad+eps))*mad\n    if noise_level_and_mad<0:\n        noise_level_and_mad=noise_level\n    max_offset = window_size\n    sym_gaps = []\n    for center in range(window_size, len(flux) - window_size):\n        sym_gaps = []\n        if flux[center] > cutoff:\n            continue\n        left = flux[center - window_size:center]\n        right = flux[center + 1:center + 1 + window_size]\n        if flux[center] >= left.mean() or flux[center] >= right.mean():\n            continue\n        for offset in range(1, max_offset + 1):\n            lhs_val = flux[center - offset]\n            rhs_val = flux[center + offset]\n            sym_gaps.append(abs(np.log(lhs_val/flux[center]) - np.log(rhs_val/flux[center])))\n        symmetry_gap = np.mean(sym_gaps)\n        if symmetry_gap > symmetry_tolerance * mad:\n            continue\n        symmetry_score = 1.0 - np.clip(symmetry_gap / (mad * symmetry_tolerance), 0, 1)\n        local_log_strength = np.mean(np.log(np.clip([flux[center], *left, *right], eps, None)))\n        if local_log_strength < global_log_ref * strength_threshold:\n            continue\n        return center, flux[center], symmetry_score\n    return None\n\ndef log_model_ensemble_adjust(preprocessed_data, transit_predictions, sigma,\n                              log_threshold=0.75, window_size=4,\n                              min_relative_dip=5e-4, max_injection=0.015):\n    adjusted = transit_predictions.copy()\n    adjusted_sigma = sigma.copy()\n    for i, signal in enumerate(preprocessed_data):\n        flux = signal[:, 1:].mean(axis=1)\n        points = np.stack([np.arange(len(flux)), flux], axis=1)\n        result = detect(points, window_size=window_size, strength_threshold=log_threshold)\n        if result is None:\n            continue\n        center_idx, center_flux, symmetry_score = result\n        w = window_size\n        left = flux[max(0, center_idx - w):center_idx]\n        right = flux[center_idx + 1:center_idx + 1 + w]\n        surrounding = np.concatenate([left, right])\n        baseline = np.median(surrounding) if surrounding.size else np.median(flux)\n        dip = max(0.0, baseline - center_flux)\n        relative_dip = dip / max(baseline, 1e-12)\n        if relative_dip < min_relative_dip:\n            continue\n        inj = np.clip(dip * symmetry_score * 0.7, 1e-5, max_injection)\n        blend_factor = 0.5\n        adjusted[i] = (1 - blend_factor) * adjusted[i] + blend_factor * inj\n        adjusted_sigma[i] = np.clip(adjusted_sigma[i] * (1.0 - 0.2 * symmetry_score), 1e-6, 0.1)\n        adjusted[i] = np.clip(adjusted[i], 1e-5, max_injection)\n    return adjusted, adjusted_sigma\n\ndef _phase_detector_signal(signal, cfg):\n    sl = cfg.MODEL_PHASE_DETECTION_SLICE\n    min_idx = int(np.argmin(signal[sl])) + sl.start\n    s1 = signal[:min_idx]; s2 = signal[min_idx:]\n    if s1.size < 3 or s2.size < 3:\n        return 0, len(signal) - 1\n    g1 = np.gradient(s1); g1_max = np.max(g1) if np.size(g1) else 0.0\n    g2 = np.gradient(s2); g2_max = np.max(g2) if np.size(g2) else 0.0\n    if g1_max != 0: g1 /= g1_max\n    if g2_max != 0: g2 /= g2_max\n    phase1 = int(np.argmin(g1)); phase2 = int(np.argmax(g2)) + min_idx\n    return phase1, phase2\n\ndef estimate_sigma_fgs(preprocessed_data, cfg):\n    sig_rel = []\n    delta = cfg.MODEL_OPTIMIZATION_DELTA\n    eps = 1e-12\n    for single in preprocessed_data:\n        air_white = savgol_filter(single[:, 1:].mean(axis=1), 20, 2)\n        p1, p2 = _phase_detector_signal(air_white, cfg)\n        p1 = max(delta, p1)\n        p2 = min(len(air_white) - delta - 1, p2)\n        fgs = single[:, 0]\n        oot = (fgs[: p1 - delta] if p1 - delta > 0 else np.empty(0, fgs.dtype))\n        if p2 + delta < fgs.size:\n            oot = np.concatenate([oot, fgs[p2 + delta :]])\n        inn = fgs[p1 + delta : max(p1 + delta, p2 - delta)]\n        if oot.size == 0 or inn.size == 0:\n            sig_rel.append(np.nan); continue\n        n_oot, n_in = len(oot), len(inn)\n        var_oot = np.nanvar(oot, ddof=1)\n        var_in  = np.nanvar(inn, ddof=1)\n        oot_mean = float(np.nanmean(oot)) if np.isfinite(np.nanmean(oot)) else float(np.nanmean(fgs))\n        sigma_rel = np.sqrt(var_oot / max(n_oot,1) + var_in / max(n_in,1)) / max(oot_mean, eps)\n        sig_rel.append(sigma_rel)\n    s = np.asarray(sig_rel, dtype=float)\n    mask = np.isfinite(s) & (s > 0)\n    med = float(np.nanmedian(s[mask])) if mask.any() else 1.0\n    k = np.ones_like(s)\n    if med > 0 and np.isfinite(med):\n        k[mask] = np.sqrt(s[mask] / med)\n    k = np.clip(k, 0.85, 1.30)\n    sigma_fgs = k * cfg.SIGMA * 1.04\n    return sigma_fgs\n\ndef estimate_sigma_air(preprocessed_data, cfg):\n    sig_rel = []\n    delta = cfg.MODEL_OPTIMIZATION_DELTA\n    eps = 1e-12\n    for single in preprocessed_data:\n        white = np.nanmean(single[:, 1:], axis=1)\n        white_s = savgol_filter(white, 20, 2)\n        p1, p2 = _phase_detector_signal(white_s, cfg)\n        p1 = max(delta, p1)\n        p2 = min(len(white) - delta - 1, p2)\n        oot_left = white[: p1 - delta] if p1 - delta > 0 else np.empty(0, white.dtype)\n        oot_right = white[p2 + delta :] if (p2 + delta) < white.size else np.empty(0, white.dtype)\n        oot = np.concatenate([oot_left, oot_right]) if (oot_left.size + oot_right.size) else oot_left\n        inn = white[p1 + delta : max(p1 + delta, p2 - delta)]\n        if oot.size == 0 or inn.size == 0:\n            sig_rel.append(np.nan); continue\n        n_oot, n_in = len(oot), len(inn)\n        var_oot = np.nanvar(oot, ddof=1)\n        var_in  = np.nanvar(inn, ddof=1)\n        oot_mean = float(np.nanmean(oot)) if np.isfinite(np.nanmean(oot)) else float(np.nanmean(white))\n        sigma_rel = np.sqrt(var_oot / max(n_oot,1) + var_in / max(n_in,1)) / max(oot_mean, eps)\n        sig_rel.append(sigma_rel)\n    s = np.asarray(sig_rel, dtype=float)\n    mask = np.isfinite(s) & (s > 0)\n    med = float(np.nanmedian(s[mask])) if mask.any() else 1.0\n    k = np.ones_like(s)\n    if med > 0 and np.isfinite(med):\n        k[mask] = np.sqrt(s[mask] / med)\n    k = np.clip(k, 0.92, 1.22)\n    sigma_air = k * cfg.SIGMA * 1.04\n    return sigma_air\n\nclass SignalProcessor:\n    def __init__(self, config):\n        self.cfg = config\n        self.adc_info = pd.read_csv(f\"{self.cfg.DATA_PATH}/adc_info.csv\")\n        self.planet_ids = pd.read_csv(f'{self.cfg.DATA_PATH}/{self.cfg.DATASET}_star_info.csv', index_col='planet_id').index.astype(int)\n\n    def _apply_linear_corr(self, linear_corr, signal):\n        coeffs = np.flip(linear_corr, axis=0)\n        x = signal.astype(np.float64, copy=False)\n        out = np.empty_like(x, dtype=np.float64)\n        out[...] = coeffs[0]\n        for k in range(1, coeffs.shape[0]):\n            np.multiply(out, x, out=out)\n            out += coeffs[k]\n        return out.astype(signal.dtype, copy=False)\n\n    def _calibrate_single_signal(self, planet_id, sensor):\n        sensor_cfg = self.cfg.SENSOR_CONFIG[sensor]\n        signal = pd.read_parquet(f\"{self.cfg.DATA_PATH}/{self.cfg.DATASET}/{planet_id}/{sensor}_signal_0.parquet\").to_numpy()\n        dark = pd.read_parquet(f\"{self.cfg.DATA_PATH}/{self.cfg.DATASET}/{planet_id}/{sensor}_calibration_0/dark.parquet\").to_numpy()\n        dead = pd.read_parquet(f\"{self.cfg.DATA_PATH}/{self.cfg.DATASET}/{planet_id}/{sensor}_calibration_0/dead.parquet\").to_numpy()\n        flat = pd.read_parquet(f\"{self.cfg.DATA_PATH}/{self.cfg.DATASET}/{planet_id}/{sensor}_calibration_0/flat.parquet\").to_numpy()\n        linear_corr = pd.read_parquet(f\"{self.cfg.DATA_PATH}/{self.cfg.DATASET}/{planet_id}/{sensor}_calibration_0/linear_corr.parquet\").values.astype(np.float64).reshape(sensor_cfg[\"linear_corr_shape\"])\n        signal = signal.reshape(sensor_cfg[\"raw_shape\"])\n        gain = self.adc_info[f\"{sensor}_adc_gain\"].iloc[0]\n        offset = self.adc_info[f\"{sensor}_adc_offset\"].iloc[0]\n        signal = signal / gain + offset\n        hot = sigma_clip(dark, sigma=5, maxiters=5).mask\n        if sensor == \"AIRS-CH0\":\n            signal = signal[:, :, self.cfg.CUT_INF : self.cfg.CUT_SUP]\n            linear_corr = linear_corr[:, :, self.cfg.CUT_INF : self.cfg.CUT_SUP]\n            dark = dark[:, self.cfg.CUT_INF : self.cfg.CUT_SUP]\n            dead = dead[:, self.cfg.CUT_INF : self.cfg.CUT_SUP]\n            flat = flat[:, self.cfg.CUT_INF : self.cfg.CUT_SUP]\n            hot = hot[:, self.cfg.CUT_INF : self.cfg.CUT_SUP]\n        if sensor == \"FGS1\":\n            y0, y1, x0, x1 = 10, 22, 10, 22\n            signal = signal[:, y0:y1, x0:x1]\n            dark   = dark[y0:y1, x0:x1]\n            dead   = dead[y0:y1, x0:x1]\n            flat   = flat[y0:y1, x0:x1]\n            linear_corr = linear_corr[:, y0:y1, x0:x1]\n            hot    = hot[y0:y1, x0:x1]\n        np.maximum(signal, 0, out=signal)\n        if sensor == \"FGS1\":\n            signal = self._apply_linear_corr(linear_corr, signal)\n        elif sensor == \"AIRS-CH0\":\n            sl = (slice(None), slice(10, 22), slice(None))\n            signal[sl] = self._apply_linear_corr(linear_corr[:, 10:22, :], signal[sl])\n        else:\n            signal = self._apply_linear_corr(linear_corr, signal)\n        base_dt, increment = sensor_cfg[\"dt_pattern\"]\n        even_scale = base_dt\n        odd_scale  = base_dt + increment\n        signal[::2]  -= dark * even_scale\n        signal[1::2] -= dark * odd_scale\n        return signal\n\n    def _preprocess_calibrated_signal(self, calibrated_signal, sensor):\n        sensor_cfg = self.cfg.SENSOR_CONFIG[sensor]\n        binning = sensor_cfg[\"binning\"]\n        if sensor == \"AIRS-CH0\":\n            signal_roi = calibrated_signal[:, 10:22, :]\n        elif sensor == \"FGS1\":\n            signal_roi = calibrated_signal[:, 10:22, 10:22]\n            signal_roi = signal_roi.reshape(signal_roi.shape[0], -1)\n        mean_signal = np.nanmean(signal_roi, axis=1)\n        cds_signal = mean_signal[1::2] - mean_signal[0::2]\n        n_bins = cds_signal.shape[0] // binning\n        binned = np.array([cds_signal[j*binning : (j+1)*binning].mean(axis=0) for j in range(n_bins)])\n        if sensor == \"AIRS-CH0\":\n            q_lo = np.nanpercentile(binned, 5.0, axis=1, keepdims=True)\n            q_hi = np.nanpercentile(binned, 95.0, axis=1, keepdims=True)\n            np.clip(binned, q_lo, q_hi, out=binned)\n        if sensor == \"FGS1\":\n            binned = binned.reshape((binned.shape[0], 1))\n        if sensor == \"AIRS-CH0\":\n            var = np.nanvar(binned, axis=0, ddof=1)\n            med = np.nanmedian(var)\n            safe_var = np.where(~np.isfinite(var) | (var <= 0), med if (np.isfinite(med) and med > 0) else 1.0, var)\n            w = 1.0 / safe_var\n            lo, hi = np.nanpercentile(w, 5.0), np.nanpercentile(w, 95.0)\n            if np.isfinite(lo) and np.isfinite(hi) and lo < hi:\n                w = np.clip(w, lo, hi)\n            M = binned.shape[1]\n            s = np.nansum(w)\n            if np.isfinite(s) and s > 0:\n                w = w * (M / s)\n            else:\n                w = np.ones_like(w)\n            binned *= w[None, :]\n        return binned\n\n    def _process_planet_sensor(self, args):\n        planet_id, sensor = args['planet_id'], args['sensor']\n        calibrated = self._calibrate_single_signal(planet_id, sensor)\n        preprocessed = self._preprocess_calibrated_signal(calibrated, sensor)\n        return preprocessed\n\n    def process_all_data(self):\n        args_fgs1 = [dict(planet_id=planet_id, sensor=\"FGS1\") for planet_id in self.planet_ids]\n        preprocessed_fgs1 = pqdm(args_fgs1, self._process_planet_sensor, n_jobs=self.cfg.N_JOBS)\n        args_airs_ch0 = [dict(planet_id=planet_id, sensor=\"AIRS-CH0\") for planet_id in self.planet_ids]\n        preprocessed_airs_ch0 = pqdm(args_airs_ch0, self._process_planet_sensor, n_jobs=self.cfg.N_JOBS)\n        preprocessed_signal = np.concatenate([np.stack(preprocessed_fgs1), np.stack(preprocessed_airs_ch0)], axis=2)\n        return preprocessed_signal\n\ndef diff_recursion_measure(signal, tol=0.01, max_iter=12):\n    lis = np.copy(signal)\n    depth = 0\n    while depth < max_iter and len(lis) > 2:\n        diffs = np.diff(lis)\n        mean = diffs.mean()\n        dev = np.mean(np.abs(diffs - mean))\n        depth += 1\n        if dev <= tol:\n            break\n        lis = diffs\n    return depth, dev\n\nclass TransitModel:\n    def __init__(self, config):\n        self.cfg = config\n\n    def _phase_detector(self, signal):\n        search_slice = self.cfg.MODEL_PHASE_DETECTION_SLICE\n        min_index = np.argmin(signal[search_slice]) + search_slice.start\n        signal1 = signal[:min_index]\n        signal2 = signal[min_index:]\n        if len(signal1) < 3 or len(signal2) < 3:\n            return max(min_index - 3, 0), min(min_index + 3, len(signal))\n        grad1 = np.gradient(signal1)\n        grad2 = np.gradient(signal2)\n        grad1 /= grad1.max() if grad1.max() != 0 else 1\n        grad2 /= grad2.max() if grad2.max() != 0 else 1\n        phase1 = np.argmin(grad1)\n        phase2 = np.argmax(grad2) + min_index\n        return phase1, phase2\n\n    def _objective_function(self, s, signal, phase1, phase2):\n        delta = self.cfg.MODEL_OPTIMIZATION_DELTA\n        \n        if phase1 - delta <= 0 or phase2 + delta >= len(signal) or phase2 - delta - (phase1 + delta) < 5:\n            delta = 2\n        \n        y = np.concatenate([\n            signal[: phase1 - delta],\n            signal[phase1 + delta : phase2 - delta] * (1 + s),\n            signal[phase2 + delta :]\n        ])\n        x = np.arange(len(y))\n        best_fit_depth, deviation_level = diff_recursion_measure(y)\n        degree = int(np.clip(3 + best_fit_depth // 50, 2, 4))\n        \n        coeffs = np.polyfit(x, y, deg=degree)\n        poly = np.poly1d(coeffs)\n        error = np.abs(poly(x) - y).mean()\n        \n        return error\n\n    def predict(self, single_preprocessed_signal):\n        signal_1d = single_preprocessed_signal[:, 1:].mean(axis=1)\n        signal_1d = savgol_filter(signal_1d, 23, 2)\n        \n        phase1, phase2 = self._phase_detector(signal_1d)\n        delta = self.cfg.MODEL_OPTIMIZATION_DELTA\n        phase1 = max(delta, phase1)\n        phase2 = min(len(signal_1d) - delta - 1, phase2)\n        \n        if phase2 - phase1 < 3:\n            return 0.0\n        \n        result = minimize(\n            fun=self._objective_function, \n            x0=[0.0001],\n            args=(signal_1d, phase1, phase2),\n            method=\"Nelder-Mead\"\n        )\n        \n        return result.x[0] if result.success else 0.0\n\n    def predict_all(self, preprocessed_data):\n        \"\"\"Returns scaled predictions like the working version\"\"\"\n        predictions = [self.predict(signal) for signal in tqdm(preprocessed_data)]\n        return np.array(predictions) * self.cfg.SCALE\n\nStarInfo = pd.read_csv(ROOT_PATH + f\"/{MODE}_star_info.csv\")\nStarInfo[\"planet_id\"] = StarInfo[\"planet_id\"].astype(int)\nPlanetIds = StarInfo[\"planet_id\"].tolist()\nStarInfo = StarInfo.set_index(\"planet_id\")\n\nclass ResidualBlock2(nn.Module):\n    def __init__(self, dim, p=0.2):\n        super().__init__()\n        self.fc1 = nn.Linear(dim, dim)\n        self.bn1 = nn.BatchNorm1d(dim)\n        self.fc2 = nn.Linear(dim, dim)\n        self.bn2 = nn.BatchNorm1d(dim)\n        self.relu = nn.ReLU()\n        self.dropout = nn.Dropout(p)\n\n    def forward(self, x):\n        identity = x\n        out = self.relu(self.bn1(self.fc1(x)))\n        out = self.dropout(out)\n        out = self.bn2(self.fc2(out))\n        return self.relu(out + identity)\n\nclass ResNetMLP2(nn.Module):\n    def __init__(self, input_dim=3, hidden_dim=128, output_dim=282, num_blocks=3, dropout_rate=0.2):\n        super().__init__()\n        self.input_layer = nn.Linear(input_dim, hidden_dim)\n        self.blocks = nn.Sequential(*[ResidualBlock2(hidden_dim, p=dropout_rate) for _ in range(num_blocks)])\n        self.output_layer = nn.Linear(hidden_dim, output_dim)\n\n    def forward(self, x):\n        x = self.input_layer(x)\n        x = self.blocks(x)\n        x = self.output_layer(x)\n        return x\n\nclass SubmissionGenerator:\n    def __init__(self, config):\n        self.cfg = config\n        self.sample_submission = pd.read_csv(\"/kaggle/input/ariel-data-challenge-2025/sample_submission.csv\", index_col=\"planet_id\")\n\n    def create(self, predictions1, predictions, sigma_fgs=None, sigma_air=None):\n        planet_ids = self.sample_submission.index\n        n_planets = len(planet_ids)\n        n_mu = self.sample_submission.shape[1] // 2\n        predictions1 = np.nan_to_num(predictions1, nan=0.0, posinf=0.0, neginf=0.0)\n        predictions = np.nan_to_num(predictions, nan=0.0, posinf=0.0, neginf=0.0)\n        if predictions.ndim > 1:\n            predictions = predictions.flatten()\n        if len(predictions) < n_planets:\n            predictions = np.pad(predictions, (0, n_planets - len(predictions)), constant_values=0.0)\n        elif len(predictions) > n_planets:\n            predictions = predictions[:n_planets]\n        if sigma_fgs is not None:\n            sigma_fgs = np.nan_to_num(sigma_fgs, nan=self.cfg.SIGMA, posinf=self.cfg.SIGMA, neginf=self.cfg.SIGMA)\n            if len(sigma_fgs) < n_planets:\n                sigma_fgs = np.pad(sigma_fgs, (0, n_planets - len(sigma_fgs)), constant_values=self.cfg.SIGMA)\n            elif len(sigma_fgs) > n_planets:\n                sigma_fgs = sigma_fgs[:n_planets]\n        if sigma_air is not None:\n            sigma_air = np.nan_to_num(sigma_air, nan=self.cfg.SIGMA, posinf=self.cfg.SIGMA, neginf=self.cfg.SIGMA)\n            if len(sigma_air) < n_planets:\n                sigma_air = np.pad(sigma_air, (0, n_planets - len(sigma_air)), constant_values=self.cfg.SIGMA)\n            elif len(sigma_air) > n_planets:\n                sigma_air = sigma_air[:n_planets]\n        if predictions1.shape[0] < n_planets:\n            pad_rows = n_planets - predictions1.shape[0]\n            predictions1 = np.vstack([predictions1, np.zeros((pad_rows, predictions1.shape[1]))])\n        elif predictions1.shape[0] > n_planets:\n            predictions1 = predictions1[:n_planets]\n        if predictions1.shape[1] < n_mu:\n            pad_cols = n_mu - predictions1.shape[1]\n            predictions1 = np.hstack([predictions1, np.zeros((predictions1.shape[0], pad_cols))])\n        elif predictions1.shape[1] > n_mu:\n            predictions1 = predictions1[:, :n_mu]\n        mu = np.zeros((n_planets, n_mu), dtype=float)\n        mu[:, 0] = np.clip(predictions, 0, None)\n        mu[:, 1:] = np.clip(predictions1[:, 1:], 0, None)\n        sigmas = np.full((n_planets, n_mu), self.cfg.SIGMA, dtype=float)\n        if sigma_fgs is not None:\n            sigmas[:, 0] = np.clip(sigma_fgs, 1e-6, 0.1)\n        if sigma_air is not None:\n            sigmas[:, 1:] = np.clip(sigma_air[:, None], 1e-6, 0.1)\n        submission_df = pd.DataFrame(np.concatenate([mu, sigmas], axis=1), columns=self.sample_submission.columns, index=planet_ids)\n        submission_df = submission_df.replace([np.inf, -np.inf], 1e-5).fillna(1e-5)\n        submission_df = submission_df.clip(lower=0.0)\n        submission_df.to_csv(\"submission.csv\", index_label=\"planet_id\")\n        print(f\"[INFO] submission.csv written successfully with shape: {submission_df.shape}\")\n        return submission_df\n\nif __name__ == \"__main__\":\n    config = Config()\n    signal_processor = SignalProcessor(config)\n    preprocessed_data = signal_processor.process_all_data()\n    \n    model = TransitModel(config)\n    predictions = model.predict_all(preprocessed_data)  # Already scaled with SCALE factor\n    \n    sigma_fgs_vec = estimate_sigma_fgs(preprocessed_data, config)\n    sigma_air_vec = estimate_sigma_air(preprocessed_data, config)\n    \n    predictions_corrected = predictions.copy()\n    sigma_air_corrected = sigma_air_vec.copy()\n    \n    # Use median-based mask like working version\n    mask = predictions < (np.median(predictions) * 0.1)\n    \n    if mask.any():\n        adjusted, adjusted_sigma = log_model_ensemble_adjust(\n            preprocessed_data[mask], \n            predictions[mask], \n            sigma_air_vec[mask], \n            log_threshold=0.75, \n            window_size=4\n        )\n        alpha = 0.5\n        predictions_corrected[mask] = alpha * adjusted + (1 - alpha) * predictions[mask]\n        sigma_air_corrected[mask] = adjusted_sigma\n    \n    predictions_df = pd.DataFrame({\"planet_id\": PlanetIds, \"transit_depth\": predictions_corrected})\n    input_df = pd.merge(predictions_df, StarInfo, on=\"planet_id\", how=\"left\")\n    input_df[\"transit_depth\"] *= 10000\n    \n    features = [\"transit_depth\", \"Rs\", \"i\"]\n    X = input_df[features].values.astype(np.float32)\n    X_tensor = torch.tensor(X, dtype=torch.float32)\n    \n    resnet2 = ResNetMLP2(num_blocks=80, dropout_rate=0.3)\n    resnet2.load_state_dict(torch.load(\"/kaggle/input/airs/pytorch/default/1/best_model_airs.pth\", map_location=\"cpu\"))\n    resnet2.eval()\n    \n    with torch.no_grad():\n        predictions1 = resnet2(X_tensor).numpy()\n    predictions1 /= 10000\n    \n    if predictions1.shape[1] >= 5:\n        predictions1_smooth = savgol_filter(\n            predictions1, \n            window_length=min(13, predictions1.shape[1] // 2 * 2 - 1), \n            polyorder=2, \n            axis=1\n        )\n        predictions1 = 0.7 * predictions1 + 0.3 * predictions1_smooth\n    \n    sigma_fgs_vec = np.nan_to_num(sigma_fgs_vec, nan=config.SIGMA, posinf=config.SIGMA, neginf=config.SIGMA)\n    sigma_air_corrected = np.nan_to_num(sigma_air_corrected, nan=config.SIGMA, posinf=config.SIGMA, neginf=config.SIGMA)\n    \n    submission_generator = SubmissionGenerator(config)\n    submission = submission_generator.create(\n        predictions1, \n        predictions_corrected, \n        sigma_fgs=sigma_fgs_vec, \n        sigma_air=sigma_air_corrected\n    )\n    \n    __t1 = time.perf_counter()\n    elapsed = __t1 - __t0\n    print(f\"[TIMING] total runtime: {elapsed:.2f} s ({elapsed/60:.2f} min)\")\n    print(submission.head())","metadata":{"_uuid":"e6844afc-ace1-4ef0-aa01-84dc82ec03aa","_cell_guid":"4dc63a37-342e-487c-8418-cd2741fcfffb","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-10-05T19:32:36.886873Z","iopub.execute_input":"2025-10-05T19:32:36.887867Z","iopub.status.idle":"2025-10-05T19:32:46.082765Z","shell.execute_reply.started":"2025-10-05T19:32:36.887812Z","shell.execute_reply":"2025-10-05T19:32:46.081697Z"}},"outputs":[],"execution_count":null}]}