{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.12"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":118765,"databundleVersionId":15231210,"sourceType":"competition"},{"sourceId":14604295,"sourceType":"datasetVersion","datasetId":9328538}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":2354.045049,"end_time":"2026-01-13T06:54:44.524542","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-01-13T06:15:30.479493","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install --no-index /kaggle/input/biopython-cp312/biopython-1.86-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T15:16:34.187731Z","iopub.execute_input":"2026-01-31T15:16:34.188021Z","iopub.status.idle":"2026-01-31T15:16:39.626411Z","shell.execute_reply.started":"2026-01-31T15:16:34.187994Z","shell.execute_reply":"2026-01-31T15:16:39.625419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport random\nimport time\nimport warnings\nimport os, sys\nfrom Bio.Align import PairwiseAligner\n\nwarnings.filterwarnings('ignore')\n\n# ======================================================\n# 1. КОНФИГУРАЦИЯ И ГИПЕРПАРАМЕТРЫ\n# ======================================================\nclass Config:\n    # --- ПУТИ К ДАННЫМ ---\n    DATA_PATH = '/kaggle/input/stanford-rna-3d-folding-2/'\n    TRAIN_SEQS_FILE = 'train_sequences.csv'\n    TEST_SEQS_FILE = 'test_sequences.csv'\n    TRAIN_LABELS_FILE = 'train_labels.csv'\n    EXTRA_SCRIPTS_DIR = 'extra' # Подпапка со скриптами, если есть\n    OUTPUT_FILE = 'submission.csv'\n\n    # --- ПАРАМЕТРЫ ВЫРАВНИВАНИЯ (ALIGNMENT) ---\n    # Влияют на то, как мы находим похожие последовательности\n    ALIGN_MODE = 'global'\n    MATCH_SCORE = 2\n    MISMATCH_SCORE = -1.5\n    \n    # Штрафы за пропуски (Gaps)\n    # Сильные штрафы предотвращают \"сдвиги\", сохраняя нумерацию остатков\n    OPEN_GAP_SCORE = -8\n    EXTEND_GAP_SCORE = -0.42\n    \n    # --- ПАРАМЕТРЫ ОТБОРА ШАБЛОНОВ ---\n    TOP_N_PREDICTIONS = 5           # Сколько вариантов предсказывать (для сабмита нужно 5)\n    LENGTH_FILTER_RATIO = 0.25      # Максимальная разница в длине (30%) для отсева кандидатов\n\n    # --- ПАРАМЕТРЫ ФИЗИЧЕСКИХ ОГРАНИЧЕНИЙ (PHYSICS/GEOMETRY) ---\n    # Используются для корректировки 3D координат\n    CONSTRAINT_STRENGTH_BASE = 0.65     # Базовая сила притягивания к идеальной геометрии\n    CONFIDENCE_CUTOFF = 0.98          # Порог уверенности для ослабления силы\n    \n    # Идеальные расстояния между атомами фосфора (P-P distance) в РНК\n    NEIGHBOR_DIST_1 = 5.95              # Расстояние до соседа (i, i+1)\n    NEIGHBOR_FACTOR_1 = 0.4           # Вес корректировки для соседа\n    \n    NEIGHBOR_DIST_2 = 10.2              # Расстояние через одного (i, i+2)\n    NEIGHBOR_FACTOR_2 = 0.25            # Вес корректировки через одного\n\n    # --- ПАРАМЕТРЫ ШУМА (NOISE INJECTION) ---\n    # Добавление шума помогает модели \"разнообразить\" предсказания\n    NOISE_MIN = 0.008\n    NOISE_THRESHOLD = 0.34\n    NOISE_SCALE = 0.05\n    \n    # --- FALLBACK ---\n    FALLBACK_STEP = 4.0 # Шаг решетки, если шаблон не найден\n\n# ======================================================\n# 2. ЗАГРУЗКА ДАННЫХ И УТИЛИТЫ\n# ======================================================\n\nsys.path.append(os.path.join(Config.DATA_PATH, Config.EXTRA_SCRIPTS_DIR))\n\n# --- Robust import for Kaggle's extra/parse_fasta_py.py ---\ntry:\n    import typing as _typing\n    import builtins as _builtins\n    _builtins.Dict  = getattr(_typing, \"Dict\")\n    _builtins.Tuple = getattr(_typing, \"Tuple\")\n    _builtins.List  = getattr(_typing, \"List\")\n    from parse_fasta_py import parse_fasta as _parse_fasta_raw\n\n    def parse_fasta(fasta_content: str):\n        d = _parse_fasta_raw(fasta_content)\n        out = {}\n        for k, v in d.items():\n            out[k] = v[0] if isinstance(v, tuple) else v\n        return out\nexcept Exception:\n    # Fallback FASTA parser\n    def parse_fasta(fasta_content: str):\n        out = {}\n        cur = None\n        seq_parts = []\n        for line in str(fasta_content).splitlines():\n            line = line.strip()\n            if not line: continue\n            if line.startswith(\">\"):\n                if cur is not None: out[cur] = \"\".join(seq_parts)\n                header = line[1:]\n                cur = header.split()[0]\n                seq_parts = []\n            else:\n                seq_parts.append(line.replace(\" \", \"\"))\n        if cur is not None: out[cur] = \"\".join(seq_parts)\n        return out\n\ndef parse_stoichiometry(stoich: str):\n    if pd.isna(stoich) or str(stoich).strip() == \"\": return []\n    out = []\n    for part in str(stoich).split(';'):\n        ch, cnt = part.split(':')\n        out.append((ch.strip(), int(cnt)))\n    return out\n\ndef get_chain_segments(row):\n    seq = row['sequence']\n    stoich = row.get('stoichiometry', '')\n    all_seq = row.get('all_sequences', '')\n    if pd.isna(stoich) or pd.isna(all_seq) or str(stoich).strip()==\"\" or str(all_seq).strip()==\"\":\n        return [(0, len(seq))]\n    try:\n        chain_dict = parse_fasta(all_seq)\n        order = parse_stoichiometry(stoich)\n        segs = []\n        pos = 0\n        for ch, cnt in order:\n            base = chain_dict.get(ch)\n            if base is None: return [(0, len(seq))]\n            for _ in range(cnt):\n                L = len(base)\n                segs.append((pos, pos + L))\n                pos += L\n        if pos != len(seq): return [(0, len(seq))]\n        return segs\n    except Exception:\n        return [(0, len(seq))]\n\ndef build_segments_map(df):\n    seg_map = {}\n    stoich_map = {}\n    for _, r in df.iterrows():\n        tid = r['target_id']\n        seg_map[tid] = get_chain_segments(r)\n        stoich_map[tid] = str(r.get('stoichiometry', '') if not pd.isna(r.get('stoichiometry', '')) else '')\n    return seg_map, stoich_map\n\ndef process_labels(labels_df):\n    coords_dict = {}\n    prefixes = labels_df['ID'].str.rsplit('_', n=1).str[0]\n    for id_prefix, group in labels_df.groupby(prefixes):\n        coords_dict[id_prefix] = group.sort_values('resid')[['x_1', 'y_1', 'z_1']].values\n    return coords_dict\n\n# Load Data\nprint(\"Loading data...\")\ntrain_seqs = pd.read_csv(os.path.join(Config.DATA_PATH, Config.TRAIN_SEQS_FILE))\ntest_seqs = pd.read_csv(os.path.join(Config.DATA_PATH, Config.TEST_SEQS_FILE))\ntrain_labels = pd.read_csv(os.path.join(Config.DATA_PATH, Config.TRAIN_LABELS_FILE))\n\ntrain_segs_map, train_stoich_map = build_segments_map(train_seqs)\ntest_segs_map,  test_stoich_map  = build_segments_map(test_seqs)\ntrain_coords_dict = process_labels(train_labels)\n\n# ======================================================\n# 3. НАСТРОЙКА ALIGNER (ВЫРАВНИВАНИЕ)\n# ======================================================\naligner = PairwiseAligner()\naligner.mode = Config.ALIGN_MODE\naligner.match_score = Config.MATCH_SCORE\naligner.mismatch_score = Config.MISMATCH_SCORE\naligner.open_gap_score   = Config.OPEN_GAP_SCORE\naligner.extend_gap_score = Config.EXTEND_GAP_SCORE\n\n# Penalize terminal gaps same as internal\naligner.query_left_open_gap_score   = Config.OPEN_GAP_SCORE\naligner.query_left_extend_gap_score = Config.EXTEND_GAP_SCORE\naligner.query_right_open_gap_score = Config.OPEN_GAP_SCORE\naligner.query_right_extend_gap_score = Config.EXTEND_GAP_SCORE\naligner.target_left_open_gap_score = Config.OPEN_GAP_SCORE\naligner.target_left_extend_gap_score = Config.EXTEND_GAP_SCORE\naligner.target_right_open_gap_score = Config.OPEN_GAP_SCORE\naligner.target_right_extend_gap_score = Config.EXTEND_GAP_SCORE\n\n# ======================================================\n# 4. ОСНОВНАЯ ЛОГИКА\n# ======================================================\n\ndef find_similar_sequences(query_seq, train_seqs_df, train_coords_dict, top_n=5):\n    similar_seqs = []\n    \n    for _, row in train_seqs_df.iterrows():\n        target_id, train_seq = row['target_id'], row['sequence']\n        if target_id not in train_coords_dict: continue\n        \n        # Length filter using Config\n        len_diff = abs(len(train_seq) - len(query_seq))\n        max_len = max(len(train_seq), len(query_seq))\n        if len_diff / max_len > Config.LENGTH_FILTER_RATIO: continue\n        \n        # Fast scoring\n        raw_score = aligner.score(query_seq, train_seq)\n        normalized_score = raw_score / (2 * min(len(query_seq), len(train_seq)))\n        similar_seqs.append((target_id, train_seq, normalized_score, train_coords_dict[target_id]))\n    \n    similar_seqs.sort(key=lambda x: x[2], reverse=True)\n    return similar_seqs[:top_n]\n\ndef adapt_template_to_query(query_seq, template_seq, template_coords):\n    alignment = next(iter(aligner.align(query_seq, template_seq)))\n    new_coords = np.full((len(query_seq), 3), np.nan)\n    \n    for (q_start, q_end), (t_start, t_end) in zip(*alignment.aligned):\n        t_chunk = template_coords[t_start:t_end]\n        if len(t_chunk) == (q_end - q_start):\n            new_coords[q_start:q_end] = t_chunk\n\n    # Interpolation\n    for i in range(len(new_coords)):\n        if np.isnan(new_coords[i, 0]):\n            prev_v = next((j for j in range(i-1, -1, -1) if not np.isnan(new_coords[j, 0])), -1)\n            next_v = next((j for j in range(i+1, len(new_coords)) if not np.isnan(new_coords[j, 0])), -1)\n            if prev_v >= 0 and next_v >= 0:\n                w = (i - prev_v) / (next_v - prev_v)\n                new_coords[i] = (1-w)*new_coords[prev_v] + w*new_coords[next_v]\n            elif prev_v >= 0: new_coords[i] = new_coords[prev_v] + [3, 0, 0]\n            elif next_v >= 0: new_coords[i] = new_coords[next_v] + [3, 0, 0]\n            else: new_coords[i] = [i*3, 0, 0]\n            \n    return np.nan_to_num(new_coords)\n\ndef adaptive_rna_constraints(coordinates, sequence, confidence=1.0):\n    refined_coords = coordinates.copy()\n    n = len(sequence)\n    # Используем параметры из Config\n    strength = Config.CONSTRAINT_STRENGTH_BASE * (1.0 - min(confidence, Config.CONFIDENCE_CUTOFF))\n\n    for _ in range(2):\n        for i in range(n - 1):\n            p1, p2 = refined_coords[i], refined_coords[i+1]\n            dist = np.linalg.norm(p2 - p1)\n            if dist > 0:\n                # i to i+1 constraint\n                adj = (Config.NEIGHBOR_DIST_1 - dist) * strength * Config.NEIGHBOR_FACTOR_1\n                refined_coords[i+1] += (p2 - p1) / dist * adj\n\n            if i < n - 2:\n                p3 = refined_coords[i+2]\n                dist2 = np.linalg.norm(p3 - p1)\n                if dist2 > 0:\n                    # i to i+2 constraint\n                    adj2 = (Config.NEIGHBOR_DIST_2 - dist2) * strength * Config.NEIGHBOR_FACTOR_2\n                    refined_coords[i+2] += (p3 - p1) / dist2 * adj2\n\n    return refined_coords\n\ndef predict_rna_structures(sequence, target_id, train_seqs_df, train_coords_dict):\n    predictions = []\n    similar_seqs = find_similar_sequences(sequence, train_seqs_df, train_coords_dict, top_n=Config.TOP_N_PREDICTIONS)\n    \n    for i in range(Config.TOP_N_PREDICTIONS):\n        if i < len(similar_seqs):\n            t_id, t_seq, sim, t_coords = similar_seqs[i]\n            adapted = adapt_template_to_query(sequence, t_seq, t_coords)\n            refined = adaptive_rna_constraints(adapted, sequence, confidence=sim)\n            \n            # Шум на основе Config\n            noise = 0.0 if i == 0 else max(Config.NOISE_MIN, (Config.NOISE_THRESHOLD - sim) * Config.NOISE_SCALE)\n            if noise > 0: refined += np.random.normal(0, noise, refined.shape)\n            predictions.append(refined)\n        else:\n            # Fallback\n            n = len(sequence)\n            coords = np.zeros((n, 3))\n            for j in range(1, n): coords[j] = coords[j-1] + [Config.FALLBACK_STEP, 0, 0]\n            predictions.append(coords)\n    return predictions\n\n# ======================================================\n# 5. ЗАПУСК И ГЕНЕРАЦИЯ САБМИТА\n# ======================================================\nall_predictions = []\nstart_time = time.time()\nprint(\"Starting predictions...\")\n\nfor idx, row in test_seqs.iterrows():\n    if idx % 10 == 0: print(f\"Processing {idx} | {time.time()-start_time:.1f}s\")\n    tid, seq = row['target_id'], row['sequence']\n    preds = predict_rna_structures(seq, tid, train_seqs, train_coords_dict)\n    \n    for j in range(len(seq)):\n        res = {'ID': f\"{tid}_{j+1}\", 'resname': seq[j], 'resid': j+1}\n        for i in range(5):\n            res[f'x_{i+1}'], res[f'y_{i+1}'], res[f'z_{i+1}'] = preds[i][j]\n        all_predictions.append(res)\n\nsub = pd.DataFrame(all_predictions)\ncols = ['ID', 'resname', 'resid'] + [f'{c}_{i}' for i in range(1,6) for c in ['x','y','z']]\nsub[cols].to_csv(Config.OUTPUT_FILE, index=False)\nprint(f\"{Config.OUTPUT_FILE} saved successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T15:16:39.628246Z","iopub.execute_input":"2026-01-31T15:16:39.628557Z","iopub.status.idle":"2026-01-31T15:19:10.011069Z","shell.execute_reply.started":"2026-01-31T15:16:39.628524Z","shell.execute_reply":"2026-01-31T15:19:10.010344Z"}},"outputs":[],"execution_count":null}]}