{"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":[{"sourceType":"competition","sourceId":118765,"databundleVersionId":15231210},{"sourceType":"datasetVersion","sourceId":15361422,"datasetId":9825781,"databundleVersionId":16272142}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport sys\nimport glob\nimport importlib\nimport numpy as np\nimport pandas as pd\nfrom scipy.spatial.transform import Rotation\nfrom scipy.spatial.distance import pdist, squareform\n\n# ====================================================\n# 1. 全路径探测安装逻辑 (保持你的代码)\n# ====================================================\ndef setup_biopython_robust():\n    print(\"Checking all available input paths...\")\n    all_whls = glob.glob(\"/kaggle/input/**/*.whl\", recursive=True)\n    target_whls = [f for f in all_whls if \"biopython\" in f.lower()]\n    \n    if target_whls:\n        target_whl = target_whls[0]\n        print(f\"✅ Found Biopython at: {target_whl}\")\n        os.system(f'pip install \"{target_whl}\" --no-index --find-links=\"{os.path.dirname(target_whl)}\" > /dev/null')\n        \n        import site\n        importlib.reload(site)\n        from Bio.Align import PairwiseAligner\n        print(\"✅ Biopython 1.86 successfully loaded!\")\n        return PairwiseAligner\n    else:\n        print(\"❌ ERROR: No Biopython .whl found in /kaggle/input\")\n        print(os.listdir('/kaggle/input'))\n        raise FileNotFoundError(\"Biopython dataset not found. Did you click '+ Add Data'?\")\n\n# 初始化比对器类\ntry:\n    PairwiseAligner = setup_biopython_robust()\nexcept Exception as e:\n    print(e)\n    # 兜底：如果环境下已经有了就直接用\n    from Bio.Align import PairwiseAligner\n\n# ====================================================\n# 2. 配置与全局实例\n# ====================================================\nclass Config:\n    DATA_PATH = '/kaggle/input/competitions/stanford-rna-3d-folding-2'\n    MAX_RELATIVE_LENGTH_DIFF = 0.5\n    ALIGNMENT_MATCH = 2\n    ALIGNMENT_MISMATCH = -1\n    ALIGNMENT_GAP_OPEN = -10\n    ALIGNMENT_GAP_EXTEND = -0.5\n    BOND_DISTANCE_TARGET = 6.0\n    BOND_DISTANCE_TOL = 0.5\n    MIN_NONBOND_DISTANCE = 3.8\n    BASE_PAIRING_DISTANCE_IDEAL = 12.5\n    BASE_PAIRING_DISTANCE_RANGE = (8.0, 14.0)\n    HELIX_RADIUS = 10.0\n    HELIX_RISE_PER_BASE = 2.5\n    HELIX_ANGLE_STEP = 0.6\n    NUM_PREDICTIONS = 5\n    RANDOM_SEED_OFFSET = 1000\n    DEFAULT_RELIABILITY = 0.2\n    PAIRING_PROB_THRESHOLD = 0.7\n    STEP_LENGTH_RANGE = (3.5, 4.5)\n    NOISE_SCALE_MIN = 0.01\n    TEMPLATE_WEIGHT = 0.55\n    RANDOM_WEIGHT = 0.45\n\n# 初始化新版比对器实例\naligner = PairwiseAligner()\naligner.mode = 'global'\naligner.match_score = Config.ALIGNMENT_MATCH\naligner.mismatch_score = Config.ALIGNMENT_MISMATCH\naligner.open_gap_score = Config.ALIGNMENT_GAP_OPEN\naligner.extend_gap_score = Config.ALIGNMENT_GAP_EXTEND\n\n# ====================================================\n# 3. 核心工具函数 (已修正兼容性)\n# ====================================================\n\n# 【关键修正】：使用 PairwiseAligner 替换废弃的 pairwise2\ndef compute_alignment_score(query_seq, template_seq):\n    score = aligner.score(query_seq, template_seq)\n    norm_score = score / (2 * min(len(query_seq), len(template_seq)))\n    \n    alignments = aligner.align(query_seq, template_seq)\n    try:\n        target_align = next(iter(alignments))\n        # 模拟旧版 pairwise2 的返回结构\n        class Compat:\n            def __init__(self, a, b):\n                self.seqA = a\n                self.seqB = b\n        return Compat(str(target_align[0]), str(target_align[1])), norm_score\n    except StopIteration:\n        return None, 0.0\n\ndef extract_structures(labels_data):\n    structures = {}\n    # 修正：显式转为 string 避免 groupby 索引错误\n    for group_label, group_data in labels_data.groupby(labels_data['ID'].astype(str).str.rsplit('_', n=1).str[0]):\n        sorted_data = group_data.sort_values('resid')\n        structures[group_label] = sorted_data[['x_1', 'y_1', 'z_1']].values.astype(np.float32)\n    return structures\n\n# 其余逻辑保持不变...\ndef secondary_structure_filter(seq_a, seq_b, min_bp=2):\n    pairing_map = {'A': 'U', 'U': 'A', 'G': 'C', 'C': 'G'}\n    count = 0\n    for i in range(min(len(seq_a), len(seq_b))):\n        if seq_a[i] in pairing_map and seq_b[i] == pairing_map[seq_a[i]]:\n            count += 1\n    return count >= min_bp\n\ndef morph_template_with_interpolation(dest_seq, source_seq, source_coords):\n    alignment, _ = compute_alignment_score(dest_seq, source_seq)\n    if not alignment: return create_default_structure(dest_seq)\n    a_dest, a_src = str(alignment.seqA), str(alignment.seqB)\n    n = len(dest_seq)\n    res = np.full((n, 3), np.nan, dtype=np.float32)\n    d_idx = s_idx = 0\n    for a, b in zip(a_dest, a_src):\n        if d_idx >= n: break\n        if a != '-' and b != '-':\n            if s_idx < len(source_coords): res[d_idx] = source_coords[s_idx]\n            d_idx += 1; s_idx += 1\n        elif a != '-': d_idx += 1\n        elif b != '-': s_idx += 1\n    v = ~np.isnan(res[:, 0])\n    if not v.any(): return create_default_structure(dest_seq)\n    idx = np.arange(n)\n    for i in range(3): res[:, i] = np.interp(idx, idx[v], res[v, i])\n    return res\n\ndef enhance_geometry(positions, rna_string, reliability=1.0):\n    refined = positions.copy()\n    n = len(rna_string)\n    adj = 0.8 * (1.0 - min(reliability, 0.8))\n    for i in range(n - 1):\n        vec = refined[i+1] - refined[i]\n        d = np.linalg.norm(vec)\n        if abs(d - Config.BOND_DISTANCE_TARGET) > Config.BOND_DISTANCE_TOL:\n            unit = vec / (d + 1e-10)\n            refined[i+1] = refined[i] + unit * (d + (Config.BOND_DISTANCE_TARGET - d) * adj)\n    return refined\n\ndef create_default_structure(seq):\n    n = len(seq)\n    coords = np.zeros((n, 3), dtype=np.float32)\n    for i in range(n): coords[i] = [i * 4.0, 0, 0]\n    return coords\n\ndef find_comparable_seqs(query_seq, template_df, structure_dict, top_k=5):\n    # 性能优化：Scoring 时只对比长度最接近的前 50 条\n    template_df = template_df.copy()\n    template_df['len_diff'] = (template_df['sequence'].str.len() - len(query_seq)).abs()\n    candidates = template_df.nsmallest(50, 'len_diff')\n    matches = []\n    for _, row in candidates.iterrows():\n        tid, tseq = row['target_id'], row['sequence']\n        if tid not in structure_dict: continue\n        if abs(len(tseq) - len(query_seq)) / max(len(tseq), len(query_seq)) > Config.MAX_RELATIVE_LENGTH_DIFF: continue\n        if not secondary_structure_filter(query_seq, tseq): continue\n        _, score = compute_alignment_score(query_seq, tseq)\n        matches.append((tid, tseq, score, structure_dict[tid]))\n    matches.sort(key=lambda x: x[2], reverse=True)\n    return matches[:top_k]\n\ndef compute_structures(seq, identifier, template_df, struct_dict, num_outputs=5):\n    predictions = []\n    comparable = find_comparable_seqs(seq, template_df, struct_dict, top_k=num_outputs)\n    for tid, tseq, sim, tcoords in comparable:\n        m = morph_template_with_interpolation(seq, tseq, tcoords)\n        refined = enhance_geometry(m, seq, reliability=sim)\n        predictions.append(refined - np.mean(refined, axis=0))\n    while len(predictions) < num_outputs:\n        predictions.append(create_default_structure(seq))\n    return predictions[:num_outputs]\n\n# ====================================================\n# 4. 主程序\n# ====================================================\nprint(\"Loading data...\")\ntrain = pd.read_csv(f'{Config.DATA_PATH}/train_sequences.csv')\ntest = pd.read_csv(f'{Config.DATA_PATH}/test_sequences.csv')\ntrain_labels = pd.read_csv(f'{Config.DATA_PATH}/train_labels.csv', low_memory=False)\n\nprint(\"Preprocessing...\")\ntrain_structs = extract_structures(train_labels)\n\nprediction_records = []\nfor _, row in test.iterrows():\n    sid, seq = row['target_id'], row['sequence']\n    structs = compute_structures(seq, sid, train, train_structs, num_outputs=Config.NUM_PREDICTIONS)\n    for r in range(len(seq)):\n        rec = {'ID': f\"{sid}_{r+1}\", 'resname': seq[r], 'resid': r+1}\n        for m in range(Config.NUM_PREDICTIONS):\n            rec[f'x_{m+1}'], rec[f'y_{m+1}'], rec[f'z_{m+1}'] = structs[m][r]\n        prediction_records.append(rec)\n\npd.DataFrame(prediction_records).fillna(0).to_csv('submission.csv', index=False)\nprint(\"--- Success! ---\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-25T13:03:35.089516Z","iopub.execute_input":"2026-03-25T13:03:35.089833Z","iopub.status.idle":"2026-03-25T13:04:39.864108Z","shell.execute_reply.started":"2026-03-25T13:03:35.089803Z","shell.execute_reply":"2026-03-25T13:04:39.863147Z"}},"outputs":[],"execution_count":null}]}