{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":118765,"databundleVersionId":15231210,"sourceType":"competition"},{"sourceId":14595185,"sourceType":"datasetVersion","datasetId":9322893}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Stanford RNA 3D Folding - Template-Based Baseline (FIXED: Proper NaN Handling)\n\nimport sys\nimport os\nimport numpy as np\nimport pandas as pd\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"🧬 Stanford RNA 3D Folding - Template-Based Predictor (NaN-Fixed v2)\")\nprint(\"=\" * 70)\n\n# === 1. PATHS ===\nDATA_PATH = '/kaggle/input/stanford-rna-3d-folding-2/'\n\n# === 2. LOAD DATA ===\nprint(\"📊 Loading data...\")\ntrain_seqs = pd.read_csv(DATA_PATH + 'train_sequences.csv')\ntest_seqs = pd.read_csv(DATA_PATH + 'test_sequences.csv')\ntrain_labels = pd.read_csv(DATA_PATH + 'train_labels.csv')\n\ntry:\n    validation_seqs = pd.read_csv(DATA_PATH + 'validation_sequences.csv')\n    validation_labels = pd.read_csv(DATA_PATH + 'validation_labels.csv')\n    train_seqs = pd.concat([train_seqs, validation_seqs], ignore_index=True)\n    train_labels = pd.concat([train_labels, validation_labels], ignore_index=True)\n    print(\"✅ Merged train + validation data\")\nexcept:\n    print(\"⚠️ Using train data only\")\n\nprint(f\"Train sequences: {len(train_seqs)}\")\nprint(f\"Test sequences: {len(test_seqs)}\")\nprint(f\"Train labels: {len(train_labels)}\")\n\n# === 3. PROCESS TRAINING COORDINATES (FIXED!) ===\nprint(\"\\n🔧 Processing training structures...\")\n\ndef interpolate_nan_coords(coords):\n    \"\"\"Fill NaN coordinates using linear interpolation\"\"\"\n    coords = coords.copy()  # Don't modify original\n    n_residues = len(coords)\n\n    for dim in range(3):  # x, y, z\n        column = coords[:, dim]\n\n        # Find NaN positions\n        nan_mask = np.isnan(column)\n\n        if nan_mask.all():\n            # All NaN in this dimension - use zeros\n            coords[:, dim] = 0.0\n            continue\n\n        if nan_mask.any():\n            # Some NaN - interpolate from valid values\n            valid_indices = np.where(~nan_mask)[0]\n            valid_values = column[~nan_mask]\n\n            # Interpolate\n            nan_indices = np.where(nan_mask)[0]\n            coords[nan_mask, dim] = np.interp(\n                nan_indices,\n                valid_indices, \n                valid_values,\n                left=valid_values[0],   # Extrapolate edges\n                right=valid_values[-1]\n            )\n\n    return coords\n\ndef process_labels(labels_df):\n    \"\"\"Extract 3D coordinates with smart NaN handling\"\"\"\n    coords_dict = {}\n    skipped = 0\n    fixed = 0\n\n    for id_prefix, group in labels_df.groupby(\n        lambda x: labels_df['ID'][x].rsplit('_', 1)[0]\n    ):\n        coords = group.sort_values('resid')[['x_1', 'y_1', 'z_1']].values\n\n        # Check for NaN\n        if np.isnan(coords).any():\n            # Count NaN residues\n            nan_count = np.isnan(coords).any(axis=1).sum()\n            total_residues = len(coords)\n            nan_pct = nan_count / total_residues * 100\n\n            # Skip only if >50% are NaN (structure too damaged)\n            if nan_pct > 50:\n                skipped += 1\n                continue\n\n            # Otherwise, interpolate NaN values\n            coords = interpolate_nan_coords(coords)\n            fixed += 1\n\n        coords_dict[id_prefix] = coords\n\n    print(f\"✅ Processed {len(coords_dict)} structures\")\n    if fixed > 0:\n        print(f\"   Fixed {fixed} structures with NaN interpolation\")\n    if skipped > 0:\n        print(f\"   Skipped {skipped} structures (>50% NaN)\")\n    return coords_dict\n\ntrain_coords = process_labels(train_labels)\nprint(f\"✅ Total usable templates: {len(train_coords)}\")\n\n# === 4. TEMPLATE MATCHING ===\nfrom Bio import pairwise2\nfrom Bio.Seq import Seq\n\ndef find_best_template(query_seq, train_seqs_df, train_coords, top_k=3):\n    \"\"\"\n    Find best template structure using sequence alignment\n    Returns ensemble of top-k templates\n    \"\"\"\n    candidates = []\n\n    for _, row in train_seqs_df.iterrows():\n        tid, tseq = row['target_id'], row['sequence']\n\n        # Skip if no coordinates available\n        if tid not in train_coords:\n            continue\n\n        # Skip if length difference is too large\n        len_ratio = abs(len(tseq) - len(query_seq)) / max(len(tseq), len(query_seq))\n        if len_ratio > 0.4:\n            continue\n\n        # Perform sequence alignment\n        try:\n            alignments = pairwise2.align.globalms(\n                Seq(query_seq), \n                tseq, \n                match=2,        # Match score\n                mismatch=-1,    # Mismatch penalty\n                open=-8,        # Gap open penalty\n                extend=-0.3,    # Gap extension penalty\n                one_alignment_only=True\n            )\n\n            if alignments:\n                score = alignments[0].score / (2 * min(len(query_seq), len(tseq)))\n                candidates.append((score, tid, train_coords[tid]))\n        except Exception:\n            continue\n\n    # Sort by score and get top-k\n    candidates.sort(reverse=True, key=lambda x: x[0])\n\n    if not candidates:\n        return None\n\n    # Return coordinates from top candidates\n    templates = []\n    for i in range(min(top_k, len(candidates))):\n        score, tid, coords = candidates[i]\n        templates.append(coords)\n\n    return templates if templates else None\n\ndef align_coordinates(template_coords, target_length):\n    \"\"\"\n    Align template coordinates to target sequence length\n    \"\"\"\n    template_len = len(template_coords)\n\n    if template_len == target_length:\n        return template_coords.copy()\n\n    elif template_len > target_length:\n        # Truncate or sample\n        indices = np.linspace(0, template_len - 1, target_length, dtype=int)\n        result = template_coords[indices]\n\n    else:\n        # Interpolate to extend\n        indices = np.linspace(0, template_len - 1, target_length)\n        result = np.zeros((target_length, 3))\n\n        for dim in range(3):\n            result[:, dim] = np.interp(\n                indices,\n                np.arange(template_len),\n                template_coords[:, dim]\n            )\n\n    return result\n\ndef generate_linear_fallback(sequence_length, seed=42):\n    \"\"\"\n    Generate a simple linear structure as fallback\n    \"\"\"\n    np.random.seed(seed)\n    coords = np.zeros((sequence_length, 3))\n    coords[:, 0] = np.arange(sequence_length) * 4.0  # 4Å spacing\n    # Add small random noise\n    noise = np.random.randn(sequence_length, 3) * 0.5\n    coords += noise\n    return coords\n\n# === 5. PREDICTION FUNCTION ===\ndef predict_structure(sequence, n_predictions=5, idx=0):\n    \"\"\"\n    Generate ensemble predictions for a sequence\n    \"\"\"\n    # Find best templates\n    templates = find_best_template(sequence, train_seqs, train_coords, top_k=min(n_predictions, 3))\n\n    predictions = []\n\n    if templates is None or len(templates) == 0:\n        # Fallback: generate linear structures with noise\n        for i in range(n_predictions):\n            pred = generate_linear_fallback(len(sequence), seed=42 + idx * 100 + i)\n            predictions.append(pred)\n    else:\n        # Use templates with small perturbations\n        for i in range(n_predictions):\n            np.random.seed(42 + idx * 100 + i)\n\n            # Select template (cycle if needed)\n            template = templates[i % len(templates)]\n\n            # Align to target length\n            aligned = align_coordinates(template, len(sequence))\n\n            # Add small noise for diversity\n            noise = np.random.randn(*aligned.shape) * 0.3\n            pred = aligned + noise\n\n            predictions.append(pred)\n\n    return predictions\n\n# === 6. GENERATE PREDICTIONS ===\nprint(\"\\n🚀 Predicting test structures...\")\nprint(\"=\" * 70)\n\nall_predictions = []\n\nfor idx, row in test_seqs.iterrows():\n    target_id = row['target_id']\n    sequence = row['sequence']\n\n    if idx % 5 == 0:\n        print(f\"Processing {idx+1}/{len(test_seqs)}: {target_id} (length={len(sequence)})\")\n\n    try:\n        # Generate ensemble predictions\n        predictions = predict_structure(sequence, n_predictions=5, idx=idx)\n\n        # Ensure correct dimensions\n        for i in range(len(predictions)):\n            pred = predictions[i]\n            if len(pred) != len(sequence):\n                # Force alignment if mismatch\n                predictions[i] = align_coordinates(pred, len(sequence))\n\n    except Exception as e:\n        print(f\"⚠️ Error for {target_id}: {e}\")\n        # Fallback\n        predictions = []\n        for i in range(5):\n            predictions.append(generate_linear_fallback(len(sequence), seed=42 + idx * 100 + i))\n\n    # Format output - ONE ROW PER RESIDUE\n    for j in range(len(sequence)):\n        row_data = {\n            'ID': f\"{target_id}_{j+1}\",\n            'resname': sequence[j],\n            'resid': j + 1\n        }\n\n        # Add predictions in CORRECT order: x_1, y_1, z_1, x_2, y_2, z_2, ...\n        for i in range(5):\n            row_data[f'x_{i+1}'] = float(predictions[i][j, 0])\n            row_data[f'y_{i+1}'] = float(predictions[i][j, 1])\n            row_data[f'z_{i+1}'] = float(predictions[i][j, 2])\n\n        all_predictions.append(row_data)\n\n# === 7. CREATE SUBMISSION WITH CORRECT COLUMN ORDER ===\nprint(\"\\n📝 Creating submission file...\")\n\n# Create DataFrame\nsubmission_df = pd.DataFrame(all_predictions)\n\n# Fill any NaN with 0.0\nnan_before = submission_df.isna().sum().sum()\nif nan_before > 0:\n    print(f\"⚠️ Found {nan_before} NaN values, filling with 0.0...\")\n    submission_df = submission_df.fillna(0.0)\n\n# CRITICAL: Define exact column order to match sample\ncolumn_order = ['ID', 'resname', 'resid'] + \\\n               [f'{coord}_{i}' for i in range(1, 6) for coord in ['x', 'y', 'z']]\n\n# Reorder columns and save\nsubmission_df = submission_df[column_order]\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"✅ SUBMISSION COMPLETE!\")\nprint(\"=\" * 70)\nprint(f\"📄 File: submission.csv\")\nprint(f\"📊 Total predictions: {len(submission_df):,} rows\")\nprint(f\"🎯 Targets: {len(test_seqs)} RNA structures\")\nprint(f\"🔬 Method: Template-based with sequence alignment + NaN interpolation\")\n\n# === 8. VALIDATION ===\nprint(\"\\n🔍 Validating submission format...\")\n\n# Check columns\nexpected_cols = ['ID', 'resname', 'resid'] + \\\n                [f'{c}_{i}' for i in range(1,6) for c in ['x','y','z']]\nassert list(submission_df.columns) == expected_cols, \"Column order mismatch!\"\n\n# Check for NaN\nnan_total = submission_df.isna().sum().sum()\nassert nan_total == 0, f\"Found {nan_total} NaN values!\"\n\n# Check for inf\ninf_mask = np.isinf(submission_df.select_dtypes(include=[np.number]).values)\nassert not inf_mask.any(), \"Found inf values!\"\n\n# Check residue counts per target\ntest_counts = test_seqs.groupby('target_id')['sequence'].apply(len)\nsub_counts = submission_df.groupby(submission_df['ID'].str.rsplit('_', n=1).str[0]).size()\nassert all(test_counts == sub_counts), \"Residue count mismatch!\"\n\nprint(\"✅ All validation checks passed!\")\nprint(f\"✅ Columns: {len(submission_df.columns)} (expected 18)\")\nprint(f\"✅ Rows: {len(submission_df):,}\")\nprint(f\"✅ No missing values\")\nprint(f\"✅ No infinite values\")\nprint(f\"✅ All targets present\")\n\nprint(\"\\n🎉 Ready to submit!\")\nprint(f\"\\n💡 Sample predictions (first target, first 3 residues):\")\nprint(submission_df.head(3)[['ID', 'x_1', 'y_1', 'z_1']])\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}