Skip to content

Fix off-by-one bugs in 1-indexed loops over pose residues#695

Open
lyskov-ai wants to merge 1 commit into
RosettaCommons:mainfrom
lyskov-ai:fix/off-by-one-pose-residue-loops
Open

Fix off-by-one bugs in 1-indexed loops over pose residues#695
lyskov-ai wants to merge 1 commit into
RosettaCommons:mainfrom
lyskov-ai:fix/off-by-one-pose-residue-loops

Conversation

@lyskov-ai
Copy link
Copy Markdown
Contributor

Summary

Replace `i != end` with `i <= end` (or `i != size()` with `i <= size()`) in several loops that iterate over 1-indexed residue ranges. With the old condition, the last residue (index == end) was silently skipped.

  • `core/pack/task/PackerTask_.cc`: pymol-style debug selection output omitted the last residue.
  • `protocols/forge/components/BDR.cc`:
    • the full-atom check used to gate `switch_to_residue_type_set` skipped the last residue, so a non-full-atom last residue could bypass conversion and later cause sidechain-restore mismatch (the precise failure the surrounding comment warns about).
    • the loop building the `RestrictResidueToRepacking` operation skipped the last residue, so a C-terminal residue outside `new_positions` was designed instead of repack-only.
  • `protocols/fldsgn/BluePrintBDR.cc`: same full-atom check bug as in `BDR.cc`.
  • `protocols/denovo_design/components/StructureData.cc`: `compute_cutpoints` skipped the last residue, dropping a `CUTPOINT_LOWER` variant on the C-terminal residue.
  • `protocols/enzdes/BackboneSampler.cc`: the user-requested number of backbone-Monte-Carlo trials was off by one (e.g. `bb_moves_=1000` actually ran 999 trials, in contradiction with the "Running N trials..." log line printed just above).

These are all instances of the same pattern: a 1-indexed loop using `!=` against a one-past-the-last bound that was actually meant to be the last valid index. Each loop body uses the index directly to access pose data, so the fix is to switch the comparison to `<=`.

Replace `i != end` with `i <= end` (or `i != size()` with `i <= size()`)
in several loops that iterate over 1-indexed residue ranges. With the
old condition, the last residue (index == end) was skipped:

* core/pack/task/PackerTask_.cc: pymol-style debug selection output
  omitted the last residue.
* protocols/forge/components/BDR.cc:
  - the full-atom check used to gate `switch_to_residue_type_set`
    skipped the last residue, so a non-full-atom last residue could
    bypass conversion and later cause sidechain-restore mismatch.
  - the loop building the `RestrictResidueToRepacking` operation
    skipped the last residue, so residues outside `new_positions`
    at the C-terminus were designed instead of repack-only.
* protocols/fldsgn/BluePrintBDR.cc: same full-atom check bug as BDR.
* protocols/denovo_design/components/StructureData.cc:
  `compute_cutpoints` skipped the last residue, dropping a
  CUTPOINT_LOWER variant on the C-terminal residue.
* protocols/enzdes/BackboneSampler.cc: the user-requested number of
  trials was off by one (e.g. `bb_moves_=1000` actually ran 999).
@ajasja
Copy link
Copy Markdown
Member

ajasja commented May 11, 2026

Wow, I can't believe this one went unnoticed for so long:
core/pack/task/PackerTask_.cc: pymol-style debug selection output omitted the last residue.

@lyskov
Copy link
Copy Markdown
Member

lyskov commented May 12, 2026

@ajasja yeah, this is certainly frightening, - and who knows how many more similar bugs are still undiscovered!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants