{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# 16环大环内酯侧链分析演示\n",
        "\n",
        "本notebook演示如何对一个16环大环内酯分子进行：\n",
        "1. 固定编号\n",
        "2. 定义碎片dataclass (JSON导入导出)\n",
        "3. 侧链断裂并保存碎片信息\n",
        "\n",
        "我们将从temp.csv中选择一个分子进行演示。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 1. 导入必要的库\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "所有模块导入成功!\n"
          ]
        }
      ],
      "source": [
        "# 配置matplotlib在Jupyter中内联显示\n",
        "%matplotlib inline\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "import sys\n",
        "sys.path.append('../')\n",
        "\n",
        "from rdkit import Chem\n",
        "from rdkit.Chem import Draw\n",
        "from rdkit.Chem.Draw import IPythonConsole\n",
        "import pandas as pd\n",
        "import json\n",
        "from pathlib import Path\n",
        "import numpy as np\n",
        "\n",
        "# 导入我们的模块\n",
        "from src.ring_numbering import (\n",
        "    assign_ring_numbering,\n",
        "    find_lactone_carbon,\n",
        "    get_ring_atoms,\n",
        "    validate_numbering\n",
        ")\n",
        "from src.fragment_dataclass import Fragment, MoleculeFragments\n",
        "from src.fragment_cleaver import (\n",
        "    identify_side_chains,\n",
        "    extract_side_chain_fragment,\n",
        "    cleave_side_chains,\n",
        "    process_molecule\n",
        ")\n",
        "from src.visualizer import draw_molecule_with_numbering\n",
        "\n",
        "print(\"所有模块导入成功!\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 2. 读取数据并选择一个分子\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "共有 2022 个16环大环内酯分子\n",
            "\n",
            "前5个分子:\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>IDs</th>\n",
              "      <th>molecule_pref_name</th>\n",
              "      <th>smiles</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>CHEMBL94657</td>\n",
              "      <td>PATUPILONE</td>\n",
              "      <td>C/C(=C\\c1csc(C)n1)[C@@H]1C[C@@H]2O[C@]2(C)CCC[...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>CHEMBL94657</td>\n",
              "      <td>PATUPILONE</td>\n",
              "      <td>C/C(=C\\c1csc(C)n1)[C@@H]1C[C@@H]2O[C@]2(C)CCC[...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>CHEMBL1554</td>\n",
              "      <td>DACTINOMYCIN</td>\n",
              "      <td>Cc1c2oc3c(C)ccc(C(=O)N[C@@H]4C(=O)N[C@H](C(C)C...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>CHEMBL1173445</td>\n",
              "      <td>LARGAZOLE</td>\n",
              "      <td>CCCCCCCC(=O)SCC/C=C/[C@@H]1CC(=O)NCc2nc(cs2)C2...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>4</th>\n",
              "      <td>CHEMBL3902498</td>\n",
              "      <td>NaN</td>\n",
              "      <td>Cc1cc2ccc1[C@@H](C)COC(=O)Nc1ccc(S(=O)(=O)C3CC...</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "             IDs molecule_pref_name  \\\n",
              "0    CHEMBL94657         PATUPILONE   \n",
              "1    CHEMBL94657         PATUPILONE   \n",
              "2     CHEMBL1554       DACTINOMYCIN   \n",
              "3  CHEMBL1173445          LARGAZOLE   \n",
              "4  CHEMBL3902498                NaN   \n",
              "\n",
              "                                              smiles  \n",
              "0  C/C(=C\\c1csc(C)n1)[C@@H]1C[C@@H]2O[C@]2(C)CCC[...  \n",
              "1  C/C(=C\\c1csc(C)n1)[C@@H]1C[C@@H]2O[C@]2(C)CCC[...  \n",
              "2  Cc1c2oc3c(C)ccc(C(=O)N[C@@H]4C(=O)N[C@H](C(C)C...  \n",
              "3  CCCCCCCC(=O)SCC/C=C/[C@@H]1CC(=O)NCc2nc(cs2)C2...  \n",
              "4  Cc1cc2ccc1[C@@H](C)COC(=O)Nc1ccc(S(=O)(=O)C3CC...  "
            ]
          },
          "execution_count": 26,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# 读取CSV文件\n",
        "df = pd.read_csv('../ring16/temp.csv')\n",
        "print(f\"共有 {len(df)} 个16环大环内酯分子\")\n",
        "print(f\"\\n前5个分子:\")\n",
        "df[['IDs', 'molecule_pref_name', 'smiles']].head()\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 58,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "选择的分子: nan (CHEMBL409074)\n",
            "SMILES: CC(C)C[C@@H]1OC(=O)C(C)(C)CNC(=O)[C@@H](Cc2ccc(NC(=O)OCC3c4ccccc4-c4ccccc43)cc2)NC(=O)/C=C/C[C@@H]([C@H](C)C2OC2c2ccccc2)OC1=O\n"
          ]
        },
        {
          "data": {
            "image/png": "[encoded data, 17412 characters]",
            "text/plain": [
              "<rdkit.Chem.rdchem.Mol at 0x746d5d37a420>"
            ]
          },
          "execution_count": 58,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# 选择第一个分子进行演示\n",
        "molecule_idx = 337\n",
        "smiles = df.iloc[molecule_idx]['smiles']\n",
        "molecule_name = df.iloc[molecule_idx]['molecule_pref_name']\n",
        "molecule_id = df.iloc[molecule_idx]['IDs']\n",
        "\n",
        "print(f\"选择的分子: {molecule_name} ({molecule_id})\")\n",
        "print(f\"SMILES: {smiles}\")\n",
        "\n",
        "# 解析分子\n",
        "mol = Chem.MolFromSmiles(smiles)\n",
        "mol\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 3. 步骤1: 固定编号\n",
        "\n",
        "我们使用内酯结构的C=O中的C作为编号起点（位置1），然后顺时针依次编号。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "内酯羰基碳的原子索引: 27\n",
            "\n",
            "16环中的原子数: 16\n",
            "16环原子索引: [34, 35, 37, 38, 7, 8, 9, 39, 11, 12, 13, 14, 40, 26, 27, 29]\n"
          ]
        }
      ],
      "source": [
        "# 找到内酯碳原子\n",
        "lactone_c = find_lactone_carbon(mol)\n",
        "print(f\"内酯羰基碳的原子索引: {lactone_c}\")\n",
        "\n",
        "# 获取所有16环原子\n",
        "ring_atoms = get_ring_atoms(mol)\n",
        "print(f\"\\n16环中的原子数: {len(ring_atoms)}\")\n",
        "print(f\"16环原子索引: {ring_atoms}\")\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "16环原子编号 (原子索引 -> 环位置):\n",
            "  位置  1: 原子索引 27 (C)\n",
            "  位置  2: 原子索引 26 (O)\n",
            "  位置  3: 原子索引 14 (C)\n",
            "  位置  4: 原子索引 13 (C)\n",
            "  位置  5: 原子索引 12 (C)\n",
            "  位置  6: 原子索引 11 (C)\n",
            "  位置  7: 原子索引  9 (C)\n",
            "  位置  8: 原子索引  8 (N)\n",
            "  位置  9: 原子索引  7 (C)\n",
            "  位置 10: 原子索引 40 (C)\n",
            "  位置 11: 原子索引 39 (N)\n",
            "  位置 12: 原子索引 38 (C)\n",
            "  位置 13: 原子索引 37 (C)\n",
            "  位置 14: 原子索引 35 (C)\n",
            "  位置 15: 原子索引 34 (O)\n",
            "  位置 16: 原子索引 29 (C)\n"
          ]
        }
      ],
      "source": [
        "# 分配固定编号 (1-16)\n",
        "numbering = assign_ring_numbering(mol)\n",
        "\n",
        "print(\"16环原子编号 (原子索引 -> 环位置):\")\n",
        "for atom_idx in sorted(numbering.keys(), key=lambda x: numbering[x]):\n",
        "    position = numbering[atom_idx]\n",
        "    atom = mol.GetAtomWithIdx(atom_idx)\n",
        "    print(f\"  位置 {position:2d}: 原子索引 {atom_idx:2d} ({atom.GetSymbol()})\")\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "编号验证结果: 通过 ✓\n"
          ]
        }
      ],
      "source": [
        "# 验证编号是否正确\n",
        "is_valid = validate_numbering(mol, numbering)\n",
        "print(f\"编号验证结果: {'通过 ✓' if is_valid else '失败 ✗'}\")\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "分子结构（标注了16环的编号）:\n"
          ]
        },
        {
          "data": {
            "image/png": "[encoded data, 107724 characters]",
            "text/plain": [
              "<Figure size 1000x1000 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# 可视化带编号的分子\n",
        "print(\"分子结构（标注了16环的编号）:\")\n",
        "draw_molecule_with_numbering(mol, numbering, highlight_atoms=list(numbering.keys()))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": []
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "示例Fragment对象:\n",
            "Fragment(fragment_smiles='CC(C)C', parent_smiles='COc1ccc(C[C@H]2NC(=O)/C=C/C[C@@H]([C@H](C)[C@H]3O[C@@H]3c3ccccc3)OC(=O)[C@H](CC(C)C)OC(=O)CCNC2=O)cc1', cleavage_position=5, fragment_id='macro_0_frag_0', parent_id='macro_0', atom_count=4, molecular_weight=58.12)\n",
            "\n",
            "转换为字典:\n",
            "{\n",
            "  \"fragment_smiles\": \"CC(C)C\",\n",
            "  \"parent_smiles\": \"COc1ccc(C[C@H]2NC(=O)/C=C/C[C@@H]([C@H](C)[C@H]3O[C@@H]3c3ccccc3)OC(=O)[C@H](CC(C)C)OC(=O)CCNC2=O)cc1\",\n",
            "  \"cleavage_position\": 5,\n",
            "  \"fragment_id\": \"macro_0_frag_0\",\n",
            "  \"parent_id\": \"macro_0\",\n",
            "  \"atom_count\": 4,\n",
            "  \"molecular_weight\": 58.12\n",
            "}\n"
          ]
        }
      ],
      "source": [
        "# 创建一个示例Fragment对象\n",
        "example_fragment = Fragment(\n",
        "    fragment_smiles=\"CC(C)C\",\n",
        "    parent_smiles=smiles,\n",
        "    cleavage_position=5,\n",
        "    fragment_id=\"macro_0_frag_0\",\n",
        "    parent_id=\"macro_0\",\n",
        "    atom_count=4,\n",
        "    molecular_weight=58.12\n",
        ")\n",
        "\n",
        "print(\"示例Fragment对象:\")\n",
        "print(example_fragment)\n",
        "print(\"\\n转换为字典:\")\n",
        "print(json.dumps(example_fragment.to_dict(), indent=2, ensure_ascii=False))\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fragment已保存到: ../output/test_fragment.json\n",
            "\n",
            "从JSON加载的Fragment:\n",
            "Fragment(fragment_smiles='CC(C)C', parent_smiles='COc1ccc(C[C@H]2NC(=O)/C=C/C[C@@H]([C@H](C)[C@H]3O[C@@H]3c3ccccc3)OC(=O)[C@H](CC(C)C)OC(=O)CCNC2=O)cc1', cleavage_position=5, fragment_id='macro_0_frag_0', parent_id='macro_0', atom_count=4, molecular_weight=58.12)\n",
            "\n",
            "验证: 加载的对象与原对象相同? True\n"
          ]
        }
      ],
      "source": [
        "# 演示JSON保存和加载\n",
        "test_json_path = '../output/test_fragment.json'\n",
        "Path(test_json_path).parent.mkdir(parents=True, exist_ok=True)\n",
        "\n",
        "# 保存到JSON\n",
        "example_fragment.to_json_file(test_json_path)\n",
        "print(f\"Fragment已保存到: {test_json_path}\")\n",
        "\n",
        "# 从JSON加载\n",
        "loaded_fragment = Fragment.from_json_file(test_json_path)\n",
        "print(\"\\n从JSON加载的Fragment:\")\n",
        "print(loaded_fragment)\n",
        "print(\"\\n验证: 加载的对象与原对象相同?\", loaded_fragment == example_fragment)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 5. 步骤3: 侧链断裂并保存碎片\n",
        "\n",
        "现在我们将实际断裂分子的侧链，提取碎片并保存。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "找到 11 个侧链连接点:\n",
            "  位置  2 (环原子 32: C) -> 侧链起始原子 33 (O)\n",
            "  位置  4 (环原子 29: C) -> 侧链起始原子 30 (O)\n",
            "  位置  5 (环原子 26: C) -> 侧链起始原子 27 (C)\n",
            "  位置  5 (环原子 26: C) -> 侧链起始原子 28 (C)\n",
            "  位置  6 (环原子 24: C) -> 侧链起始原子 25 (O)\n",
            "  位置  7 (环原子 22: C) -> 侧链起始原子 23 (C)\n",
            "  位置  8 (环原子 20: C) -> 侧链起始原子 21 (O)\n",
            "  位置  9 (环原子 18: C) -> 侧链起始原子 19 (C)\n",
            "  位置 12 (环原子 15: C) -> 侧链起始原子 13 (C)\n",
            "  位置 13 (环原子 9: C) -> 侧链起始原子 1 (C)\n",
            "  位置 14 (环原子 10: C) -> 侧链起始原子 11 (C)\n"
          ]
        }
      ],
      "source": [
        "# 识别所有侧链\n",
        "side_chains = identify_side_chains(mol, list(numbering.keys()))\n",
        "\n",
        "print(f\"找到 {len(side_chains)} 个侧链连接点:\")\n",
        "for ring_atom_idx, side_chain_start_idx in side_chains:\n",
        "    position = numbering[ring_atom_idx]\n",
        "    ring_atom = mol.GetAtomWithIdx(ring_atom_idx)\n",
        "    side_atom = mol.GetAtomWithIdx(side_chain_start_idx)\n",
        "    print(f\"  位置 {position:2d} (环原子 {ring_atom_idx}: {ring_atom.GetSymbol()}) -> 侧链起始原子 {side_chain_start_idx} ({side_atom.GetSymbol()})\")\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "分子ID: macro_0\n",
            "共提取了 11 个碎片\n",
            "\n",
            "\n",
            "碎片ID: macro_0_frag_0\n",
            "  断裂位置: 2\n",
            "  SMILES: O\n",
            "  原子数: 1\n",
            "  分子量: 18.02 Da\n",
            "\n",
            "碎片ID: macro_0_frag_1\n",
            "  断裂位置: 4\n",
            "  SMILES: O\n",
            "  原子数: 1\n",
            "  分子量: 18.02 Da\n",
            "\n",
            "碎片ID: macro_0_frag_2\n",
            "  断裂位置: 5\n",
            "  SMILES: C\n",
            "  原子数: 1\n",
            "  分子量: 16.04 Da\n",
            "\n",
            "碎片ID: macro_0_frag_3\n",
            "  断裂位置: 5\n",
            "  SMILES: C\n",
            "  原子数: 1\n",
            "  分子量: 16.04 Da\n",
            "\n",
            "碎片ID: macro_0_frag_4\n",
            "  断裂位置: 6\n",
            "  SMILES: O\n",
            "  原子数: 1\n",
            "  分子量: 18.02 Da\n",
            "\n",
            "碎片ID: macro_0_frag_5\n",
            "  断裂位置: 7\n",
            "  SMILES: C\n",
            "  原子数: 1\n",
            "  分子量: 16.04 Da\n",
            "\n",
            "碎片ID: macro_0_frag_6\n",
            "  断裂位置: 8\n",
            "  SMILES: O\n",
            "  原子数: 1\n",
            "  分子量: 18.02 Da\n",
            "\n",
            "碎片ID: macro_0_frag_7\n",
            "  断裂位置: 9\n",
            "  SMILES: C\n",
            "  原子数: 1\n",
            "  分子量: 16.04 Da\n",
            "\n",
            "碎片ID: macro_0_frag_8\n",
            "  断裂位置: 12\n",
            "  SMILES: CC1CO1\n",
            "  原子数: 4\n",
            "  分子量: 58.08 Da\n",
            "\n",
            "碎片ID: macro_0_frag_9\n",
            "  断裂位置: 13\n",
            "  SMILES: CC=Cc1csc(C)n1\n",
            "  原子数: 9\n",
            "  分子量: 139.22 Da\n",
            "\n",
            "碎片ID: macro_0_frag_10\n",
            "  断裂位置: 14\n",
            "  SMILES: CC1CO1\n",
            "  原子数: 4\n",
            "  分子量: 58.08 Da\n"
          ]
        }
      ],
      "source": [
        "# 使用cleave_side_chains函数一次性处理所有侧链\n",
        "parent_id = f\"macro_{molecule_idx}\"\n",
        "mol_fragments = cleave_side_chains(mol, smiles, parent_id, numbering)\n",
        "\n",
        "print(f\"分子ID: {mol_fragments.parent_id}\")\n",
        "print(f\"共提取了 {len(mol_fragments.fragments)} 个碎片\\n\")\n",
        "\n",
        "# 显示所有碎片信息\n",
        "for frag in mol_fragments.fragments:\n",
        "    print(f\"\\n碎片ID: {frag.fragment_id}\")\n",
        "    print(f\"  断裂位置: {frag.cleavage_position}\")\n",
        "    print(f\"  SMILES: {frag.fragment_smiles}\")\n",
        "    print(f\"  原子数: {frag.atom_count}\")\n",
        "    print(f\"  分子量: {frag.molecular_weight:.2f} Da\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 6. 保存碎片到文件夹\n",
        "\n",
        "为每个分子创建一个文件夹，保存所有碎片的JSON文件。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "创建输出文件夹: ../output/fragments/macro_0\n",
            "\n",
            "✓ 保存完整碎片信息到: ../output/fragments/macro_0/macro_0_all_fragments.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_0.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_1.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_2.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_3.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_4.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_5.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_6.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_7.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_8.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_9.json\n",
            "✓ 保存碎片: ../output/fragments/macro_0/macro_0_frag_10.json\n",
            "\n",
            "总共保存了 12 个JSON文件\n"
          ]
        }
      ],
      "source": [
        "# 创建输出文件夹\n",
        "output_dir = Path(f'../output/fragments/{parent_id}')\n",
        "output_dir.mkdir(parents=True, exist_ok=True)\n",
        "\n",
        "print(f\"创建输出文件夹: {output_dir}\\n\")\n",
        "\n",
        "# 保存整个MoleculeFragments对象\n",
        "mol_fragments_path = output_dir / f\"{parent_id}_all_fragments.json\"\n",
        "mol_fragments.to_json_file(str(mol_fragments_path))\n",
        "print(f\"✓ 保存完整碎片信息到: {mol_fragments_path}\")\n",
        "\n",
        "# 为每个碎片单独保存JSON文件\n",
        "for frag in mol_fragments.fragments:\n",
        "    frag_path = output_dir / f\"{frag.fragment_id}.json\"\n",
        "    frag.to_json_file(str(frag_path))\n",
        "    print(f\"✓ 保存碎片: {frag_path}\")\n",
        "\n",
        "print(f\"\\n总共保存了 {len(mol_fragments.fragments) + 1} 个JSON文件\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 7. 验证：从JSON重新加载数据\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "从JSON加载的数据:\n",
            "分子ID: macro_0\n",
            "碎片数量: 11\n",
            "\n",
            "前3个碎片:\n",
            "  macro_0_frag_0: 位置 2, SMILES: O\n",
            "  macro_0_frag_1: 位置 4, SMILES: O\n",
            "  macro_0_frag_2: 位置 5, SMILES: C\n",
            "\n",
            "✓ JSON数据加载和解析成功!\n"
          ]
        }
      ],
      "source": [
        "# 重新加载MoleculeFragments\n",
        "loaded_mol_fragments = MoleculeFragments.from_json_file(str(mol_fragments_path))\n",
        "\n",
        "print(\"从JSON加载的数据:\")\n",
        "print(f\"分子ID: {loaded_mol_fragments.parent_id}\")\n",
        "print(f\"碎片数量: {len(loaded_mol_fragments.fragments)}\")\n",
        "print(f\"\\n前3个碎片:\")\n",
        "for frag in loaded_mol_fragments.fragments[:3]:\n",
        "    print(f\"  {frag.fragment_id}: 位置 {frag.cleavage_position}, SMILES: {frag.fragment_smiles}\")\n",
        "\n",
        "print(\"\\n✓ JSON数据加载和解析成功!\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 总结\n",
        "\n",
        "我们成功完成了以下步骤：\n",
        "\n",
        "1. ✓ **固定编号**: 基于内酯结构的C=O碳原子（位置1），顺时针对16环进行编号\n",
        "2. ✓ **定义碎片dataclass**: 使用dataclass存储碎片信息，支持JSON导入导出\n",
        "3. ✓ **侧链断裂**: 识别并断裂所有侧链，提取碎片SMILES\n",
        "4. ✓ **保存碎片**: 将碎片信息保存到`output/fragments/macro_X/`文件夹\n",
        "\n",
        "下一步可以对所有2022个分子进行批处理，然后进行统计分析，找出哪些位置的取代变化最大。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 8. 分析数据集中的桥环问题\n",
        "\n",
        "检查有多少分子存在桥环导致的环原子识别不完整问题。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "分析前100个分子的16环识别情况...\n",
            "\n",
            "样本数量: 100\n",
            "\n",
            "环原子数量统计:\n",
            "  9个原子: 24 个分子 (24.0%)\n",
            "  10个原子: 20 个分子 (20.0%)\n",
            "  13个原子: 5 个分子 (5.0%)\n",
            "  14个原子: 18 个分子 (18.0%)\n",
            "  16个原子: 26 个分子 (26.0%)\n",
            "  20个原子: 2 个分子 (2.0%)\n",
            "  28个原子: 5 个分子 (5.0%)\n",
            "\n",
            "✓ 完整识别(16个原子): 26 个分子 (26.0%)\n",
            "✗ 不完整识别(非16个): 74 个分子 (74.0%)\n",
            "\n",
            "前10个不完整识别的分子:\n",
            "  索引 0: 14个原子 - PATUPILONE (CHEMBL94657)\n",
            "  索引 1: 14个原子 - PATUPILONE (CHEMBL94657)\n",
            "  索引 2: 28个原子 - DACTINOMYCIN (CHEMBL1554)\n",
            "  索引 3: 10个原子 - LARGAZOLE (CHEMBL1173445)\n",
            "  索引 4: 9个原子 - nan (CHEMBL3902498)\n",
            "  索引 5: 10个原子 - nan (CHEMBL3593247)\n",
            "  索引 6: 10个原子 - LARGAZOLE THIOL (CHEMBL1796689)\n",
            "  索引 8: 10个原子 - nan (CHEMBL3593409)\n",
            "  索引 9: 10个原子 - nan (CHEMBL3593411)\n",
            "  索引 12: 9个原子 - nan (CHEMBL3891120)\n"
          ]
        }
      ],
      "source": [
        "# 检查所有分子的环原子数量\n",
        "def check_ring_atoms_count(smiles):\n",
        "    \"\"\"检查分子的16环原子数量\"\"\"\n",
        "    mol = Chem.MolFromSmiles(smiles)\n",
        "    if mol is None:\n",
        "        return None\n",
        "    ring_atoms = get_ring_atoms(mol)\n",
        "    return len(ring_atoms)\n",
        "\n",
        "# 分析前100个分子\n",
        "print(\"分析前100个分子的16环识别情况...\\n\")\n",
        "sample_size = min(100, len(df))\n",
        "ring_counts = []\n",
        "\n",
        "for idx in range(sample_size):\n",
        "    smiles = df.iloc[idx]['smiles']\n",
        "    count = check_ring_atoms_count(smiles)\n",
        "    ring_counts.append(count)\n",
        "\n",
        "# 统计结果\n",
        "import numpy as np\n",
        "ring_counts_array = np.array(ring_counts)\n",
        "\n",
        "print(f\"样本数量: {sample_size}\")\n",
        "print(f\"\\n环原子数量统计:\")\n",
        "unique, counts = np.unique(ring_counts_array, return_counts=True)\n",
        "for ring_size, count in zip(unique, counts):\n",
        "    percentage = (count / sample_size) * 100\n",
        "    print(f\"  {ring_size}个原子: {count} 个分子 ({percentage:.1f}%)\")\n",
        "\n",
        "# 统计完整识别的分子\n",
        "complete_molecules = np.sum(ring_counts_array == 16)\n",
        "incomplete_molecules = np.sum(ring_counts_array != 16)\n",
        "\n",
        "print(f\"\\n✓ 完整识别(16个原子): {complete_molecules} 个分子 ({complete_molecules/sample_size*100:.1f}%)\")\n",
        "print(f\"✗ 不完整识别(非16个): {incomplete_molecules} 个分子 ({incomplete_molecules/sample_size*100:.1f}%)\")\n",
        "\n",
        "# 如果存在不完整识别的分子，列出前几个\n",
        "if incomplete_molecules > 0:\n",
        "    print(f\"\\n前10个不完整识别的分子:\")\n",
        "    shown = 0\n",
        "    for idx in range(sample_size):\n",
        "        if ring_counts[idx] != 16 and shown < 10:\n",
        "            print(f\"  索引 {idx}: {ring_counts[idx]}个原子 - {df.iloc[idx]['molecule_pref_name']} ({df.iloc[idx]['IDs']})\")\n",
        "            shown += 1\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "分析整个数据集...\n",
            "已处理 500/2022 个分子...\n",
            "已处理 1000/2022 个分子...\n",
            "已处理 1500/2022 个分子...\n",
            "已处理 2000/2022 个分子...\n",
            "\n",
            "总样本数量: 2022\n",
            "\n",
            "环原子数量统计:\n",
            "  6个原子: 5 个分子 (0.2%)\n",
            "  7个原子: 12 个分子 (0.6%)\n",
            "  9个原子: 98 个分子 (4.8%)\n",
            "  10个原子: 141 个分子 (7.0%)\n",
            "  11个原子: 19 个分子 (0.9%)\n",
            "  12个原子: 38 个分子 (1.9%)\n",
            "  13个原子: 30 个分子 (1.5%)\n",
            "  14个原子: 254 个分子 (12.6%)\n",
            "  15个原子: 12 个分子 (0.6%)\n",
            "  16个原子: 1241 个分子 (61.4%)\n",
            "  20个原子: 2 个分子 (0.1%)\n",
            "  26个原子: 1 个分子 (0.0%)\n",
            "  28个原子: 165 个分子 (8.2%)\n",
            "  32个原子: 4 个分子 (0.2%)\n",
            "\n",
            "============================================================\n",
            "✓ 完整识别(16个原子): 1241 个分子 (61.4%)\n",
            "✗ 不完整识别(非16个): 781 个分子 (38.6%)\n",
            "============================================================\n",
            "\n",
            "可用于侧链裂解的分子索引已保存到变量 'complete_indices'\n",
            "存在桥环问题的分子索引已保存到变量 'incomplete_indices'\n"
          ]
        }
      ],
      "source": [
        "# 分析整个数据集（所有2022个分子）\n",
        "print(\"分析整个数据集...\")\n",
        "all_ring_counts = []\n",
        "\n",
        "for idx in range(len(df)):\n",
        "    smiles = df.iloc[idx]['smiles']\n",
        "    count = check_ring_atoms_count(smiles)\n",
        "    all_ring_counts.append(count)\n",
        "    if (idx + 1) % 500 == 0:\n",
        "        print(f\"已处理 {idx + 1}/{len(df)} 个分子...\")\n",
        "\n",
        "all_ring_counts_array = np.array(all_ring_counts)\n",
        "\n",
        "print(f\"\\n总样本数量: {len(df)}\")\n",
        "print(f\"\\n环原子数量统计:\")\n",
        "unique, counts = np.unique(all_ring_counts_array, return_counts=True)\n",
        "for ring_size, count in zip(unique, counts):\n",
        "    percentage = (count / len(df)) * 100\n",
        "    print(f\"  {ring_size}个原子: {count} 个分子 ({percentage:.1f}%)\")\n",
        "\n",
        "# 统计完整识别的分子\n",
        "complete_all = np.sum(all_ring_counts_array == 16)\n",
        "incomplete_all = np.sum(all_ring_counts_array != 16)\n",
        "\n",
        "print(f\"\\n{'='*60}\")\n",
        "print(f\"✓ 完整识别(16个原子): {complete_all} 个分子 ({complete_all/len(df)*100:.1f}%)\")\n",
        "print(f\"✗ 不完整识别(非16个): {incomplete_all} 个分子 ({incomplete_all/len(df)*100:.1f}%)\")\n",
        "print(f\"{'='*60}\")\n",
        "\n",
        "# 保存完整识别的分子索引\n",
        "complete_indices = [i for i, c in enumerate(all_ring_counts) if c == 16]\n",
        "incomplete_indices = [i for i, c in enumerate(all_ring_counts) if c != 16]\n",
        "\n",
        "print(f\"\\n可用于侧链裂解的分子索引已保存到变量 'complete_indices'\")\n",
        "print(f\"存在桥环问题的分子索引已保存到变量 'incomplete_indices'\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 9. 可视化问题修复\n",
        "\n",
        "修复可视化没有显示编号的问题。在Jupyter Notebook中，需要确保正确显示matplotlib图形。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "测试分子索引: 7\n",
            "分子名称: ARENASTATIN A\n",
            "识别到的环原子数: 16\n",
            "\n",
            "带编号的分子结构:\n"
          ]
        },
        {
          "data": {
            "image/png": "[encoded data, 107724 characters]",
            "text/plain": [
              "<Figure size 1000x1000 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# 启用matplotlib inline显示\n",
        "%matplotlib inline\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "# 选择一个完整识别的分子进行可视化测试\n",
        "if len(complete_indices) > 0:\n",
        "    test_idx = complete_indices[0]\n",
        "    test_smiles = df.iloc[test_idx]['smiles']\n",
        "    test_mol = Chem.MolFromSmiles(test_smiles)\n",
        "    test_numbering = assign_ring_numbering(test_mol)\n",
        "    \n",
        "    print(f\"测试分子索引: {test_idx}\")\n",
        "    print(f\"分子名称: {df.iloc[test_idx]['molecule_pref_name']}\")\n",
        "    print(f\"识别到的环原子数: {len(test_numbering)}\")\n",
        "    print(\"\\n带编号的分子结构:\")\n",
        "    \n",
        "    # 使用修改后的可视化方法\n",
        "    draw_molecule_with_numbering(test_mol, test_numbering, highlight_atoms=list(test_numbering.keys()))\n",
        "else:\n",
        "    print(\"没有找到完整识别的分子！\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 10. 解决方案建议\n",
        "\n",
        "### 方案1：只使用完整识别的分子（推荐作为起点）\n",
        "\n",
        "**优点：**\n",
        "- 保证数据质量和编号准确性\n",
        "- 避免错误的侧链裂解\n",
        "- 代码简单，不需要特殊处理\n",
        "\n",
        "**缺点：**\n",
        "- 可能丢失部分数据\n",
        "- 需要检查剩余分子数量是否足够\n",
        "\n",
        "### 方案2：扩展到其他环大小（14, 15, 17, 18元环）\n",
        "\n",
        "**学术合理性解释：**\n",
        "1. **结构相似性**：14-18元大环内酯都属于大环化合物，具有相似的化学性质和生物活性模式\n",
        "2. **侧链多样性**：不同环大小的大环内酯可能具有不同的侧链修饰模式，扩展研究范围可以增加碎片库的多样性\n",
        "3. **药物化学实践**：在实际药物开发中，研究人员经常考虑不同环大小的类似物来优化药物性质\n",
        "4. **文献支持**：许多大环内酯药物（如大环内酯类抗生素）的环大小在14-16之间变化\n",
        "\n",
        "**实施建议：**\n",
        "- 首先完成16元环的分析\n",
        "- 如果16元环完整识别的分子数量不足（< 500个），再扩展到其他环大小\n",
        "- 在论文中明确说明扩展的原因和合理性\n",
        "\n",
        "### 方案3：改进算法处理桥环结构\n",
        "\n",
        "**技术方案：**\n",
        "- 检测桥环结构：使用RDKit的桥环检测功能\n",
        "- 修改环识别逻辑：考虑桥环原子\n",
        "- 调整编号策略：为桥环结构设计特殊的编号方案\n",
        "\n",
        "**难度：**\n",
        "- 需要更复杂的算法\n",
        "- 可能需要更多的验证\n",
        "- 不同类型的桥环需要不同的处理策略\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "✓ 已创建过滤后的数据集:\n",
            "  原始数据集: 2022 个分子\n",
            "  过滤后数据集: 1241 个分子 (61.4%)\n",
            "  保存路径: ../ring16/temp_filtered_complete.csv\n",
            "\n",
            "建议:\n",
            "  ✓ 过滤后还有 1241 个分子，数量充足，建议使用方案1\n",
            "    可以直接使用这些完整识别的分子进行侧链裂解\n"
          ]
        }
      ],
      "source": [
        "# 创建一个过滤后的CSV文件，只包含完整识别的分子\n",
        "if len(complete_indices) > 0:\n",
        "    df_filtered = df.iloc[complete_indices].copy()\n",
        "    output_path = '../ring16/temp_filtered_complete.csv'\n",
        "    df_filtered.to_csv(output_path, index=False)\n",
        "    \n",
        "    print(f\"✓ 已创建过滤后的数据集:\")\n",
        "    print(f\"  原始数据集: {len(df)} 个分子\")\n",
        "    print(f\"  过滤后数据集: {len(df_filtered)} 个分子 ({len(df_filtered)/len(df)*100:.1f}%)\")\n",
        "    print(f\"  保存路径: {output_path}\")\n",
        "    \n",
        "    print(f\"\\n建议:\")\n",
        "    if len(df_filtered) >= 500:\n",
        "        print(f\"  ✓ 过滤后还有 {len(df_filtered)} 个分子，数量充足，建议使用方案1\")\n",
        "        print(f\"    可以直接使用这些完整识别的分子进行侧链裂解\")\n",
        "    elif len(df_filtered) >= 200:\n",
        "        print(f\"  ⚠ 过滤后有 {len(df_filtered)} 个分子，数量中等\")\n",
        "        print(f\"    建议先尝试方案1，如果碎片多样性不足，再考虑方案2（扩展到其他环大小）\")\n",
        "    else:\n",
        "        print(f\"  ✗ 过滤后只有 {len(df_filtered)} 个分子，数量较少\")\n",
        "        print(f\"    建议使用方案2：扩展到14, 15, 17, 18元环\")\n",
        "        print(f\"    或者考虑方案3：改进算法处理桥环结构\")\n",
        "else:\n",
        "    print(\"✗ 没有找到完整识别的分子！\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 11. 扩展到其他环大小（可选）\n",
        "\n",
        "如果需要扩展到其他环大小，可以使用以下代码检查14, 15, 17, 18元环的分子数量。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "分析前10个分子的多环大小识别情况：\n",
            "\n",
            "分子 0: PATUPILONE\n",
            "  16元环: 14个原子 (不完整)\n",
            "\n",
            "分子 1: PATUPILONE\n",
            "  16元环: 14个原子 (不完整)\n",
            "\n",
            "分子 2: DACTINOMYCIN\n",
            "  16元环: 28个原子 (不完整)\n",
            "\n",
            "分子 3: LARGAZOLE\n",
            "  16元环: 10个原子 (不完整)\n",
            "\n",
            "分子 4: nan\n",
            "  16元环: 9个原子 (不完整)\n",
            "\n",
            "分子 5: nan\n",
            "  16元环: 10个原子 (不完整)\n",
            "\n",
            "分子 6: LARGAZOLE THIOL\n",
            "  16元环: 10个原子 (不完整)\n",
            "\n",
            "分子 7: ARENASTATIN A\n",
            "  16元环: 16个原子 ✓\n",
            "\n",
            "分子 8: nan\n",
            "  16元环: 10个原子 (不完整)\n",
            "\n",
            "分子 9: nan\n",
            "  16元环: 10个原子 (不完整)\n",
            "\n"
          ]
        }
      ],
      "source": [
        "# 检查ChEMBL数据库中其他环大小的大环内酯数量\n",
        "# 这里只是一个示例，实际需要重新从ChEMBL查询\n",
        "\n",
        "def check_multiple_ring_sizes(smiles, ring_sizes=[14, 15, 16, 17, 18]):\n",
        "    \"\"\"检查分子中是否包含指定大小的环\"\"\"\n",
        "    mol = Chem.MolFromSmiles(smiles)\n",
        "    if mol is None:\n",
        "        return {}\n",
        "    \n",
        "    results = {}\n",
        "    for size in ring_sizes:\n",
        "        # 使用SMARTS模式检测特定大小的环\n",
        "        pattern = Chem.MolFromSmarts(f\"[r{size}]\")\n",
        "        if pattern is not None:\n",
        "            matches = mol.GetSubstructMatches(pattern)\n",
        "            unique_atoms = list(set([match[0] for match in matches]))\n",
        "            results[size] = len(unique_atoms)\n",
        "        else:\n",
        "            results[size] = 0\n",
        "    \n",
        "    return results\n",
        "\n",
        "# 分析前10个分子作为示例\n",
        "print(\"分析前10个分子的多环大小识别情况：\\n\")\n",
        "for idx in range(min(10, len(df))):\n",
        "    smiles = df.iloc[idx]['smiles']\n",
        "    name = df.iloc[idx]['molecule_pref_name']\n",
        "    results = check_multiple_ring_sizes(smiles)\n",
        "    \n",
        "    print(f\"分子 {idx}: {name}\")\n",
        "    for size, count in sorted(results.items()):\n",
        "        if count > 0:\n",
        "            print(f\"  {size}元环: {count}个原子\", end=\"\")\n",
        "            if count == size:\n",
        "                print(\" ✓\")\n",
        "            else:\n",
        "                print(\" (不完整)\")\n",
        "    print()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 12. 桥环检测与分析\n",
        "\n",
        "检测哪些分子含有桥环结构，这是导致环原子识别不完整的主要原因。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "分析不完整识别的分子...\n",
            "索引 0: 14个环原子, 桥环: 是 - PATUPILONE\n",
            "索引 1: 14个环原子, 桥环: 是 - PATUPILONE\n",
            "索引 2: 28个环原子, 桥环: 是 - DACTINOMYCIN\n",
            "索引 3: 10个环原子, 桥环: 是 - LARGAZOLE\n",
            "索引 4: 9个环原子, 桥环: 是 - nan\n",
            "索引 5: 10个环原子, 桥环: 是 - nan\n",
            "索引 6: 10个环原子, 桥环: 是 - LARGAZOLE THIOL\n",
            "索引 8: 10个环原子, 桥环: 是 - nan\n",
            "索引 9: 10个环原子, 桥环: 是 - nan\n",
            "索引 12: 9个环原子, 桥环: 是 - nan\n",
            "索引 15: 14个环原子, 桥环: 是 - nan\n",
            "索引 16: 14个环原子, 桥环: 是 - nan\n",
            "索引 18: 10个环原子, 桥环: 是 - nan\n",
            "索引 19: 10个环原子, 桥环: 是 - nan\n",
            "索引 20: 10个环原子, 桥环: 是 - nan\n",
            "索引 21: 20个环原子, 桥环: 是 - nan\n",
            "索引 22: 9个环原子, 桥环: 是 - nan\n",
            "索引 23: 9个环原子, 桥环: 是 - nan\n",
            "索引 24: 9个环原子, 桥环: 是 - nan\n",
            "索引 25: 10个环原子, 桥环: 是 - nan\n",
            "\n",
            "样本统计（前20个不完整分子）:\n",
            "  含桥环: 20 个 (100.0%)\n",
            "  不含桥环: 0 个 (0.0%)\n"
          ]
        }
      ],
      "source": [
        "def has_bridged_ring(mol):\n",
        "    \"\"\"检测分子是否包含桥环结构\"\"\"\n",
        "    if mol is None:\n",
        "        return False\n",
        "    \n",
        "    # 获取所有环信息\n",
        "    ring_info = mol.GetRingInfo()\n",
        "    \n",
        "    # 检查是否有原子属于多个环（可能表示桥环）\n",
        "    for atom in mol.GetAtoms():\n",
        "        if atom.IsInRing():\n",
        "            # 获取该原子所属的环数量\n",
        "            ring_count = ring_info.NumAtomRings(atom.GetIdx())\n",
        "            if ring_count > 1:\n",
        "                # 检查是否至少有一个是16元环\n",
        "                atom_rings = ring_info.AtomRings()\n",
        "                for ring in atom_rings:\n",
        "                    if atom.GetIdx() in ring and len(ring) == 16:\n",
        "                        return True\n",
        "    \n",
        "    return False\n",
        "\n",
        "# 分析不完整识别的分子中有多少含有桥环\n",
        "if len(incomplete_indices) > 0:\n",
        "    print(\"分析不完整识别的分子...\")\n",
        "    bridged_count = 0\n",
        "    non_bridged_count = 0\n",
        "    \n",
        "    # 检查前20个不完整识别的分子\n",
        "    sample = incomplete_indices[:min(20, len(incomplete_indices))]\n",
        "    \n",
        "    for idx in sample:\n",
        "        smiles = df.iloc[idx]['smiles']\n",
        "        mol = Chem.MolFromSmiles(smiles)\n",
        "        has_bridge = has_bridged_ring(mol)\n",
        "        \n",
        "        if has_bridge:\n",
        "            bridged_count += 1\n",
        "        else:\n",
        "            non_bridged_count += 1\n",
        "        \n",
        "        print(f\"索引 {idx}: {all_ring_counts[idx]}个环原子, 桥环: {'是' if has_bridge else '否'} - {df.iloc[idx]['molecule_pref_name']}\")\n",
        "    \n",
        "    print(f\"\\n样本统计（前{len(sample)}个不完整分子）:\")\n",
        "    print(f\"  含桥环: {bridged_count} 个 ({bridged_count/len(sample)*100:.1f}%)\")\n",
        "    print(f\"  不含桥环: {non_bridged_count} 个 ({non_bridged_count/len(sample)*100:.1f}%)\")\n",
        "else:\n",
        "    print(\"所有分子都完整识别，没有不完整的分子！\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 13. 最终建议与行动计划\n",
        "\n",
        "根据上述分析，以下是针对你的研究目标的具体建议：\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "================================================================================\n",
            "最终建议与行动计划\n",
            "================================================================================\n",
            "\n",
            "当前数据集分析结果:\n",
            "  - 总分子数: 2022\n",
            "  - 完整识别(16原子): 1241 (61.4%)\n",
            "  - 不完整识别: 781 (38.6%)\n",
            "\n",
            "推荐方案:\n",
            "\n",
            "✓ 方案1（强烈推荐）：使用完整识别的分子\n",
            "  - 你有 1241 个完整识别的16元环分子\n",
            "  - 这个数量非常充足，可以获得丰富的侧链碎片库\n",
            "  - 数据质量有保证，编号准确\n",
            "  - 不需要扩展到其他环大小\n",
            "\n",
            "下一步操作:\n",
            "  1. 运行上述分析cells，查看具体的数据分布\n",
            "  2. 使用 'temp_filtered_complete.csv' 进行批量侧链裂解\n",
            "  3. 分析碎片多样性和分布\n",
            "  4. 根据结果决定是否需要扩展到其他环大小\n",
            "\n",
            "可视化问题修复:\n",
            "  - Cell 10 的可视化需要添加 '%matplotlib inline'\n",
            "  - 已在 Cell 26 中添加修复代码\n",
            "  - 确保在notebook开头启用 matplotlib inline 模式\n",
            "\n",
            "================================================================================\n"
          ]
        }
      ],
      "source": [
        "print(\"=\"*80)\n",
        "print(\"最终建议与行动计划\")\n",
        "print(\"=\"*80)\n",
        "\n",
        "if 'complete_all' in locals():\n",
        "    print(f\"\\n当前数据集分析结果:\")\n",
        "    print(f\"  - 总分子数: {len(df)}\")\n",
        "    print(f\"  - 完整识别(16原子): {complete_all} ({complete_all/len(df)*100:.1f}%)\")\n",
        "    print(f\"  - 不完整识别: {incomplete_all} ({incomplete_all/len(df)*100:.1f}%)\")\n",
        "    \n",
        "    print(f\"\\n推荐方案:\")\n",
        "    \n",
        "    if complete_all >= 800:\n",
        "        print(f\"\\n✓ 方案1（强烈推荐）：使用完整识别的分子\")\n",
        "        print(f\"  - 你有 {complete_all} 个完整识别的16元环分子\")\n",
        "        print(f\"  - 这个数量非常充足，可以获得丰富的侧链碎片库\")\n",
        "        print(f\"  - 数据质量有保证，编号准确\")\n",
        "        print(f\"  - 不需要扩展到其他环大小\")\n",
        "        \n",
        "    elif complete_all >= 400:\n",
        "        print(f\"\\n✓ 方案1（推荐）：优先使用完整识别的分子\")\n",
        "        print(f\"  - 你有 {complete_all} 个完整识别的16元环分子\")\n",
        "        print(f\"  - 这个数量较为充足\")\n",
        "        print(f\"  - 建议先完成这些分子的侧链裂解和分析\")\n",
        "        print(f\"  - 如果碎片多样性不足，再考虑扩展到14-15元环或17-18元环\")\n",
        "        \n",
        "    else:\n",
        "        print(f\"\\n⚠ 方案2（推荐）：扩展到其他环大小\")\n",
        "        print(f\"  - 你只有 {complete_all} 个完整识别的16元环分子\")\n",
        "        print(f\"  - 建议扩展到14, 15, 17, 18元环\")\n",
        "        print(f\"  - 这样可以获得更多的真实分子侧链碎片\")\n",
        "        \n",
        "        print(f\"\\n学术答辩时的解释策略:\")\n",
        "        print(f\"  1. 强调大环内酯药物的环大小通常在12-18之间\")\n",
        "        print(f\"  2. 不同环大小的大环内酯具有相似的化学骨架和生物活性\")\n",
        "        print(f\"  3. 侧链修饰是决定药物性质的关键因素，与环大小相对独立\")\n",
        "        print(f\"  4. 扩展研究范围可以增加碎片库的多样性和代表性\")\n",
        "        print(f\"  5. 参考文献：大环内酯类抗生素（红霉素14元环，泰乐菌素16元环等）\")\n",
        "    \n",
        "    print(f\"\\n下一步操作:\")\n",
        "    print(f\"  1. 运行上述分析cells，查看具体的数据分布\")\n",
        "    print(f\"  2. 使用 'temp_filtered_complete.csv' 进行批量侧链裂解\")\n",
        "    print(f\"  3. 分析碎片多样性和分布\")\n",
        "    print(f\"  4. 根据结果决定是否需要扩展到其他环大小\")\n",
        "    \n",
        "    print(f\"\\n可视化问题修复:\")\n",
        "    print(f\"  - Cell 10 的可视化需要添加 '%matplotlib inline'\")\n",
        "    print(f\"  - 已在 Cell 26 中添加修复代码\")\n",
        "    print(f\"  - 确保在notebook开头启用 matplotlib inline 模式\")\n",
        "\n",
        "print(\"\\n\" + \"=\"*80)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": []
    }
  ],
  "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.14.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
