Skip to content

Crops#48

Open
willsheffler wants to merge 32 commits intomainfrom
crops
Open

Crops#48
willsheffler wants to merge 32 commits intomainfrom
crops

Conversation

@willsheffler
Copy link
Copy Markdown
Contributor

@willsheffler willsheffler commented Apr 22, 2025

Description

  • Introduced new symmetry generation functionalities in pymol_symgen.py.
  • Added comprehensive tests for ASU grid placement in oldtest_asugrid.py.
  • Refactored initialization in __init__.py to improve performance tracking.
  • Updated import paths for CUDA functionality in qcp_rms.py.

Changes walkthrough 📝

Relevant files
Enhancement
pymol_symgen.py
New symmetry generation and visualization functionalities

legacy/_symfit/pymol_symgen.py

  • Added hacky_xtal_maker function for crystal structure generation.
  • Introduced PymolSymElem class for symmetry elements in PyMOL.
  • Implemented BuildCGO class for building CGO objects in PyMOL.
  • Added various utility functions for symmetry and visualization.
  • +1604/-1
    __init__.py
    Refactor initialization and performance tracking                 

    ipd/init.py

  • Replaced timing mechanism with evn.chrono for performance tracking.
  • Updated imports for CUDA functionality.
  • Cleaned up initialization checkpoints.
  • +19/-32 
    qcp_rms.py
    Fix import path for CUDA RMS                                                         

    ipd/cuda/rms/qcp_rms.py

    • Updated import path for CUDA RMS functionality.
    +1/-1     
    Tests
    oldtest_asugrid.py
    New tests for ASU grid placement functionality                     

    legacy/_symfit/oldtests/oldtest_asugrid.py

  • Added tests for place_asu_grid functionality.
  • Included multiple test cases for different symmetry types.
  • Ensured coverage for edge cases in grid placement.
  • +432/-1 

    💡 Penify usage:
    Comment /help on the PR to get a list of all available Penify tools and their descriptions

    …pdate dependencies
    
    - Added `is_symmetric`, `asupos`, and `positioned_atoms` properties to `Body` and `SymBody`
    - Improved validation in `BodyContacts` to reject redundant comparisons
    - Included `tokens1` and `tokens2` metadata in `ContactMatrixStack`; enforced nonempty contact matrix
    - Exposed `symbody()` and `symbodies()` accessors on `Components`
    - Refined `func_params` docstring and `picklocals` frame logic for Python <3.12
    - Updated `@iterize_on_first_param` decorator to pass `wrap_ctx` to `info()`
    - Bumped version to 0.3.9; upgraded `httpx`, `hydra-core`, `omegaconf` and pinned `torch==2.6.0`
    - Removed `pytest-benchmark` and `py-cpuinfo` from dev dependencies
    - Added documentation link to `README.md`
    …lity from `ipd.dev.cuda` to `ipd.cuda`
    
    BREAKING CHANGE: The `ipd._prelude.chrono` module and the `global_chrono` mechanism have been removed. All CUDA-related imports formerly accessed via `ipd.dev.cuda` are now found under `ipd.cuda`. This affects lazy imports, voxel utilities, RMS scanning, and CUDA extension building. Any direct usage of `chrono`, `@chrono`, or `checkpoint()` must be updated or removed.
    
    Additionally:
    - Deleted `chrono.py` and its associated tests
    - Updated `lazyimport` to support fallback tuple syntax
    - Adjusted tests and CUDA modules to reflect new package structure
    - Deprecated `fast` pytest marker in favor of clearer alternatives
    …ine component merging
    
    - Enable generic `SymAdapt` dispatch for all dataclasses by:
      - Introducing a `DataclassProxy` registration target
      - Wrapping the `singledispatch` dispatcher to route dataclasses via `is_dataclass`
    - Replace legacy `DepRecatEd_symAdaptTensor` with `deprecated_SymAdaptTensor` consistently
    - Unify `find_components_by_seqaln_rmsfit` logic:
      - Rename recursive function with underscore prefix
      - Integrate `merge_small_components` logic into top-level function
      - Add new options: `pickchain` and `min_chain_atoms` for selective merging
    - Clean up `merge_small_components` signature and move to main function scope
    - Add debug logging to `SymmetryManager.__call__` for unsupported types
    - Improve type hints and cleanup `SymAdapt` class declarations
    Added .pre-commit-config.yaml with the following tools:
    - Commitizen for conventional commit linting
    - Ruff for linting, formatting, and import sorting
    - Pre-commit hooks for whitespace, YAML, and merge conflict checks
    - validate-pyproject for validating pyproject.toml metadata
    - pytest-pre-commit with `ipd/tests` and parallel execution support
    …ss codebase
    
    This change updates all occurrences of `ContactMatrixStack` to the more descriptive and consistent
    `ContactBlockMatrix`, reflecting its role in handling block-wise contact matrices for biological structures.
    
    - Renamed class in `contact_matrix.py`, including all docstrings and doctests
    - Updated all references in documentation, tests, and tutorials accordingly
    - Changed `contact_matrix_stack()` to `contact_blocks()` for improved clarity
    - Adjusted SymBody, BodyContacts, and test logic to match new naming
    - Preserved compatibility with doctests and rich repr output
    
    Also included in this commit:
    - Migrated init-time profiling from custom logic to `evn.chrono_*` API
    - Refactored `lazy_import.py` to remove pip/mamba install logic and simplify usage
    - Added `Frags` and `FragsOne2Many` data classes for contact-based fragment grouping
    - Introduced `npNone` and `NumpyNone` sentinel types for struct field defaults
    - Improved select logic in `atom_utils` and added `chaincom()` utility
    - Enhanced `Body` with `token`-based atom indexing methods
    - Added new test coverage in `test_contact_matrix.py` and `test_sym_builder.py`
    - Updated dependency bounds for `evn` and pytest in `pyproject.toml`
    …ly module
    
    This commit introduces several refactoring changes for consistency and maintainability, improves type hinting and documentation, and temporarily disables the `ipd.atom.assembly` module.
    
    **Refactoring & Standardization:**
    
    * **Metadata Handling:** Introduced `ipd.dev.HoldsMetadata` base class. Classes requiring metadata capabilities (like `Body`, `SymBody`) now inherit from this class, providing `set/get/sync_metadata` methods and a `meta` property explicitly. The `@ipd.dev.holds_metadata` decorator now primarily handles patching `__copy__` to preserve metadata.
    * **Dataclass Usage:** Replaced custom `@ipd.struct`/`@ipd.mutablestruct` decorators with `@ipd.dc.dataclass` (likely standard `dataclasses.dataclass`) across classes like `Body`, `SymBody`, and `ContactBlockMatrix`.
    * **Dataclass Decorator Wrapping:** Applied `@functools.wraps(dc.dataclass)` to struct decorators in `ipd/_prelude/structs.py` to preserve original class metadata (name, docstring).
    * **Mutable Default Fix:** Changed `ipd.npNone` from a direct `dc.field` instance to a function `npNone()` that *returns* a `dc.field`. This ensures each dataclass instance gets a new `NumpyNone` array, preventing issues with shared mutable defaults. (`Body.rescen`, `Body.tokens`).
    * **BVH Operation Dispatch:** Modified `_bvh_binary_operation` in `body.py` to accept the operation name as a string (e.g., 'bvh_isect') and use `getattr` for dispatch, instead of passing the function object directly.
    
    **Improvements & Fixes:**
    
    * **`ipd.atom.atom_utils`:**
        * Added extensive `typing.overload` definitions for `select` function to improve static type checking based on `chainlist`/`chaindict` arguments.
        * Refactored `select` internals for clarity, using explicit casts (`castaa`), `np.isin`, and handling default string args consistently.
        * Fixed potential `UFuncNoLoopError` in `join` by using `np.char.add` for string concatenation with numpy arrays.
    * **`ipd.atom.body`:**
        * Corrected `SymBody.coord` property calculation.
        * Improved type hints in `SymBody.contacts`.
    * **Namespace:** Added `Observer` to the top-level `ipd` namespace via `ipd/__init__.py`.
    
    **Documentation:**
    
    * **`ipd.observer.observer`:** Added extensive module, class, and method docstrings explaining the Observer pattern implementation (`Subject`, `Observer`, `ObserverMethod`, `hub`).
    
    **Code Disabling:**
    
    * **`ipd.atom.assembly`:** The entire content of this module (including `Assembly`, `AsuSelector`, `NeighborhoodSelector` classes) has been commented out, effectively disabling this functionality.
    
    **Minor:**
    
    * Cleaned up minor formatting and type hints in various files.
    * Used `contextlib.suppress` in `ipd.dev.meta.shallow_copy`.
    Copilot AI review requested due to automatic review settings April 22, 2025 02:23
    @sourcery-ai
    Copy link
    Copy Markdown
    Contributor

    sourcery-ai bot commented Apr 22, 2025

    Reviewer's Guide by Sourcery

    This pull request includes several major changes, including refactoring data classes to use ipd.dc.dataclass, improving the lazy import mechanism, enhancing the select function, modifying find_components_by_seqaln_rmsfit, refactoring the observer pattern implementation, updating SymAdapt classes, modifying sym_builder.py, updating ContactBlockMatrix, updating lazy imports to use ipd.cuda, and removing CUDA-related tests from the main test suite.

    No diagrams generated as the changes look simple and do not need a visual representation.

    File-Level Changes

    Change Details Files
    Switched from ipd.struct to ipd.dc.dataclass for defining data classes and added NumpyNone for default values.
    • Replaced @ipd.struct with @ipd.dc.dataclass for Body, SymBody, and ContactBlockMatrix.
    • Added NumpyNone class and npNone() function for handling default None values in dataclasses.
    • Updated dataclass fields to use npNone() for default None values.
    • Added is_symmetric property to Body and SymBody classes.
    • Added asupos property to Body and SymBody classes.
    ipd/_prelude/structs.py
    ipd/atom/body.py
    ipd/homog/contact_matrix.py
    Refactored lazy import mechanism to improve performance and error handling.
    • Modified lazyimport to accept a tuple of module names for contingency imports.
    • Added timed_import_module to measure import times.
    • Improved error handling in _LazyModule to provide more informative error messages.
    • Added maybeimport and maybeimports functions for conditional module imports.
    • Added in_doctest function to check if the code is running in a doctest environment.
    • Removed pip and mamba installation options from lazyimport.
    • Added forbid set to prevent specific imports.
    ipd/_prelude/lazy_import.py
    Enhanced the select function in ipd/atom/atom_utils.py to handle AtomArray and AtomArrayStack objects more effectively.
    • Added type hints and overloads for better clarity and type safety.
    • Modified the function to accept AtomArray or AtomArrayStack as input.
    • Removed redundant code and improved the selection logic.
    • Added castaa function to cast to AtomArray.
    ipd/atom/atom_utils.py
    Modified find_components_by_seqaln_rmsfit to include a minimum chain atoms parameter and merge small components.
    • Added min_chain_atoms parameter to find_components_by_seqaln_rmsfit.
    • Added a call to merge_small_components after finding components.
    • Removed the recursive call to find_components_by_seqaln_rmsfit_recurse and renamed the function to _find_components_by_seqaln_rmsfit_recurse.
    • Renamed accumulate_seqalign_rmsfit to _accumulate_seqalign_rmsfit.
    ipd/atom/components.py
    Refactored observer pattern implementation for better debug control and usage tracking.
    • Added ObserverError exception class.
    • Added ObserverMethod class to handle method calls on observers.
    • Added _usage dictionary to track observer method usage.
    • Added _debug flag to enable/disable debug output.
    • Modified Subject class to handle debug settings from conf.viz.
    • Modified Subject class to print observer warnings and method usage on shutdown.
    • Added call_is_allowed method to determine whether an observer call is permitted.
    • Modified Observer class to auto-register instances and define a set_config() method.
    • Added HoldsMetadata class to ipd.dev for observer components.
    ipd/observer/observer.py
    ipd/dev/metadata.py
    Updated SymAdapt classes to use __adapts__ for adapted type definition and improved dataclass handling.
    • Replaced adapts with __adapts__ for defining adapted types in SymAdapt subclasses.
    • Modified SymAdapt to register multiple adapted types using AdaptTypes.
    • Added DataclassProxy and updated _sym_adapt.register to handle dataclasses.
    • Removed adapts = None from SymAdaptNamedDenseTensor and SymAdaptNamedSparseTensor.
    • Replaced DepRecatEd_symAdaptTensor with deprecated_SymAdaptTensor.
    ipd/sym/sym_adapt.py
    Modified sym_builder.py to improve symmetry building from components.
    • Added get_args function to parse command line arguments.
    • Added get_component_syminfo function to extract symmetry information from components.
    • Modified build_from_components_abbas to handle multiple input files and output the joint structure.
    • Added a main function to parse arguments and call the appropriate function.
    ipd/sym/sym_builder.py
    Updated ContactBlockMatrix to ensure non-empty contacts and added token support.
    • Added checks to ensure that the contacts array is not empty.
    • Added tokens1 and tokens2 attributes to store token information.
    • Added to_original_index method to convert indices to original indices.
    • Modified __repr__ to display ContactBlockMatrix instead of ContactMatrixStack.
    ipd/homog/contact_matrix.py
    Updated lazy imports to use ipd.cuda instead of ipd.dev.cuda.
    • Replaced ipd.dev.cuda with ipd.cuda in lazy import statements.
    • Updated import statements in various files to reflect the change.
    ipd/__init__.py
    ipd/cuda/voxel/voxdock.py
    ipd/cuda/voxel/voxel.py
    ipd/cuda/cudafunc.py
    Removed CUDA-related tests and code from the main test suite and moved them to a separate CUDA test suite.
    • Added pytest.mark.slow to tests that take a long time to run.
    • Added evn.testing.quicktest to run tests quickly.
    • Removed CUDA-related tests from ipd/tests/conftest.py.
    • Removed CUDA-related tests from ipd/tests/maintest.py.
    • Removed CUDA-related tests from ipd/tests/homog/test_thgeom.py.
    • Removed CUDA-related tests from ipd/tests/sym/test_ipd_sym_manager.py.
    ipd/tests/conftest.py
    ipd/tests/maintest.py
    ipd/tests/homog/test_thgeom.py
    ipd/tests/sym/test_ipd_sym_manager.py

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!
    • Generate a plan of action for an issue: Comment @sourcery-ai plan on
      an issue to generate a plan of action for it.

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    Copy link
    Copy Markdown

    Copilot AI left a 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, titled "Crops", reorganizes and extends several modules in the IPD codebase, including updates to atom utility functions, lazy imports, and initialization code while also integrating new modules (frags and crops). Key changes include:

    • Refactoring import statements and type annotations in ipd/atom/atom_utils.py (e.g., using "import typing as t").
    • Enhancing the overloaded "select" function and updating "join" to use np.char.add.
    • Updating prelude modules and the main init.py to use evn.chrono_* functions, as well as minor changes in workflows and configuration files.

    Reviewed Changes

    Copilot reviewed 127 out of 134 changed files in this pull request and generated 1 comment.

    Show a summary per file
    File Description
    ipd/atom/atom_utils.py Refactored import style, updated overloaded functions and added a new "select" implementation
    ipd/atom/assembly.py Commented legacy assembly code and maintained changes for potential new usage
    ipd/atom/init.py Added new imports for frags and crops modules to support "Crops" feature
    ipd/_prelude/* Updated lazy import and structs modules; removed the chrono module
    ipd/init.py Replaced ipd_init_checkpoint with evn.chrono_checkpoint calls
    docs/conf.py, README.md, workflow files Minor updates to documentation paths and configuration settings
    Files not reviewed (7)
    • .python-version: Language not supported
    • docs/dev_guide/coding.rst: Language not supported
    • docs/dev_guide/dependencies.rst: Language not supported
    • docs/tutorials/contact_matrix.rst: Language not supported
    • docs/tutorials/symmetry_detection.rst: Language not supported
    • docs/tutorials/using_homog.rst: Language not supported
    • docs/tutorials/working_with_atoms.rst: Language not supported
    Comments suppressed due to low confidence (1)

    ipd/atom/atom_utils.py:103

    • [nitpick] Consider using a more descriptive and conventional sentinel name (e.g., 'MISSING_STR') for clarity.
    missingstr=''
    

    - name: Deploy to GitHub Pages
    uses: peaceiris/actions-gh-pages@v3
    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
    if: ${{ github.event_name == 'push' }} # && github.ref == 'refs/heads/main' }}
    Copy link

    Copilot AI Apr 22, 2025

    Choose a reason for hiding this comment

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

    [nitpick] Check the trailing comment for an extra closing brace '}}' which might be confusing; either remove or clarify the comment.

    Suggested change
    if: ${{ github.event_name == 'push' }} # && github.ref == 'refs/heads/main' }}
    if: ${{ github.event_name == 'push' }} # Example: Add '&& github.ref == "refs/heads/main"' for main branch only

    Copilot uses AI. Check for mistakes.
    @penify-dev
    Copy link
    Copy Markdown
    Contributor

    penify-dev bot commented Apr 22, 2025

    PR Review 🔍

    ⏱️ Estimated effort to review [1-5]

    5, because the addition of a large file with 2403 lines of code introduces significant complexity. The new functionalities and classes require thorough understanding and testing.

    🧪 Relevant tests

    Yes

    ⚡ Possible issues

    Possible Bug: The large number of new functions and classes may introduce untested edge cases that could lead to runtime errors.

    Code Quality: The code lacks comments and documentation, making it difficult to understand the purpose and usage of many functions.

    🔒 Security concerns

    No

    Copy link
    Copy Markdown
    Contributor

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Hey @willsheffler - I've reviewed your changes - here's some feedback:

    Overall Comments:

    • This looks like a large change - consider breaking it up into smaller, more manageable pull requests.
    • It looks like a lot of code has been commented out - consider removing the dead code.
    Here's what I looked at during the review
    • 🟡 General issues: 3 issues found
    • 🟢 Security: all looks good
    • 🟢 Testing: all looks good
    • 🟡 Complexity: 1 issue found
    • 🟢 Documentation: all looks good

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    self._atombvh = hg.SphereBVH_double(self.atoms.coord) # type: ignore
    self._resbvh = hg.SphereBVH_double(self.rescen) # type: ignore
    self.asu = self
    if not self.tokens: self.tokens = self.atoms.res_id
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    suggestion (bug_risk): Clarify tokens initialization condition.

    Using 'if not self.tokens' may mistakenly trigger when tokens is an empty array even if not None. Consider explicitly checking for None to avoid unintended reinitialization when a valid (even if empty) tokens array exists.

    Suggested implementation:

            if self.tokens is None:
                self.tokens = self.atoms.res_id

    Ensure that self.tokens is initialized to None (or not set) before post_init is called, so that the new condition works as expected.

    :alt: partialsum2d illustration
    :alt: partialsum2d illustration

    Illustration of data 2D with pink region to be "summed" and 2D cumulative sum array from which four points are needed to computs the "sum:" ``sum = CSUM[ub1,ub2] (red point) + CSUM[lb1,lb2] (green point) - CUSM[ub1,lb2] (blue point) - CSUM[lb1,lb2] (blue point``.
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    issue (typo): Typo: "computs" should be "compute".

    Suggested change
    Illustration of data 2D with pink region to be "summed" and 2D cumulative sum array from which four points are needed to computs the "sum:" ``sum = CSUM[ub1,ub2] (red point) + CSUM[lb1,lb2] (green point) - CUSM[ub1,lb2] (blue point) - CSUM[lb1,lb2] (blue point``.
    Illustration of data 2D with pink region to be "summed" and 2D cumulative sum array from which four points are needed to compute the "sum:" ``sum = CSUM[ub1,ub2] (red point) + CSUM[lb1,lb2] (green point) - CUSM[ub1,lb2] (blue point) - CSUM[lb1,lb2] (blue point``.

    ContactMatrixStack uses a precomputed 2D partialsum array for efficient region-based queries. To explain
    ContactBlockMatrix uses a precomputed 2D partialsum array for efficient region-based queries. To explain
    start with the 1D partialsum case. ``DATA`` is a 1D array and ``SUMS`` is the cumulative sum
    of ``DATA`` (``ipd.partialsum(DATA)``. If we want the sum of ``DATA[i:j]`` we can compute it as
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    issue (typo): Inconsistent backticks around ipd.partialsum(DATA). The second backtick should be single.

    Suggested change
    of ``DATA`` (``ipd.partialsum(DATA)``. If we want the sum of ``DATA[i:j]`` we can compute it as
    of ``DATA`` (``ipd.partialsum(DATA)`. If we want the sum of ``DATA[i:j]`` we can compute it as

    if cls.adapts is not None: # type: ignore

    @_sym_adapt.register(cls.adapts) # type: ignore
    if not hasattr(cls, '__adapts__'):
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    issue (complexity): Consider extracting the registration logic into a helper function to simplify subclass definitions and centralize dispatch behavior.

    Consider extracting the repeated registration logic from `__init_subclass__` into a small helper so that each subclass only has to specify its adapted types. This reduces inline complexity and centralizes the dispatch‐registration behavior. For example:
    
    ```python
    def register_adapted_types(cls):
        types_to_register = cls.__adapts__ if isinstance(cls.__adapts__, AdaptTypes) else AdaptTypes((cls.__adapts__,))
        for adapted_type in types_to_register:
            @_sym_adapt.register(adapted_type)
            def adapter(thing, sym, isasym=None, cls=cls):
                return cls(thing, sym, isasym)
    register_adapted_types(cls)

    Then, in SymAdapt.__init_subclass__, replace the inline registration with a call to this helper. This keeps the subclass definition cleaner while preserving all functionality.

    Comment on lines +94 to +97
    if 'doctest' in sys.modules:
    if in_doctest():
    return FalseModule(self._lazymodule_name if isinstance(self._lazymodule_name, str
    ) else self._lazymodule_name[0])
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

    Suggested change
    if 'doctest' in sys.modules:
    if in_doctest():
    return FalseModule(self._lazymodule_name if isinstance(self._lazymodule_name, str
    ) else self._lazymodule_name[0])
    if 'doctest' in sys.modules and in_doctest():
    return FalseModule(self._lazymodule_name if isinstance(self._lazymodule_name, str
    ) else self._lazymodule_name[0])


    ExplanationToo much nesting can make code difficult to understand, and this is especially
    true in Python, where there are no brackets to help out with the delineation of
    different nesting levels.

    Reading deeply nested code is confusing, since you have to keep track of which
    conditions relate to which levels. We therefore strive to reduce nesting where
    possible, and the situation where two if conditions can be combined using
    and is an easy win.

    Comment on lines +319 to 320
    contactmat = contactlist.contact_blocks(self.asu.atoms.res_id)
    return contactmat
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

    Suggested change
    contactmat = contactlist.contact_blocks(self.asu.atoms.res_id)
    return contactmat
    return contactlist.contact_blocks(self.asu.atoms.res_id)

    Comment on lines +332 to 334
    result = (self.partialsum[:, fsz::s, fsz::s] - self.partialsum[:, fsz::s, :-fsz:s] -
    self.partialsum[:, :-fsz:s, fsz::s] + self.partialsum[:, :-fsz:s, :-fsz:s])
    return result
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

    Suggested change
    result = (self.partialsum[:, fsz::s, fsz::s] - self.partialsum[:, fsz::s, :-fsz:s] -
    self.partialsum[:, :-fsz:s, fsz::s] + self.partialsum[:, :-fsz:s, :-fsz:s])
    return result
    return (
    self.partialsum[:, fsz::s, fsz::s]
    - self.partialsum[:, fsz::s, :-fsz:s]
    - self.partialsum[:, :-fsz:s, fsz::s]
    + self.partialsum[:, :-fsz:s, :-fsz:s]
    )

    Comment on lines +77 to +82
    print('dumping to:', output)
    output, ext = output.rsplit('.', 1)
    ipd.atom.dump(joint, f'{output}_components.{ext}')
    asu = ipd.atom.Body(joint[joint.chain_id == 'A'])
    sym = ipd.atom.SymBody(asu, ipd.sym.frames('I'))
    sym.dump(f'{output}_icos.{ext}')
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    issue (code-quality): Extract code out into function (extract-method)

    @@ -119,7 +119,7 @@ def helper_test_qcpscan_perf(nscan, nres, natom, cyclic, nsamp):
    if nscan**len(lbub) < 1e8:
    with ipd.dev.Timer(verbose=False) as t:
    for isamp in range(nsamp):
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    issue (code-quality): Replace unused for index with underscore [×2] (for-index-underscore)

    vox = ipd.cuda.voxel.Voxel(xyz)

    with ipd.dev.Timer():
    for i in range(10):
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

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

    issue (code-quality): Replace unused for index with underscore [×2] (for-index-underscore)

    @penify-dev
    Copy link
    Copy Markdown
    Contributor

    penify-dev bot commented Apr 22, 2025

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Enhancement
    Replace the assertion with the actual implementation for converting indices

    The to_original_index method currently has an assertion that will always fail; it should
    be implemented to convert indices correctly instead of asserting false.

    ipd/homog/contact_matrix.py [254]

    -assert 0
    +# Implement the logic to convert the index to the original index
     
    Suggestion importance[1-10]: 9

    Why: This suggestion addresses a critical issue where the method will always fail due to the assertion, and replacing it with an actual implementation is necessary for the method to function as intended.

    9
    Improve the error message for unsupported types in the adaptation function

    The NotImplementedError raised in the _sym_adapt function should provide more context
    about the type of thing that is not supported, possibly by including the type name
    directly in the message.

    ipd/sym/sym_adapt.py [29]

    -raise NotImplementedError(f"Don't know how to make SymAdapt for {type(thing)} ({os.getpid()})")
    +raise NotImplementedError(f"Don't know how to make SymAdapt for {type(thing).__name__} ({os.getpid()})")
     
    Suggestion importance[1-10]: 8

    Why: This suggestion improves the clarity of error messages, which is important for debugging and understanding the code's behavior.

    8
    Possible bug
    Add bounds checking for bodyid and frameid in the body method

    Validate that the bodyid and frameid parameters in the body method do not exceed the
    bounds of the bodies and frames lists to prevent index errors.

    ipd/atom/assembly.py [36-40]

    +if bodyid < 0 or bodyid >= len(self.bodies) or frameid < 0 or frameid >= len(self.frames[bodyid]):
    +    raise IndexError("bodyid or frameid out of range")
     body = self.bodies[bodyid]
     
    Suggestion importance[1-10]: 9

    Why: This suggestion addresses a critical issue that could lead to index errors when accessing lists. Adding bounds checking is essential for robustness and prevents runtime exceptions, making it a significant improvement.

    9
    Validate the slicing in the calculation to prevent potential index errors

    The calculation of result in the fragment_contact method should ensure that the slices are
    valid and handle cases where fsz or s could lead to out-of-bounds indexing.

    ipd/homog/contact_matrix.py [332-333]

    -result = (self.partialsum[:, fsz::s, fsz::s] - self.partialsum[:, fsz::s, :-fsz:s] -
    +# Ensure that slicing does not lead to out-of-bounds errors
     
    Suggestion importance[1-10]: 7

    Why: This suggestion highlights a potential bug related to index slicing, which could lead to runtime errors. Validating the slices is important for maintaining the integrity of the calculations.

    7
    Bug
    Correct the reference to the class name in the error message within __init_subclass__

    The init_subclass method should use cls.name instead of name to reference the
    class name in the error message, as name is not defined in this context.

    ipd/sym/sym_adapt.py [76]

    -raise TypeError(f'class {name} must define adapted type via __adapts__ = ThingType')  # type: ignore
    +raise TypeError(f'class {cls.__name__} must define adapted type via __adapts__ = ThingType')  # type: ignore
     
    Suggestion importance[1-10]: 9

    Why: This correction addresses a potential bug in the code, ensuring that the error message references the correct class name, which is crucial for accurate debugging.

    9
    Possible issue
    Enhance the assertion to validate both non-emptiness and dimensionality of the contacts array

    The assertion for the contacts attribute should check if it is not only non-empty but also
    has the correct dimensions, ensuring that it is a 3D array.

    ipd/homog/contact_matrix.py [230]

    -assert len(self.contacts), 'contacts must not be empty'
    +assert len(self.contacts) > 0 and len(self.contacts.shape) == 3, 'contacts must be a non-empty 3D array'
     
    Suggestion importance[1-10]: 8

    Why: This suggestion improves the robustness of the code by ensuring that the contacts attribute is not only non-empty but also has the correct dimensionality, which is crucial for the functionality of the class.

    8
    Initialize the tokens attribute based on the atoms attribute

    Ensure that the tokens attribute is initialized properly in the Body class to avoid
    potential issues with uninitialized attributes.

    ipd/atom/body.py [125]

    -self.tokens = ipd.npNone()
    +self.tokens = self.atoms.res_id if self.atoms is not None else ipd.npNone()
     
    Suggestion importance[1-10]: 7

    Why: The suggestion improves the initialization of the tokens attribute, which is important for avoiding potential runtime errors if atoms is not set. However, it could be considered a minor improvement rather than a critical fix.

    7
    Add a check to handle empty input for the to_bodies function

    Ensure that the to_bodies function handles the case where atoms_or_bodies is empty to
    avoid returning an empty list without processing.

    ipd/atom/assembly.py [76-79]

    -bodies = map(ipd.kwcurry(kw, Body), atoms_or_bodies)
    +bodies = map(ipd.kwcurry(kw, Body), atoms_or_bodies) if atoms_or_bodies else []
     
    Suggestion importance[1-10]: 7

    Why: The suggestion correctly identifies a potential issue with handling empty input in the to_bodies function, which could lead to unexpected behavior. However, the existing code does not explicitly handle the case of empty input, making this a valid improvement.

    7
    Add a validation step to ensure the generated contact matrices are symmetric

    The rand_contacts function should include a check to ensure that the generated matrices
    are symmetric, as the function's purpose is to create symmetric contact matrices.

    ipd/homog/contact_matrix.py [445]

    -# AI slop, very slow. Create a stack of m random symmetric contact matrices of size n x n.
    +# Ensure generated matrices are symmetric
     
    Suggestion importance[1-10]: 6

    Why: This suggestion improves the function's reliability by ensuring that the generated matrices meet the expected properties, although it is a minor enhancement compared to the other suggestions.

    6
    Add validation for the contacts variable in the NeighborhoodSelector class

    Ensure that the contacts variable in the call method of NeighborhoodSelector is
    validated before being used to prevent potential errors.

    ipd/atom/assembly.py [108]

     contacts = asu.contacts(body, **contactkw)
    +if contacts is None:
    +    continue  # or handle the case appropriately
     
    Suggestion importance[1-10]: 6

    Why: The suggestion points out a potential issue with the contacts variable, which could lead to errors if not validated. While it is a valid improvement, the impact is less severe compared to other suggestions, hence the lower score.

    6
    Maintainability
    Validate the type of the other parameter in clash detection methods

    In the hasclash and nclash methods, ensure that the other parameter is validated to
    prevent potential runtime errors when it is not of the expected type.

    ipd/atom/body.py [168-170]

    +if not isinstance(other, (Body, SymBody)):
    +    raise ValueError("Invalid type for 'other'; expected Body or SymBody.")
     result = _bvh_binary_operation('bvh_isect', self, other, radius=radius, **kw)
     
    Suggestion importance[1-10]: 8

    Why: This suggestion addresses a potential issue where the other parameter could lead to runtime errors if not validated. Ensuring type safety in method parameters is crucial for maintainability.

    8
    Validate the types of tokens1 and tokens2 in the contact_blocks method

    In the contact_blocks method, ensure that the tokens1 and tokens2 parameters are validated
    to prevent potential runtime errors.

    ipd/atom/body.py [474-476]

     if tokens1 is None: tokens1 = self.symbody.asu.atoms.res_id
    +if tokens2 is None: tokens2 = self.body2.asu.atoms.res_id
    +if not isinstance(tokens1, np.ndarray) or not isinstance(tokens2, np.ndarray):
    +    raise ValueError("tokens1 and tokens2 must be numpy arrays.")
     
    Suggestion importance[1-10]: 8

    Why: This suggestion enhances the robustness of the contact_blocks method by validating the types of its parameters, which is important for preventing runtime errors and ensuring the method behaves as expected.

    8
    Add type validation to the AdaptTypes class to ensure only valid types are accepted

    The AdaptTypes class should ensure that it only accepts types that are valid for
    adaptation; consider adding a validation method to enforce this.

    ipd/sym/sym_adapt.py [53-54]

     class AdaptTypes(tuple):
    -    pass
    +    def __new__(cls, *args):
    +        for arg in args:
    +            if not isinstance(arg, type):
    +                raise TypeError(f"{arg} is not a valid type")
    +        return super().__new__(cls, args)
     
    Suggestion importance[1-10]: 7

    Why: Adding type validation enhances the robustness of the class, ensuring that only valid types are accepted, which is beneficial for maintainability.

    7
    Handle the exclude parameter more robustly in the contacts method

    In the contacts method, ensure that the exclude parameter is properly handled to avoid
    issues when it is not iterable.

    ipd/atom/body.py [303-305]

    -if not isinstance(exclude, ipd.Iterable): exclude = [ipd.cast(int, exclude)]
    +if exclude is None: exclude = []
    +elif not isinstance(exclude, ipd.Iterable): exclude = [ipd.cast(int, exclude)]
     
    Suggestion importance[1-10]: 6

    Why: The suggestion improves the handling of the exclude parameter, making the code more robust. However, the original implementation already had some handling, making this a moderate improvement.

    6
    Clearly mark the deprecated_SymAdaptTensor class as deprecated to indicate its intended replacement

    The deprecated_SymAdaptTensor class should be marked as deprecated using a decorator or a
    comment to indicate its status clearly, as it is intended to be replaced.

    ipd/sym/sym_adapt.py [431]

    -class deprecated_SymAdaptTensor(SymAdapt):
    +class deprecated_SymAdaptTensor(SymAdapt):  # Deprecated: to be replaced with NamedTensor variant
     
    Suggestion importance[1-10]: 6

    Why: Marking deprecated classes is a good practice for maintainability, but this suggestion does not address a critical issue, hence the moderate score.

    6
    Rename newasuframe to a more descriptive name for clarity

    Consider using a more descriptive name for the newasuframe variable to improve code
    readability and maintainability.

    ipd/atom/assembly.py [117]

    -newasuframe = nbrframe[body][0]
    +transformed_frame = nbrframe[body][0]
     
    Suggestion importance[1-10]: 5

    Why: While renaming variables for clarity is a good practice, this suggestion addresses a minor readability issue rather than a functional problem. Therefore, it receives a lower score as it does not significantly impact the code's correctness or performance.

    5

    - Replace direct evn.chrono calls in ipd/__init__.py with the new chronometer API:
      set `chronometer.name`, emit a profile report on exit, and annotate import/checkpoint scopes
    - Consolidate imports in ipd/__init__.py (alias `typing` as `t`, import evn for side effects, remove unused `contextlib`)
    - Remove the legacy `ipd._prelude.lazy_import` module in favor of `evn._prelude.lazy_import`
    - Update ipd/_prelude/structs and typehints:
      - Add `import typing as t` and `import dataclasses`
      - Define `npNone()` and `framesNone()` factory fields
      - Simplify array‐type classes by inheriting from a common `FalseNDarray`
    - Fix `SymBody.__getitem__` to handle single-index slicing correctly
    - Enhance `ipd.dev.storage.package_storage.is_pickle_fname` to recognize `.pkl[.gz|.xz]` and `.pickle[.gz|.xz]`
    - Instrument `ipd.sym` classes with `@evn.chrono`, add `__bool__` to `SymIndex`, and introduce `null_sym_index`/`null_sym_kind` defaults
    - Add `NULL` members to `ShapeKind` and `ValueKind` enums
    - Relax numerical tolerance in `test_thgeom` from `1e-4` to `1e-3`
    - Remove obsolete `test_lazy_import.py`
    - Update CI tooling in `citool.py` and its tests to prepend `PYTHONPATH=.:$PYTHONPATH`
    - Bump project version to `0.4.8` and require `evn>=0.7.8`
    
    BREAKING CHANGE: the standalone `ipd._prelude.lazy_import` module has been removed; ensure all lazy imports use `evn.lazyimport`
    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.

    2 participants