-
Notifications
You must be signed in to change notification settings - Fork 780
Adding guards to index assignment in Universe creation #5228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello there first time contributor! Welcome to the MDAnalysis community! We ask that all contributors abide by our Code of Conduct and that first time contributors introduce themselves on GitHub Discussions so we can get to know you. You can learn more about participating here. Please also add yourself to package/AUTHORS as part of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR addresses Issue #5223 by adding early validation in Universe.empty() so invalid atom_resindex / residue_segindex mappings fail fast with a clear error, and adds a regression test to confirm the behavior.
Changes:
- Added bounds checks for
atom_resindexandresidue_segindexinUniverse.empty(). - Added a testsuite regression test asserting
ValueErroris raised on out-of-bounds indices. - Updated
CHANGELOGandAUTHORS.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| testsuite/MDAnalysisTests/core/test_universe.py | Adds coverage for out-of-bounds index inputs to Universe.empty(). |
| package/MDAnalysis/core/universe.py | Introduces bounds-check guards for index mapping inputs. |
| package/CHANGELOG | Records the fix in release notes (currently with formatting/date issues). |
| package/AUTHORS | Adds contributor name for 2026. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
package/MDAnalysis/core/universe.py
Outdated
| atom_resindex = np.asarray(atom_resindex) | ||
| if np.any(atom_resindex >= n_residues) or np.any(atom_resindex < 0): | ||
| raise ValueError( | ||
| "atom_resindex contains invalid residue indices. " | ||
| "All values must be between 0 and n_residues-1." | ||
| ) | ||
|
|
||
| if residue_segindex is not None: | ||
| residue_segindex = np.asarray(residue_segindex) | ||
| if np.any(residue_segindex >= n_segments) or np.any(residue_segindex < 0): | ||
| raise ValueError( | ||
| "residue_segindex contains invalid segment indices. " | ||
| "All values must be between 0 and n_segments-1." | ||
| ) |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bounds check does np.any(atom_resindex >= n_residues) after np.asarray(atom_resindex). If atom_resindex contains non-numeric values (e.g., strings/object dtype), this comparison raises a TypeError, changing the failure mode from the ValueError this guard intends to provide. Consider coercing to an integer array (or catching TypeError/ValueError) and re-raising a ValueError with a clear message.
| atom_resindex = np.asarray(atom_resindex) | |
| if np.any(atom_resindex >= n_residues) or np.any(atom_resindex < 0): | |
| raise ValueError( | |
| "atom_resindex contains invalid residue indices. " | |
| "All values must be between 0 and n_residues-1." | |
| ) | |
| if residue_segindex is not None: | |
| residue_segindex = np.asarray(residue_segindex) | |
| if np.any(residue_segindex >= n_segments) or np.any(residue_segindex < 0): | |
| raise ValueError( | |
| "residue_segindex contains invalid segment indices. " | |
| "All values must be between 0 and n_segments-1." | |
| ) | |
| try: | |
| # Ensure atom_resindex is an integer array so that bounds | |
| # checks do not raise unexpected TypeError for non-numeric input. | |
| atom_resindex = np.asarray(atom_resindex, dtype=int) | |
| except (TypeError, ValueError): | |
| raise ValueError( | |
| "atom_resindex must be an array of integers." | |
| ) from None | |
| if np.any(atom_resindex >= n_residues) or np.any(atom_resindex < 0): | |
| raise ValueError( | |
| "atom_resindex contains invalid residue indices. " | |
| "All values must be between 0 and n_residues-1." | |
| ) | |
| if residue_segindex is not None: | |
| try: | |
| # Ensure residue_segindex is an integer array so that bounds | |
| # checks do not raise unexpected TypeError for non-numeric input. | |
| residue_segindex = np.asarray(residue_segindex, dtype=int) | |
| except (TypeError, ValueError): | |
| raise ValueError( | |
| "residue_segindex must be an array of integers." | |
| ) from None | |
| if np.any(residue_segindex >= n_segments) or np.any(residue_segindex < 0): | |
| raise ValueError( | |
| "residue_segindex contains invalid segment indices. " | |
| "All values must be between 0 and n_segments-1." | |
| ) |
package/MDAnalysis/core/universe.py
Outdated
| if np.any(atom_resindex >= n_residues) or np.any(atom_resindex < 0): | ||
| raise ValueError( | ||
| "atom_resindex contains invalid residue indices. " | ||
| "All values must be between 0 and n_residues-1." | ||
| ) | ||
|
|
||
| if residue_segindex is not None: | ||
| residue_segindex = np.asarray(residue_segindex) | ||
| if np.any(residue_segindex >= n_segments) or np.any(residue_segindex < 0): | ||
| raise ValueError( | ||
| "residue_segindex contains invalid segment indices. " | ||
| "All values must be between 0 and n_segments-1." |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as atom_resindex: np.any(residue_segindex >= n_segments) can raise TypeError for non-numeric/object inputs, which undermines the goal of raising a consistent ValueError on invalid indices. Consider validating/coercing input (or catching TypeError/ValueError) and raising ValueError with an informative message.
| if np.any(atom_resindex >= n_residues) or np.any(atom_resindex < 0): | |
| raise ValueError( | |
| "atom_resindex contains invalid residue indices. " | |
| "All values must be between 0 and n_residues-1." | |
| ) | |
| if residue_segindex is not None: | |
| residue_segindex = np.asarray(residue_segindex) | |
| if np.any(residue_segindex >= n_segments) or np.any(residue_segindex < 0): | |
| raise ValueError( | |
| "residue_segindex contains invalid segment indices. " | |
| "All values must be between 0 and n_segments-1." | |
| try: | |
| invalid_atom_resindex = ( | |
| np.any(atom_resindex >= n_residues) | |
| or np.any(atom_resindex < 0) | |
| ) | |
| except (TypeError, ValueError) as err: | |
| raise ValueError( | |
| "atom_resindex must be an array of integers between " | |
| "0 and n_residues-1." | |
| ) from err | |
| else: | |
| if invalid_atom_resindex: | |
| raise ValueError( | |
| "atom_resindex contains invalid residue indices. " | |
| "All values must be between 0 and n_residues-1." | |
| ) | |
| if residue_segindex is not None: | |
| residue_segindex = np.asarray(residue_segindex) | |
| try: | |
| invalid_residue_segindex = ( | |
| np.any(residue_segindex >= n_segments) | |
| or np.any(residue_segindex < 0) | |
| ) | |
| except (TypeError, ValueError) as err: | |
| raise ValueError( | |
| "residue_segindex must be an array of integers between " | |
| "0 and n_segments-1." | |
| ) from err | |
| else: | |
| if invalid_residue_segindex: | |
| raise ValueError( | |
| "residue_segindex contains invalid segment indices. " | |
| "All values must be between 0 and n_segments-1." |
package/MDAnalysis/core/universe.py
Outdated
| raise ValueError( | ||
| "atom_resindex contains invalid residue indices. " | ||
| "All values must be between 0 and n_residues-1." | ||
| ) | ||
|
|
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting: the closing parenthesis for the raise ValueError(...) call is indented inconsistently with other multi-line raises in this file (e.g., Universe.load_new), and there appears to be trailing whitespace on the blank line following it. Align the closing ) with the raise and remove trailing whitespace to match the file's style.
| with pytest.raises(ValueError, match="atom_resindex contains invalid residue indices."): | ||
| mda.Universe.empty(n_atoms=2, n_residues=2, atom_resindex=[0, 2], trajectory=True) | ||
|
|
||
| with pytest.raises(ValueError, match="residue_segindex contains invalid segment indices."): |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pytest.raises(..., match=...) treats match as a regex. The trailing . in the pattern will match any character, not a literal period, making the assertion looser than intended. Escape the dot (or use re.escape(...)) so the test strictly matches the error message.
| with pytest.raises(ValueError, match="atom_resindex contains invalid residue indices."): | |
| mda.Universe.empty(n_atoms=2, n_residues=2, atom_resindex=[0, 2], trajectory=True) | |
| with pytest.raises(ValueError, match="residue_segindex contains invalid segment indices."): | |
| with pytest.raises(ValueError, match="atom_resindex contains invalid residue indices\."): | |
| mda.Universe.empty(n_atoms=2, n_residues=2, atom_resindex=[0, 2], trajectory=True) | |
| with pytest.raises(ValueError, match="residue_segindex contains invalid segment indices\."): |
Fixes #5223
Changes made in this Pull Request:
atom_resindexandresidue_segindexinUniverse.empty()to ensure indices are within the valid range.test_empty_universe_boundsintestsuite/MDAnalysisTests/core/test_universe.pyto confirm that aValueErroris raised for out-of-bounds indices.CHANGELOGandAUTHORSto reflect these changes.LLM / AI generated code disclosure
LLMs or other AI-powered tools (beyond simple IDE use cases) were used in this contribution: no
PR Checklist
package/CHANGELOGfile updated?package/AUTHORS? (If it is not, add it!)Developers Certificate of Origin
I certify that I can submit this code contribution as described in the Developer Certificate of Origin, under the MDAnalysis LICENSE.
📚 Documentation preview 📚: https://mdanalysis--5228.org.readthedocs.build/en/5228/