{"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":"none","dataSources":[{"sourceType":"competition","sourceId":118765,"databundleVersionId":15231210},{"sourceType":"datasetVersion","sourceId":14604295,"datasetId":9328538,"databundleVersionId":15440074}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false},"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-02-20T02:15:25.795289Z","iopub.execute_input":"2026-02-20T02:15:25.795594Z","iopub.status.idle":"2026-02-20T02:15:34.036696Z","shell.execute_reply.started":"2026-02-20T02:15:25.795566Z","shell.execute_reply":"2026-02-20T02:15:34.035535Z"}},"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\nimport pickle\nfrom scipy.cluster.hierarchy import linkage, fcluster\nfrom scipy.spatial.distance import pdist\nfrom collections import Counter\n\nwarnings.filterwarnings('ignore')\n\n# 数据路径定义\nDATA_PATH = '/kaggle/input/stanford-rna-3d-folding-2/'\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\n# 添加额外工具包路径\nsys.path.append(os.path.join(DATA_PATH, \"extra\"))\n\n# FASTA解析函数定义\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    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:\n                continue\n            if line.startswith(\">\"):\n                if cur is not None:\n                    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:\n            out[cur] = \"\".join(seq_parts)\n        return out\n\n# 化学计量学解析函数\ndef parse_stoichiometry(stoich: str):\n    if pd.isna(stoich) or str(stoich).strip() == \"\":\n        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\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:\n                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):\n            return [(0, len(seq))]\n        return segs\n    except Exception:\n        return [(0, len(seq))]\n\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\ntrain_segs_map, train_stoich_map = build_segments_map(train_seqs)\ntest_segs_map,  test_stoich_map  = build_segments_map(test_seqs)\n\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\ntrain_coords_dict = process_labels(train_labels)\n\n# 从MSA生成共识序列\ndef generate_consensus_from_msa(msa_sequences, threshold=0.5):\n    if not msa_sequences:\n        return \"\", None\n    max_len = max(len(seq) for seq in msa_sequences)\n    aligned_seqs = [seq.ljust(max_len, '-') for seq in msa_sequences]\n    n_seqs = len(aligned_seqs)\n    base_order = ['A', 'U', 'G', 'C', '-']\n    pssm = np.zeros((max_len, len(base_order)))\n    for pos in range(max_len):\n        counts = Counter(seq[pos] for seq in aligned_seqs)\n        for i, base in enumerate(base_order):\n            pssm[pos, i] = counts.get(base, 0) / n_seqs\n    consensus = []\n    for pos in range(max_len):\n        best_base = base_order[np.argmax(pssm[pos])]\n        if pssm[pos, np.argmax(pssm[pos])] >= threshold:\n            consensus.append(best_base)\n        else:\n            consensus.append('N')\n    return \"\".join(consensus), pssm\n\n# 计算序列间保守性加权相似度\ndef calculate_conservative_similarity(target_seq, msa_sequences):\n    base2onehot = {\n        'A': [1,0,0,0], 'a': [1,0,0,0],\n        'U': [0,1,0,0], 'u': [0,1,0,0],\n        'G': [0,0,1,0], 'g': [0,0,1,0],\n        'C': [0,0,0,1], 'c': [0,0,0,1],\n        '-': [0,0,0,0]\n    }\n    \n    max_len = max(len(target_seq), max(len(seq) for seq in msa_sequences))\n    target_padded = target_seq.ljust(max_len, '-')\n    target_onehot = np.array([base2onehot.get(c, [0,0,0,0]) for c in target_padded]).reshape(-1)\n    \n    aligned_seqs = [seq.ljust(max_len, '-') for seq in msa_sequences]\n    n_seqs = len(aligned_seqs)\n    \n    # 计算保守性分数（反向变异分数）\n    conservation_scores = []\n    for pos in range(max_len):\n        counts = Counter(seq[pos] for seq in aligned_seqs)\n        col = np.array([base2onehot.get(seq[pos], [0,0,0,0]) for seq in aligned_seqs])\n        entropy = -np.sum((col.mean(0) + 1e-10) * np.log2(col.mean(0) + 1e-10))\n        conservation = 1.0 - (entropy / 2.0)\n        conservation_scores.append(conservation)\n    conservation_scores = np.array(conservation_scores)\n    \n    # 对保守位点加权计算相似度\n    weights = conservation_scores / (conservation_scores.sum() + 1e-6)\n    weighted_weights = np.repeat(weights, 4)\n    \n    similarities = []\n    for seq in aligned_seqs:\n        seq_onehot = np.array([base2onehot.get(c, [0,0,0,0]) for c in seq]).reshape(-1)\n        cos_sim = np.dot(target_onehot * weighted_weights, seq_onehot * weighted_weights)\n        cos_sim /= (np.linalg.norm(target_onehot * weighted_weights) * np.linalg.norm(seq_onehot * weighted_weights) + 1e-12)\n        similarities.append(cos_sim)\n    \n    return similarities\n\n# MSA聚类和采样\ndef cluster_and_sample_msa(msa_sequences, target_seq, N=128, distance_threshold=0.15):\n    if len(msa_sequences) <= N:\n        return msa_sequences, None\n    \n    # 第一步：基于保守性加权筛选top-N*N个相似序列\n    top_k = N * N\n    if len(msa_sequences) > top_k:\n        similarities = calculate_conservative_similarity(target_seq, msa_sequences)\n        sorted_idx = np.argsort(similarities)[::-1]\n        msa_sequences = [msa_sequences[i] for i in sorted_idx[:top_k]]\n    \n    # 步骤1：One-Hot编码（和原逻辑一致）\n    base2onehot = {\n        'A': [1,0,0,0], 'a': [1,0,0,0],\n        'U': [0,1,0,0], 'u': [0,1,0,0],\n        'G': [0,0,1,0], 'g': [0,0,1,0],\n        'C': [0,0,0,1], 'c': [0,0,0,1],\n        '-': [0,0,0,0]\n    }\n    seq_vectors = []\n    max_len = max(len(seq) for seq in msa_sequences)\n    for seq in msa_sequences:\n        padded_seq = seq.ljust(max_len, '-')\n        onehot_vec = []\n        for c in padded_seq:\n            onehot_vec.extend(base2onehot.get(c, [0,0,0,0]))\n        seq_vectors.append(onehot_vec)\n    seq_vectors = np.array(seq_vectors, dtype=np.float32)  # [n_seq, 4*max_len]\n    \n    # 步骤2：计算“变异分数”（反向替换原保守分数）\n    variation_scores = []\n    for pos in range(max_len):\n        # 提取该位点的One-Hot列向量 [n_seq, 4]\n        col = seq_vectors[:, pos*4:(pos+1)*4]\n        # 计算该位点的信息熵（熵越高，变异越大）\n        entropy = -np.sum((col.mean(0) + 1e-10) * np.log2(col.mean(0) + 1e-10))\n        # 变异分数：熵/2 → 范围0~1（全保守=0，最大变异=1）\n        variation_scores.append(entropy / 2.0)\n    variation_scores = np.array(variation_scores)  # [max_len,]\n    \n    # 步骤3：过滤全保守位点（变异分数=0的位点权重=0）\n    # 仅保留变异分数>0的位点（有差异的位点）\n    non_conservative_mask = variation_scores > 1e-6  # 避免浮点误差，极小值视为0\n    if not np.any(non_conservative_mask):\n        # 极端情况：所有位点都保守 → 直接随机采样\n        sampled_msa = np.random.choice(msa_sequences, size=N, replace=False).tolist()\n        return sampled_msa, None\n    \n    # 步骤4：对变异位点加权（变异越大，权重越高）\n    # 仅保留变异位点的权重，全保守位点权重=0\n    weights = np.zeros_like(variation_scores)\n    weights[non_conservative_mask] = variation_scores[non_conservative_mask] / (variation_scores[non_conservative_mask].mean() + 1e-6)\n    \n    # 步骤5：生成加权向量（仅变异位点有贡献）\n    weighted_vectors = seq_vectors * np.repeat(weights, 4)\n    \n    # 步骤6：聚类（和原逻辑一致，但基于变异位点的加权向量）\n    distance_matrix = pdist(weighted_vectors, metric='cosine')\n    linkage_matrix = linkage(distance_matrix, method='ward')\n    cluster_labels = fcluster(linkage_matrix, distance_threshold, criterion='distance')\n    \n    # 后续采样逻辑（和原逻辑一致）\n    cluster_counts = {}\n    cluster_seqs = {}\n    for idx, label in enumerate(cluster_labels):\n        if label not in cluster_counts:\n            cluster_counts[label] = 0\n            cluster_seqs[label] = []\n        cluster_counts[label] += 1\n        cluster_seqs[label].append(msa_sequences[idx])\n    \n    total_samples = sum(cluster_counts.values())\n    sampled_msa = []\n    for cluster_id in cluster_seqs:\n        cluster_size = cluster_counts[cluster_id]\n        sample_num = int(round((cluster_size / total_samples) * N))\n        sample_num = max(2, sample_num)\n        cluster_samples = np.random.choice(cluster_seqs[cluster_id], size=min(sample_num, len(cluster_seqs[cluster_id])), replace=False).tolist()\n        sampled_msa.extend(cluster_samples)\n    \n    if len(sampled_msa) > N:\n        sampled_msa = sampled_msa[:N]\n    elif len(sampled_msa) < N:\n        remaining = N - len(sampled_msa)\n        supplement = np.random.choice(msa_sequences, size=remaining, replace=False).tolist()\n        sampled_msa.extend(supplement)\n    \n    consensus_seq, pssm = generate_consensus_from_msa(sampled_msa)\n    return sampled_msa, {'consensus': consensus_seq, 'pssm': pssm, 'variation': variation_scores}\n\n# 读取并处理MSA文件\ndef read_and_process_msa(target_id, target_seq, msa_dir=\"/kaggle/input/stanford-rna-3d-folding-2/train_msas\", N=128):\n    msa_path = os.path.join(msa_dir, f\"{target_id}.a3m\")\n    if not os.path.exists(msa_path):\n        return [], None\n    \n    msas = []\n    with open(msa_path, 'r', encoding='utf-8') as f:\n        current_seq = \"\"\n        for line in f:\n            line = line.strip()\n            if not line:\n                continue\n            if line.startswith('>'):\n                if current_seq:\n                    msas.append(current_seq)\n                    current_seq = \"\"\n                continue\n            clean_line = ''.join([c for c in line if c in 'AaUuGgCc'])\n            current_seq += clean_line\n        if current_seq:\n            msas.append(current_seq)\n    \n    sampled_msas, features = cluster_and_sample_msa(msas, target_seq, N=N)\n    return sampled_msas, features\n\n# 序列比对器初始化\nfrom Bio.Align import PairwiseAligner\n\naligner = PairwiseAligner()\naligner.mode = 'global'\naligner.match_score = 2\naligner.mismatch_score = -1.5\naligner.open_gap_score   = -8\naligner.extend_gap_score = -0.4\naligner.query_left_open_gap_score  = -8\naligner.query_left_extend_gap_score = -0.4\naligner.query_right_open_gap_score = -8\naligner.query_right_extend_gap_score = -0.4\naligner.target_left_open_gap_score = -8\naligner.target_left_extend_gap_score = -0.4\naligner.target_right_open_gap_score = -8\naligner.target_right_extend_gap_score = -0.4\n\n# 寻找相似序列\ndef find_similar_sequences(query_seq, train_seqs_df, train_coords_dict, top_n=5, use_msa_clustering=True, msa_N=128):\n    similar_seqs = []\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        if abs(len(train_seq) - len(query_seq)) / max(len(train_seq), len(query_seq)) > 0.3: continue\n        \n        if use_msa_clustering:\n            sampled_msas, features = read_and_process_msa(target_id, train_seq, N=msa_N)\n            if sampled_msas:\n                msa_scores = []\n                for msa_seq in sampled_msas:\n                    if abs(len(msa_seq) - len(train_seq)) / max(len(msa_seq), len(train_seq)) > 0.3:\n                        continue\n                    msa_score = aligner.score(train_seq, msa_seq)\n                    msa_scores.append((msa_seq, msa_score))\n                if msa_scores:\n                    best_msa_seq = max(msa_scores, key=lambda x: x[1])[0]\n                    raw_score = aligner.score(query_seq, best_msa_seq)\n                else:\n                    raw_score = aligner.score(query_seq, train_seq)\n            else:\n                raw_score = aligner.score(query_seq, train_seq)\n        else:\n            raw_score = aligner.score(query_seq, train_seq)\n        \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\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    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    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\n# RNA坐标约束优化\ndef adaptive_rna_constraints(coordinates, target_id, confidence=1.0, passes=10):\n    coords = coordinates.copy()\n    segments = test_segs_map.get(target_id, [(0, len(coords))])\n    strength = 0.75 * (1.0 - min(confidence, 0.97))\n    strength = max(strength, 0.02)\n\n    for iteration in range(passes):\n        iteration_factor = 1.0 if iteration < 5 else 0.5\n        current_strength = strength * iteration_factor\n        for (s, e) in segments:\n            X = coords[s:e]\n            L = e - s\n            if L < 3:\n                coords[s:e] = X\n                continue\n\n            d = X[1:] - X[:-1]\n            dist = np.linalg.norm(d, axis=1) + 1e-6\n            target = 5.95\n            scale = (target - dist) / dist\n            adj = (d * scale[:, None]) * (0.22 * current_strength)\n            X[:-1] -= adj\n            X[1:]  += adj\n\n            d2 = X[2:] - X[:-2]\n            dist2 = np.linalg.norm(d2, axis=1) + 1e-6\n            target2 = 10.2\n            scale2 = (target2 - dist2) / dist2\n            adj2 = (d2 * scale2[:, None]) * (0.10 * current_strength)\n            X[:-2] -= adj2\n            X[2:]  += adj2\n\n            lap = 0.5 * (X[:-2] + X[2:]) - X[1:-1]\n            X[1:-1] += (0.06 * current_strength) * lap\n\n            if L >= 25:\n                k = min(L, 160) if L > 220 else L\n                if k < L:\n                    idx = np.linspace(0, L - 1, k).astype(int)\n                else:\n                    idx = np.arange(L)\n\n                P = X[idx]\n                diff = P[:, None, :] - P[None, :, :]\n                distm = np.linalg.norm(diff, axis=2) + 1e-6\n                sep = np.abs(idx[:, None] - idx[None, :])\n\n                mask = (sep > 2) & (distm < 3.2)\n                if np.any(mask):\n                    force = (3.2 - distm) / distm\n                    vec = (diff * force[:, :, None] * mask[:, :, None]).sum(axis=1)\n                    X[idx] += (0.015 * current_strength) * vec\n\n            coords[s:e] = X\n\n    return coords\n\n# 旋转矩阵生成\ndef _rotmat(axis, ang):\n    axis = np.asarray(axis, float)\n    axis = axis / (np.linalg.norm(axis) + 1e-12)\n    x, y, z = axis\n    c, s = np.cos(ang), np.sin(ang)\n    C = 1.0 - c\n    return np.array([\n        [c + x*x*C,     x*y*C - z*s, x*z*C + y*s],\n        [y*x*C + z*s,   c + y*y*C,   y*z*C - x*s],\n        [z*x*C - y*s,   z*y*C + x*s, c + z*z*C]\n    ], dtype=float)\n\n# 铰链变换\ndef apply_hinge(coords, seg, rng, max_angle_deg=25):\n    s, e = seg\n    L = e - s\n    if L < 30:\n        return coords\n    pivot = s + int(rng.integers(10, L - 10))\n    axis = rng.normal(size=3)\n    ang = np.deg2rad(float(rng.uniform(-max_angle_deg, max_angle_deg)))\n    R = _rotmat(axis, ang)\n    X = coords.copy()\n    p0 = X[pivot].copy()\n    X[pivot+1:e] = (X[pivot+1:e] - p0) @ R.T + p0\n    return X\n\n# 链抖动\ndef jitter_chains(coords, segments, rng, max_angle_deg=12, max_trans=1.5):\n    X = coords.copy()\n    global_center = X.mean(axis=0, keepdims=True)\n    for (s, e) in segments:\n        axis = rng.normal(size=3)\n        ang = np.deg2rad(float(rng.uniform(-max_angle_deg, max_angle_deg)))\n        R = _rotmat(axis, ang)\n        shift = rng.normal(size=3)\n        shift = shift / (np.linalg.norm(shift) + 1e-12) * float(rng.uniform(0.0, max_trans))\n        c = X[s:e].mean(axis=0, keepdims=True)\n        X[s:e] = (X[s:e] - c) @ R.T + c + shift\n    X -= X.mean(axis=0, keepdims=True) - global_center\n    return X\n\n# 平滑抖动\ndef smooth_wiggle(coords, segments, rng, amp=0.8):\n    X = coords.copy()\n    for (s, e) in segments:\n        L = e - s\n        if L < 20:\n            continue\n        n_ctrl = 6\n        ctrl_x = np.linspace(0, L - 1, n_ctrl)\n        ctrl_disp = rng.normal(0, amp, size=(n_ctrl, 3))\n        t = np.arange(L)\n        disp = np.vstack([np.interp(t, ctrl_x, ctrl_disp[:, k]) for k in range(3)]).T\n        X[s:e] += disp\n    return X\n\n# RNA结构预测主函数\ndef predict_rna_structures(row, train_seqs_df, train_coords_dict, n_predictions=5):\n    tid = row['target_id']\n    seq = row['sequence']\n    assert set(seq).issubset(set(\"ACGU\")), f\"Non-ACGU in {tid}; do not remap here.\"\n    segments = test_segs_map.get(tid, [(0, len(seq))])\n\n    cands = find_similar_sequences(query_seq=seq, train_seqs_df=train_seqs_df, train_coords_dict=train_coords_dict, top_n=30, use_msa_clustering=True, msa_N=128)\n    assert all(len(c[3]) == len(c[1]) for c in cands), \"Template coords/seq length mismatch\"\n    predictions = []\n    used = set()\n\n    for i in range(n_predictions):\n        seed = (abs(hash(tid)) + i * 10007) % (2**32)\n        rng = np.random.default_rng(seed)\n\n        if not cands:\n            coords = np.zeros((len(seq), 3), dtype=float)\n            for (s, e) in segments:\n                for j in range(s+1, e):\n                    coords[j] = coords[j-1] + [5.95, 0, 0]\n            predictions.append(coords)\n            continue\n\n        if i == 0:\n            t_id, t_seq, sim, t_coords = cands[0]\n        else:\n            K = min(12, len(cands))\n            sims = np.array([cands[k][2] for k in range(K)], float)\n            w = np.exp((sims - sims.max()) / 0.08)\n            for k in range(K):\n                if cands[k][0] in used:\n                    w[k] *= 0.10\n            w = w / (w.sum() + 1e-12)\n            k = int(rng.choice(np.arange(K), p=w))\n            t_id, t_seq, sim, t_coords = cands[k]\n\n        used.add(t_id)\n        adapted = adapt_template_to_query(query_seq=seq, template_seq=t_seq, template_coords=t_coords)\n\n        if i == 0:\n            X = adapted\n        elif i == 1:\n            X = adapted + rng.normal(0, max(0.01, (0.40 - sim) * 0.06), adapted.shape)\n        elif i == 2:\n            longest = max(segments, key=lambda se: se[1] - se[0])\n            X = apply_hinge(adapted, longest, rng, max_angle_deg=22)\n        elif i == 3:\n            X = jitter_chains(adapted, segments, rng, max_angle_deg=10, max_trans=1.0)\n        else:\n            X = smooth_wiggle(adapted, segments, rng, amp=0.7)\n\n        refined = adaptive_rna_constraints(X, tid, confidence=sim, passes=10)\n        predictions.append(refined)\n\n    return predictions\n\n# 生成预测结果并保存\nall_predictions = []\nstart_time = time.time()\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(row, train_seqs, train_coords_dict)\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']]\ncoord_cols = [c for c in cols if c.startswith(('x_','y_','z_'))]\nsub[coord_cols] = sub[coord_cols].clip(-999.999, 9999.999)\nsub[cols].to_csv('submission.csv', index=False)\nprint(\"submission.csv! saved\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T02:15:34.039902Z","iopub.execute_input":"2026-02-20T02:15:34.040263Z","iopub.status.idle":"2026-02-20T02:16:51.968569Z","shell.execute_reply.started":"2026-02-20T02:15:34.040205Z","shell.execute_reply":"2026-02-20T02:16:51.966678Z"}},"outputs":[],"execution_count":null}]}