Skip to content

nanobind: fix GetNeighbors()/GetBonds() indexing - #9658

Merged
greglandrum merged 1 commit into
rdkit:masterfrom
barlowa124:fix-nb-seqholder-indexing
Oct 10, 2026
Merged

greglandrum merged 1 commit into
rdkit:masterfrom
barlowa124:fix-nb-seqholder-indexing

Conversation

@barlowa124

@barlowa124 barlowa124 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Reference Issue

Fixes #9645

What does this implement/fix? Explain your changes.

Under the nanobind wrappers, AtomSeqHolder::operator[] and BondSeqHolder::operator[] in Code/GraphMol/nbWrap/seqholders.hpp resolved an index with mol->getAtomWithIdx(idx) / mol->getBondWithIdx(idx). For a sub-range sequence this returns the molecule's i-th atom or bond when the caller asked for the i-th element of the sequence. Iteration was correct, so seq[i] and the i-th iterated element disagreed.

Only Atom.GetNeighbors() and Atom.GetBonds() produced visibly wrong results. On Mol.GetAtoms() and Mol.GetBonds() the molecule-global index coincidentally matches the sequence index.

operator[] now bounds-checks idx against iter.size() and dereferences the sequence's own iterator via std::advance. This resolves to a single += for the random-access atom iterators and a linear walk for the bidirectional bond iterators. Negative indices are wrapped before the bounds check, matching the tuple semantics of the boost.python wrappers and the convention used by the MolSupplier __getitem__ implementations.

rough_test.py::test15Neighbors now asserts indexed access matches iteration for both neighbors and bonds. It also covers negative-index wrapping and out-of-range IndexError.

Any other comments?

Checked on a local nanobind build (2026.09.1b1 nanobind, py3.12, macOS arm64, RDK_BUILD_NANOBIND_WRAPPERS=ON with boost serialization off). With CCNO, GetNeighbors() on atom 2 yields the same 1, 3 whether iterated or indexed, and GetBonds() yields 1, 2 both ways. Mol.GetAtoms()/GetBonds() indexing is unchanged. QueryAtomIterSeq and ConformerIterSeq were checked and already index their own ranges correctly.

AtomSeqHolder::operator[] and BondSeqHolder::operator[] returned
mol->getAtomWithIdx(idx) / mol->getBondWithIdx(idx), i.e. the molecule's
i-th atom/bond instead of the i-th element of the held iterator. For
the whole-molecule sequences (Mol.GetAtoms()/GetBonds()) the two are
coincidentally identical, but for Atom.GetNeighbors()/GetBonds() the
indexing silently returned wrong atoms/bonds (iteration was correct).

Index the sequence's own iterator instead. std::advance resolves to
O(1) for the random-access atom iterators and a linear walk for the
bidirectional bond iterators. Also support negative indices, matching
the boost.python tuple semantics and the convention used by the
MolSupplier __getitem__ implementations.
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Fixes indexing behavior in neighbor and bond accessor methods.

The PR appears safe to merge; no actionable issue was identified.

Summary

The PR changes nanobind atom and bond sequence indexing to select an element from the sequence’s own iterator range rather than by molecule-global index.

  • Adds negative-index handling and retains bounds checks.
  • Adds regression assertions comparing indexed access with iteration for neighbors and bonds.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Python sequence index"] --> B["Wrap negative index and check bounds"]
  B --> C["Advance sequence iterator"]
  C --> D["Return atom or bond at that position"]
Loading

Reviews (1) · Last reviewed commit: "nanobind: fix GetNeighbors()/GetBonds() ..."

@greglandrum greglandrum added this to the 2026_09_2 milestone Oct 10, 2026

@greglandrum greglandrum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@greglandrum greglandrum removed the bug label Oct 10, 2026
@greglandrum
greglandrum merged commit 68c007f into rdkit:master Oct 10, 2026
14 checks passed
@greglandrum

Copy link
Copy Markdown
Member

Thanks for the contribution @barlowa124

greglandrum pushed a commit that referenced this pull request Nov 5, 2026
AtomSeqHolder::operator[] and BondSeqHolder::operator[] returned
mol->getAtomWithIdx(idx) / mol->getBondWithIdx(idx), i.e. the molecule's
i-th atom/bond instead of the i-th element of the held iterator. For
the whole-molecule sequences (Mol.GetAtoms()/GetBonds()) the two are
coincidentally identical, but for Atom.GetNeighbors()/GetBonds() the
indexing silently returned wrong atoms/bonds (iteration was correct).

Index the sequence's own iterator instead. std::advance resolves to
O(1) for the random-access atom iterators and a linear walk for the
bidirectional bond iterators. Also support negative indices, matching
the boost.python tuple semantics and the convention used by the
MolSupplier __getitem__ implementations.

Co-authored-by: barlowa124 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nanobind: Atom.GetNeighbors()[i] and Atom.GetBonds()[i] index the whole molecule

2 participants