SMILES stereo tags issue
Exported on 7/15/2026 at 11:07:43 GMT+8 from Cursor (3.7.27)
User
C1=C[C@@]2(OC)C=C[C@]1(OC)c1ccc(cc1)[C@H]1C=CC@(C=C1)[C@H]1C=CC@H[C@H]1C=CC@(C=C1)[C@H]1C=C[C@@H]2C=C1
在这个smiles使用 get_stereo_tags 会无限循环,卡死,请查看原因
Cursor
正在查找 get_stereo_tags 的实现,并分析该 SMILES 导致无限循环的原因。
get_stereo_tags 调用了 assignStereochemistry 和 CIPLabeler::assignCIPLabels,接下来我会在这两处定位可能的无限循环。
接下来我会用该 SMILES 复现问题,并确认是否由 CIPLabeler 的默认无限迭代引起。
原因分析
问题不在 SMILES 解析,而在 get_stereo_tags() 内部调用的 CIP 立体标签计算。
调用链
std::string JSMolBase::get_stereo_tags() {
bj::object doc;
bool cleanIt = true;
bool force = true;
bool flagPossibleStereocenters = true;
MolOps::assignStereochemistry(get(), cleanIt, force,
flagPossibleStereocenters);
CIPLabeler::assignCIPLabels(get());
AssignStereochemistry— 很快(约 0.1 ms)CIPLabeler::assignCIPLabels(get())— 卡死在这里
根本原因:高对称分子 + 无迭代上限
assignCIPLabels 的默认参数是 maxRecursiveIterations = 0,表示不设上限:
if (maxRecursiveIterations != 0) {
remainingCallCount = maxRecursiveIterations;
} else {
remainingCallCount = UINT_MAX; // really big - will never be hit
}
CIP 文档也说明了这一点——高度对称分子会导致“伪无限”计算(例如十二面烷 dodecahedrane):
/*
Some very symmetrical mols can cause pseudo infinite processing
(e.g. dodecahedrane)
To avoid this a maxinum number of iterations can be set by the caller as a
parameter to assignCIPLabels.
If that maximum value is exceeded, the following error is thrown
*/
该分子的特征
| 属性 | 值 |
|---|---|
| 原子数 | 44 |
| 手性中心 | 10 个(四面体 CW/CCW) |
| 结构 | 多环稠合,含多个重复的 [C@](OC)(C=C) 手性单元 |
实测 CIP 计算耗时(同一 SMILES):
| maxRecursiveIterations | 结果 | 耗时 |
|---|---|---|
| 100,000 | 失败(超出迭代上限) | 0.03 s |
| 10,000,000 | 失败 | 2.4 s |
| 100,000,000 | 失败 | 24.5 s |
| 200,000,000 | 失败 | 48.9 s |
| 500,000,000 | 成功 | ~79 s |
0(无上限,即 get_stereo_tags 的行为) |
成功 | ~79 s |
结论:该分子 CIP 排序大约需要 2–5 亿次递归比较。不是死循环,但在浏览器/MinimalLib 里会长时间无响应,看起来像“卡死”。
成功时的标签示例:
{2: 's', 7: 's', 16: 'r', 19: 'r', 24: 's', 27: 's', 30: 'r', 33: 'r', 38: 's', 41: 's'}
为何 get_stereo_tags 特别容易出问题
- 未传
maxRecursiveIterations,走无上限路径 - 未捕获
MaxIterationsExceeded,超时也无法优雅降级 - 10 个手性中心相互依赖(
labelAux交叉引用),比单中心计算量大得多 - 在 JS/WASM 环境中,79 秒可能更长,且无进度反馈
建议修复方向
在 get_stereo_tags() 中为 CIP 计算设置合理上限,并捕获异常,对未解析中心返回 "?":
// 建议修改 minilib.cpp
try {
CIPLabeler::assignCIPLabels(get(), 1250000); // 文档建议的典型上限
} catch (const CIPLabeler::MaxIterationsExceeded &) {
// 部分中心可能无 CIP 标签,后续逻辑已有 "?" 处理
}
或在 MinimalLib API 中增加可选的 maxRecursiveIterations 参数,让调用方自行控制。
总结:该 SMILES 是高度对称的多环手性分子,CIP 算法需要约 2–5 亿次递归比较(约 1–2 分钟)。get_stereo_tags() 调用 assignCIPLabels 时未设迭代上限,在 UI 中表现为长时间无响应。如需,我可以直接改 minilib.cpp 并补测试。
User
<system_notification>
The following task has finished. If you were already aware, ignore this notification and do not restate prior responses.
Cursor
结论
该 SMILES 不是真正的死循环,而是 get_stereo_tags() 内部调用 CIPLabeler::assignCIPLabels() 时未设置迭代上限(默认 maxRecursiveIterations=0,即无限制)。
这个分子有 10 个手性中心、结构高度对称,CIP 排序大约需要 2–5 亿次递归比较。实测约 79 秒才能完成;在浏览器/MinimalLib 里长时间无响应,看起来像卡死。
卡住的位置:AssignStereochemistry 很快(<1 ms),瓶颈在 assignCIPLabels。
可选修复:在 minilib.cpp 的 get_stereo_tags() 中为 CIP 传入合理的 maxRecursiveIterations,并捕获 MaxIterationsExceeded,对未解析中心返回 "?"。需要的话我可以直接改代码。
评论区