epam / epam/Indigo

Adding non-matching monomer templates to library leads to change in HELM sequence permutation in cyclic peptide

Open
#3,326 0 comments 0 reactions 0 assignees View on GitHub
User`s Feedback
Dominant language
C++
Stars
406
Forks
134
Avg merge
2d 11h
Merged PRs (30d)
24

Description

**Summary**
We see that a small modification (addition of two monomer templates to a ket file) can change the sequence order within a HELM string. While still valid HELM (they are permutations of the other in a N-to-C cyclized peptide), ideally Indigo would be determinisitic based on the matching monomer templates in the peptide and not be dependent on any non-matching monomer templates.

**Steps to Reproduce**
```bash
python non_determinisitic_helm_sequence.py
```

**Actual behavior**
We receive two different HELM sequences which are valid but which cause challenges for looking at equality.

`HELM string 1: PEPTIDE1{L.I.S.G.F.A.E.Y.V}$PEPTIDE1,PEPTIDE1,1:R1-9:R2$$$V2.0`
`HELM string 2: PEPTIDE1{V.L.I.S.G.F.A.E.Y}$PEPTIDE1,PEPTIDE1,9:R2-1:R1$$$V2.0`

**Expected behavior**
Expected to always get the same HELM sequence out (as well as the same connection points: note the 1:R1-9:R2 vs 9:R2-1:R1)

**Environment details:**
Operating System: Ubuntu 24.04.1 LTS
Kernel: Linux 5.15.167.4-microsoft-standard-WSL2
python 3.12.12

**Attachments**

[example_script_with_two_ket_libs.zip](https://github.com/user-attachments/files/23467213/example_script_with_two_ket_libs.zip)

**Additional context**
I am doing checks to see if we need to generate new HELM strings, for example, when a chemist changes a monomer name and we want the monomer name to show up in updated HELM strings.

We are finding the simple fact of adding non-matching monomer templates to the ket library are causing the HELM sequence string to change.

I am aware there is some sorting of monomer templates (e.g. based on size) but in my mind, the relative positions of the matching monomer templates within the monomer template library (.ket) will be searched in the same relative order when matching.

I've done some debugging and I believe the issue comes down to some non-determinism in the edge traversal across connections.

Here are some notes, albeit from ChatGPT just in case this is helpful

> Where the nondeterminism can creep in
>
> Replacement path depends on library contents (unordered iteration)
>
> BaseMolecule::transformSuperatomsToTemplates(...) calls:
>
> _replaceExpandedMonomerWithTemplate(sg_idx, template_id, *mtl, added_templates, remove_atoms) if a library is present and suitable
>
> else _transformSGroupToTGroup(...)
>
> The exact sequence of “which superatom gets replaced first” can depend on how MonomerTemplateLibrary::monomerTemplates() is iterated inside _replaceExpandedMonomerWithTemplate.
> If that container is a std::unordered_* (or equivalent) the traversal order changes with bucket count, which in turn changes when you add unrelated monomers to the library. That means: even if the set of matched monomers is identical, the first successful match can differ between libraries A and B (because of different hash bucket layouts) → different atoms pushed to remove_atoms at different times.
>
> Compaction depends on the order of removals
>
> After replacements, you call removeAtoms(remove_atoms) (called once at the end). If remove_atoms arrives unsorted or sorted differently between A and B, the reindexing strategy in removeAtoms (and any underlying “swap-last” vs “shift-left” policy) can leave different surviving vertex indices.
> In your saver, enumeration is:
>
> for (auto i : mol.vertices())
> if (mol.isTemplateAtom(i))
> _monomers_enum.emplace(i, mon_idx++);
>
>
> So the output monomer order is current vertex index order, i.e., whatever indices you have after compaction. If compaction order changes, output order changes—even though the actual matched monomers are the same.
>
> Maps with undefined order used in decision paths
>
> You also build helper structures with std::unordered_map (e.g., _monomer_connections, added_templates). Any logic that relies on iteration over these maps (directly or indirectly) can inject order-dependence. (Your emission doesn’t iterate these maps for numbering, but the replacement logic might.)
>

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.