Skip to content

Conversation

@iProzd
Copy link
Collaborator

@iProzd iProzd commented May 20, 2025

Summary by CodeRabbit

  • New Features

    • Introduced dynamic neighbor selection for the DPA3 descriptor, enabling handling of variable neighbor counts without fixed padding.
    • Added configuration options to enable dynamic selection (use_dynamic_sel) and adjust normalization scaling (sel_reduce_factor).
    • Added new utility functions for aggregation and graph index computation to support dynamic neighbor processing.
  • Bug Fixes

    • Corrected tensor split order in angle update logic for improved accuracy.
  • Documentation

    • Enhanced argument documentation for new dynamic selection options.
  • Tests

    • Added comprehensive tests for dynamic selection mode, verifying consistency across configurations.
    • Updated existing test parameterizations to include dynamic selection options.
    • Integrated new descriptor and parameters into model test suites.
  • Chores

    • Extended public APIs to include new utility functions supporting dynamic neighbor selection.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 20, 2025

📝 Walkthrough
## Walkthrough

This change introduces a "dynamic neighbor selection" feature to the DPA3 descriptor and associated repflow layers in both NumPy and PyTorch backends. It adds new configuration parameters (`use_dynamic_sel`, `sel_reduce_factor`), adapts core logic to support dynamic (unpadded) neighbor and angle selection, and implements new utility functions for graph indexing and aggregation. Corresponding tests and argument schemas are updated.

## Changes

| File(s)                                                                                     | Change Summary |
|---------------------------------------------------------------------------------------------|----------------|
| `deepmd/dpmodel/descriptor/dpa3.py`, `deepmd/pt/model/descriptor/dpa3.py`, `deepmd/pd/model/descriptor/dpa3.py` | Added `use_dynamic_sel` and `sel_reduce_factor` to `RepFlowArgs` and passed them to `DescrptBlockRepflows` in DPA3 descriptor classes. Serialization/deserialization and constructor logic updated accordingly. |
| `deepmd/dpmodel/descriptor/repflows.py`, `deepmd/pt/model/descriptor/repflows.py`, `deepmd/pd/model/descriptor/repflows.py` | Added dynamic selection support to `DescrptBlockRepflows` and `RepFlowLayer`. New parameters control neighbor selection and normalization. New dynamic methods and logic for flattened neighbor/angle handling, aggregation, and index management. In PD backend, dynamic selection is disallowed with an explicit error. |
| `deepmd/pt/model/descriptor/repflow_layer.py`, `deepmd/pd/model/descriptor/repflow_layer.py` | Added dynamic selection logic to `RepFlowLayer`, including new dynamic methods for rotation, symmetrization, and optimized updates. Modified `forward` (PyTorch) and serialization logic to support new parameters and dynamic paths. PD backend updated for serialization versioning and parameter storage. |
| `deepmd/dpmodel/utils/network.py`, `deepmd/pt/model/network/utils.py`                       | Added utility functions `aggregate` and `get_graph_index` for grouping/aggregation and index mapping in graph-based message passing. |
| `deepmd/dpmodel/utils/__init__.py`                                                          | Exported `aggregate` and `get_graph_index` from the utils module. |
| `deepmd/utils/argcheck.py`                                                                  | Added `use_dynamic_sel` and `sel_reduce_factor` as optional arguments in the DPA3 repflow argument schema with documentation. |
| `source/tests/consistent/descriptor/test_dpa3.py`, `source/tests/universal/dpmodel/descriptor/test_descriptor.py` | Added `use_dynamic_sel` parameter to test parameterizations and logic. Adjusted test skipping and tolerance logic. |
| `source/tests/universal/dpmodel/model/test_model.py`                                        | Integrated DPA3 descriptor and parameter classes into energy and spin model parameterized test sets. |
| `source/tests/pt/model/test_dynamic_sel.py`                                                 | Added new test class to verify consistency between static and dynamic neighbor selection in the DPA3 descriptor under various configurations. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant DPA3Descriptor
    participant RepFlowArgs
    participant RepFlowBlock
    participant RepFlowLayer
    participant Utils

    User->>DPA3Descriptor: Initialize (with use_dynamic_sel, sel_reduce_factor)
    DPA3Descriptor->>RepFlowArgs: Pass parameters
    DPA3Descriptor->>RepFlowBlock: Pass use_dynamic_sel, sel_reduce_factor
    RepFlowBlock->>RepFlowLayer: Pass use_dynamic_sel, sel_reduce_factor

    User->>DPA3Descriptor: Compute descriptors
    DPA3Descriptor->>RepFlowBlock: Forward (inputs)
    RepFlowBlock->>Utils: get_graph_index (if dynamic)
    RepFlowBlock->>RepFlowLayer: Forward (inputs, edge_index, angle_index)
    RepFlowLayer->>Utils: aggregate (if dynamic)
    RepFlowLayer->>RepFlowBlock: Return embeddings
    RepFlowBlock->>DPA3Descriptor: Return descriptors

Possibly related PRs

Suggested reviewers

  • wanghan-iapcm
  • njzjz

</details>

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 83ff09e679e1274e035e265ffdae7b06ed855b89 and a55c6e3d0da7f1b9199c99375ce29aa879d1b263.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `deepmd/dpmodel/utils/network.py` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* deepmd/dpmodel/utils/network.py

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (29)</summary>

* GitHub Check: Test Python (6, 3.12)
* GitHub Check: Test Python (6, 3.9)
* GitHub Check: Test Python (5, 3.12)
* GitHub Check: Test Python (5, 3.9)
* GitHub Check: Test Python (4, 3.12)
* GitHub Check: Build wheels for cp310-manylinux_aarch64
* GitHub Check: Test Python (4, 3.9)
* GitHub Check: Build wheels for cp311-win_amd64
* GitHub Check: Test Python (3, 3.12)
* GitHub Check: Build wheels for cp311-macosx_arm64
* GitHub Check: Test Python (3, 3.9)
* GitHub Check: Build C++ (clang, clang)
* GitHub Check: Test Python (2, 3.12)
* GitHub Check: Build C++ (rocm, rocm)
* GitHub Check: Build wheels for cp311-macosx_x86_64
* GitHub Check: Test Python (2, 3.9)
* GitHub Check: Build C++ (cuda120, cuda)
* GitHub Check: Build wheels for cp311-manylinux_x86_64
* GitHub Check: Analyze (python)
* GitHub Check: Build C library (2.14, >=2.5.0rc0,<2.15, libdeepmd_c_cu11.tar.gz)
* GitHub Check: Build wheels for cp311-manylinux_x86_64
* GitHub Check: Test Python (1, 3.12)
* GitHub Check: Test C++ (false)
* GitHub Check: Build C++ (cuda, cuda)
* GitHub Check: Build C library (2.18, libdeepmd_c.tar.gz)
* GitHub Check: Test C++ (true)
* GitHub Check: Test Python (1, 3.9)
* GitHub Check: Analyze (c-cpp)
* GitHub Check: Build C++ (cpu, cpu)

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=deepmodeling/deepmd-kit&utm_content=4754):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🔭 Outside diff range comments (3)
deepmd/dpmodel/descriptor/repflows.py (2)

189-224: 🛠️ Refactor suggestion

Add parameter validation

Same as in the PyTorch backend: guard against invalid sel_reduce_factor and incompatible flag combinations right inside __init__.


786-838: 🛠️ Refactor suggestion

Sanity-check dynamically-derived neighbour counts

self.dynamic_e_sel = self.nnei / self.sel_reduce_factor
self.dynamic_a_sel = self.a_sel / self.sel_reduce_factor

If self.nnei or a_sel is not divisible by sel_reduce_factor, a silent float is produced but later used in integer contexts (**-0.5, divisions). Round or cast explicitly and document the intended behaviour.

deepmd/pt/model/descriptor/repflow_layer.py (1)

710-775: ⚠️ Potential issue

Make edge_index & angle_index truly optional to preserve backward compatibility

The docstring marks both tensors as optional, yet the function signature makes them mandatory.
All pre-existing call sites will now break unless they are patched simultaneously.

-        edge_index: torch.Tensor,  # n_edge x 2
-        angle_index: torch.Tensor,  # n_angle x 3
+        edge_index: Optional[torch.Tensor] = None,  # n_edge x 2
+        angle_index: Optional[torch.Tensor] = None,  # n_angle x 3
 )

Immediately after the argument block:

-        n_edge = int(nlist_mask.sum().item())
+        if self.use_dynamic_sel:
+            assert edge_index is not None and angle_index is not None, (
+                "edge_index and angle_index must be provided when "
+                "use_dynamic_sel=True"
+            )
+        n_edge = int(nlist_mask.sum().item())

This small change keeps the new functionality while preventing a breaking API change.

🧰 Tools
🪛 Ruff (0.11.9)

759-759: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

🧹 Nitpick comments (19)
deepmd/pt/model/descriptor/dpa3.py (1)

153-154: New dynamic selection parameters properly propagated to DescrptBlockRepflows

The dynamic neighbor selection feature is correctly implemented by passing the parameters from the repflow_args to the constructor.

However, I recommend adding documentation for these parameters in the class docstring to help users understand their purpose and usage.

@@ -86,6 +86,10 @@
     use_tebd_bias : bool, Optional
         Whether to use bias in the type embedding layer.
     type_map : list[str], Optional
+        A list of strings. Give the name to each type of atoms.
+    use_dynamic_sel : bool, Optional
+        Whether to use dynamic selection of neighbors.
+    sel_reduce_factor : float, Optional
         A list of strings. Give the name to each type of atoms.
deepmd/dpmodel/descriptor/dpa3.py (2)

126-137: Doc-string section needs a quick pass for coherence

The two new parameters are correctly documented, but they break the visual grouping of the previous bullet list – the “smooth_edge_update” paragraph is now split in half. A quick tidy-up (move the block to the end or re-indent) will keep the generated docs readable.


164-166: Add basic validation for sel_reduce_factor

sel_reduce_factor is silently stored even when use_dynamic_sel=False, and no guard prevents zero / negative values.
Consider enforcing a positive value only when the flag is enabled; in the off case a neutral 1.0 keeps later maths sane.

+        if self.use_dynamic_sel:
+            if sel_reduce_factor <= 0:
+                raise ValueError("`sel_reduce_factor` must be positive.")
+            self.sel_reduce_factor = sel_reduce_factor
+        else:
+            self.sel_reduce_factor = 1.0

Also applies to: 192-194

deepmd/pt/model/network/utils.py (1)

33-45: Potential divide-by-zero / wrong normalisation when num_owner > actual owners

bin_count.where(bin_count != 0, 1) forces empty owner bins to 1, so averaging leaves zeros unchanged – good.
However, when num_owner > max(owners)+1, you append ones, meaning you will divide by 1 instead of 0 for those entirely-missing owners.
If a caller relies on zeros for missing owners, the result is silently wrong.

Consider appending zeros and masking the division instead:

-        bin_count = torch.cat([bin_count, bin_count.new_ones(difference)])
+        bin_count = torch.cat([bin_count, bin_count.new_zeros(difference)])-    if average:
-        output = (output.T / bin_count).T
+    if average:
+        # avoid NaN when bin_count == 0
+        safe_bc = torch.where(bin_count == 0, torch.ones_like(bin_count), bin_count)
+        output = output / safe_bc.unsqueeze(-1)
source/tests/pt/model/test_dynamic_sel.py (1)

31-33: Unused module-level dtype variable – can be removed

dtype = env.GLOBAL_PT_FLOAT_PRECISION is never referenced after definition because the loop shadows it with another dtype.
Delete to avoid confusion.

source/tests/consistent/descriptor/test_dpa3.py (1)

66-69: Parameterisation: single-value tuples could be plain literals

Minor: using (True, ) instead of True inside @parameterized expands the test matrix unnecessarily (extra unit dimension).
It’s harmless but removing the tuple keeps the cartesian product size minimal.

-    (True, ),  # a_compress_use_split
+    (True,),   # or just True if no cartesian product intended
deepmd/pt/model/descriptor/repflows.py (5)

200-236: Move validation logic closer to parameters

use_dynamic_sel, sel_reduce_factor and the newly-derived dynamic_*_sel are set, but no check protects users from passing nonsense values (e.g., negative factors or 0). A short input-sanity block would prevent NaNs later on.

+        if sel_reduce_factor <= 0:
+            raise ValueError("`sel_reduce_factor` must be positive.")

445-448: Potentially clobbering a legitimate neighbour indexed “0”

Padding indices -1 are rewritten to 0.
If atom-id 0 really occurs as a neighbour, padded slots become indistinguishable from the genuine neighbour in tensors passed to downstream ops (even though the boolean mask still helps).
Consider replacing -1 with nall (an out-of-range sentinel) or keeping -1 and masking inside get_graph_index.


476-495: Minor optimisation: avoid reallocating large boolean tensors

Inside the dynamic branch we build a four-dimensional mask
a_nlist_mask = ... & ... only to immediately use it once for indexing and then discard it.
Given typical neighbour counts, this tensor can be tens of MiBs.
Instead compute the 1-D boolean index via torch.nonzero or boolean-mask directly against angle_input to save memory.


495-498: Prefer None over dummy tensors for unused indices

When dynamic selection is disabled you fabricate a dummy edge_index of shape (1,3).
Passing None conveys intent more clearly and removes two unnecessary gather operations executed in RepFlowLayer.call, with zero functional cost.


1267-1270: Remove unused local variable nall

nall is assigned but never referenced afterwards, triggering lint (ruff F841) and slightly confusing readers.

-        nall = node_ebd_ext.shape[1]
deepmd/dpmodel/descriptor/repflows.py (4)

130-140: Replicate doc-string clarification from PyTorch backend

For consistency, also mention that use_dynamic_sel=True presently requires smooth_edge_update=True, otherwise a NotImplementedError will be raised later in the layer code.


475-478: Possible collision of sentinel value

Replacing -1 with 0 may collide with real atom id 0. See analogous remark for the PyTorch file.


507-527: Memory footprint of temporary masks

Building a_nlist_mask[:, :, :, None] & a_nlist_mask[:, :, None, :] doubles memory for large systems. Consider boolean-masking the angle tensors in-place instead.


1267-1270: Unused variable nall — linter warning

Same as in the PyTorch counterpart, delete the assignment.

-        nall = node_ebd_ext.shape[1]
🧰 Tools
🪛 Ruff (0.11.9)

1268-1268: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

deepmd/pt/model/descriptor/repflow_layer.py (4)

330-383: Use einsum to avoid a large temporary tensor in _cal_hg_dynamic

flat_h2[..., None] * flat_edge_ebd[:, None, :] materialises an (n_edge, 3, e_dim) tensor just to reshape it again.
torch.einsum generates the same result while keeping memory overhead lower and improving readability.

-# n_edge x 3 x e_dim
-flat_h2g2 = (flat_h2[..., None] * flat_edge_ebd[:, None, :]).reshape(
-    -1, 3 * e_dim
-)
+# n_edge x 3 x e_dim  ➜  reshaped to (n_edge, 3*e_dim)
+flat_h2g2 = torch.einsum("ni,nj->nij", flat_h2, flat_edge_ebd).reshape(
+    -1, 3 * e_dim
+)

759-760: Remove unused variable nall

nall is assigned but never referenced, triggering static-analysis warnings (ruff F841).
Simply drop the assignment:

-        nall = node_ebd_ext.shape[1]
🧰 Tools
🪛 Ruff (0.11.9)

759-759: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)


893-904: Let aggregate() compute the mean instead of manual division

You already know the effective neighbour count (dynamic_e_sel).
Passing average=True (or implementing mean inside aggregate) avoids an extra division and guards against accidental mismatch between dynamic_e_sel and the real count.

-aggregate(
-    node_edge_update,
-    n2e_index,
-    average=False,
-    num_owner=nb * nloc,
-).reshape(nb, nloc, node_edge_update.shape[-1]) / self.dynamic_e_sel
+aggregate(
+    node_edge_update,
+    n2e_index,
+    average=True,          # mean inside the helper
+    num_owner=nb * nloc,
+).reshape(nb, nloc, node_edge_update.shape[-1])

347-355: Spelling nit: “Flatted” → “Flattened”

Minor docstring polish improves readability and future documentation extraction.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8176173 and 3ccb3a6.

📒 Files selected for processing (13)
  • deepmd/dpmodel/descriptor/dpa3.py (5 hunks)
  • deepmd/dpmodel/descriptor/repflows.py (27 hunks)
  • deepmd/dpmodel/utils/__init__.py (2 hunks)
  • deepmd/dpmodel/utils/network.py (1 hunks)
  • deepmd/pt/model/descriptor/dpa3.py (1 hunks)
  • deepmd/pt/model/descriptor/repflow_layer.py (19 hunks)
  • deepmd/pt/model/descriptor/repflows.py (8 hunks)
  • deepmd/pt/model/network/utils.py (1 hunks)
  • deepmd/utils/argcheck.py (2 hunks)
  • source/tests/consistent/descriptor/test_dpa3.py (10 hunks)
  • source/tests/pt/model/test_dynamic_sel.py (1 hunks)
  • source/tests/universal/dpmodel/descriptor/test_descriptor.py (3 hunks)
  • source/tests/universal/dpmodel/model/test_model.py (6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
deepmd/dpmodel/utils/__init__.py (2)
deepmd/dpmodel/utils/network.py (2)
  • aggregate (962-1000)
  • get_graph_index (1003-1091)
deepmd/pt/model/network/utils.py (2)
  • aggregate (10-45)
  • get_graph_index (49-135)
source/tests/universal/dpmodel/model/test_model.py (3)
deepmd/dpmodel/descriptor/dpa3.py (1)
  • DescrptDPA3 (235-659)
deepmd/pt/model/descriptor/dpa3.py (1)
  • DescrptDPA3 (65-567)
source/tests/universal/dpmodel/descriptor/test_descriptor.py (1)
  • DescriptorParamDPA3 (467-530)
deepmd/pt/model/network/utils.py (1)
deepmd/dpmodel/utils/network.py (2)
  • aggregate (962-1000)
  • get_graph_index (1003-1091)
deepmd/pt/model/descriptor/repflows.py (2)
deepmd/pt/model/network/utils.py (1)
  • get_graph_index (49-135)
deepmd/pt/model/descriptor/repflow_layer.py (3)
  • RepFlowLayer (38-1310)
  • _cal_hg (288-328)
  • _cal_hg_dynamic (331-383)
🪛 Ruff (0.11.9)
deepmd/dpmodel/descriptor/repflows.py

1268-1268: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

deepmd/pt/model/descriptor/repflow_layer.py

759-759: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

⏰ Context from checks skipped due to timeout of 90000ms (30)
  • GitHub Check: Test Python (6, 3.12)
  • GitHub Check: Test Python (6, 3.9)
  • GitHub Check: Test Python (5, 3.12)
  • GitHub Check: Test Python (5, 3.9)
  • GitHub Check: Test Python (4, 3.12)
  • GitHub Check: Build wheels for cp310-manylinux_aarch64
  • GitHub Check: Test Python (4, 3.9)
  • GitHub Check: Test Python (3, 3.12)
  • GitHub Check: Build wheels for cp311-win_amd64
  • GitHub Check: Build C++ (clang, clang)
  • GitHub Check: Test Python (3, 3.9)
  • GitHub Check: Build wheels for cp311-macosx_arm64
  • GitHub Check: Build C++ (rocm, rocm)
  • GitHub Check: Test Python (2, 3.12)
  • GitHub Check: Build wheels for cp311-macosx_x86_64
  • GitHub Check: Build C++ (cuda120, cuda)
  • GitHub Check: Test Python (2, 3.9)
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Analyze (python)
  • GitHub Check: Build C++ (cuda, cuda)
  • GitHub Check: Test Python (1, 3.12)
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Build C library (2.14, >=2.5.0rc0,<2.15, libdeepmd_c_cu11.tar.gz)
  • GitHub Check: Test C++ (false)
  • GitHub Check: Build C++ (cpu, cpu)
  • GitHub Check: Test Python (1, 3.9)
  • GitHub Check: Build C library (2.18, libdeepmd_c.tar.gz)
  • GitHub Check: Analyze (c-cpp)
  • GitHub Check: Test C++ (true)
🔇 Additional comments (12)
deepmd/dpmodel/utils/__init__.py (2)

18-19: New utility functions added for dynamic neighbor selection

The addition of aggregate and get_graph_index functions from the network module makes them publicly available through the utils package.


58-59: Properly exposed new functions in all list

Good practice - ensuring the newly imported functions are properly exposed in the __all__ list allows them to be imported directly from the package.

source/tests/universal/dpmodel/descriptor/test_descriptor.py (3)

486-486: Added parameter for dynamic selection

The new parameter use_dynamic_sel has been properly added to the descriptor parameters.


511-511: Parameter correctly passed to RepFlowArgs constructor

The dynamic selection parameter is correctly passed to the underlying RepFlowArgs class.


542-543: Test configuration updated for new parameters

The test configuration has been updated to include the dynamic selection feature, properly testing both enabled and disabled states. The change to a_compress_use_split restricts it to only test the True option.

Can you confirm the reason for removing the False option from a_compress_use_split tests? This change seems to reduce test coverage.

Also applies to: 547-547

deepmd/utils/argcheck.py (1)

1500-1512: Well-designed dynamic selection feature with clear documentation.

The new parameters use_dynamic_sel and sel_reduce_factor are well-documented with clear descriptions of their purpose and effects. The default values are sensible, with dynamic selection disabled by default for backward compatibility. The documentation effectively explains the benefit of dynamic neighbor selection - allowing users to set larger selection values safely while only considering actual neighbors within the cutoff radius.

Also applies to: 1507-1512, 1613-1626

deepmd/dpmodel/utils/network.py (1)

962-1000: xp.bincount / xp.add.at are backend-specific – verify JAX & CuPy compatibility

The helper relies on xp.bincount and xp.add.at, neither of which are part of the 2022-12 Array-API spec.
NumPy works, but JAX, CuPy or a future tensorflow backend may not expose these names through array_api_compat, causing a hard crash in dynamic-selection mode.

Please double-check supported back-ends and, if needed, fall back to a pure-xp scatter-add implementation (e.g. xp.zeros + manual indexing) or conditional import.

source/tests/universal/dpmodel/model/test_model.py (1)

7-13: Tests updated correctly

The descriptor & parameter lists now cover DescrptDPA3; parametrisation and imports look consistent with the existing pattern.

Also applies to: 44-46, 98-100, 114-115, 187-188, 203-204

deepmd/pt/model/descriptor/repflows.py (3)

590-603: Check scale factor when sel_reduce_factor ≠ neighbor count

The normalisation uses
scale_factor = (self.nnei / self.sel_reduce_factor)**(-0.5).

If sel_reduce_factor is merely a heuristic and the actual neighbour count differs frame-by-frame, the rotation matrix may be mis-scaled.
Consider computing the divisor from the per-owner edge count (available via aggregate(..., average=True)), or at least add a doc-string note clarifying the approximation.


1600-1605: NotImplementedError wording & early-exit

This runtime check is great, but you can move it to the constructor (see first comment) and turn it into a ValueError. Doing so fails fast during graph construction instead of midway through a forward pass.


1592-1597: Normalisation uses fixed dynamic_a_sel

aggregate(...)/(self.dynamic_a_sel**0.5) assumes every edge participates in exactly dynamic_a_sel angles, which is only an approximation.
If the neighbour distribution is broad (e.g., surface atoms), this may bias updates. A per-edge reduction factor (angle_count_per_edge**-0.5) would be more accurate.

deepmd/dpmodel/descriptor/repflows.py (1)

1590-1605: Early validation for smooth_edge_update with dynamic selection

Run-time NotImplementedError can be promoted to an early constructor-level check.

a_compress_e_rate,
a_compress_use_split,
optim_update,
use_dynamic_sel,

Check notice

Code scanning / CodeQL

Unused local variable Note test

Variable use_dynamic_sel is not used.
a_compress_e_rate,
a_compress_use_split,
optim_update,
use_dynamic_sel,

Check notice

Code scanning / CodeQL

Unused local variable Note test

Variable use_dynamic_sel is not used.
a_compress_e_rate,
a_compress_use_split,
optim_update,
use_dynamic_sel,

Check notice

Code scanning / CodeQL

Unused local variable Note test

Variable use_dynamic_sel is not used.
a_compress_e_rate,
a_compress_use_split,
optim_update,
use_dynamic_sel,

Check notice

Code scanning / CodeQL

Unused local variable Note test

Variable use_dynamic_sel is not used.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
deepmd/pt/model/descriptor/repflow_layer.py (1)

106-109: ⚠️ Potential issue

Add validation for sel_reduce_factor to prevent invalid operations

Your code computes dynamic_e_sel and dynamic_a_sel by dividing by sel_reduce_factor, but there's no validation to ensure this value is positive and non-zero. This could lead to division-by-zero errors or negative values that would cause issues later when used as divisors or in negative exponents.

self.use_dynamic_sel = use_dynamic_sel
self.sel_reduce_factor = sel_reduce_factor
+if self.sel_reduce_factor <= 0:
+    raise ValueError(f"`sel_reduce_factor` must be positive, got {self.sel_reduce_factor}")
self.dynamic_e_sel = self.nnei / self.sel_reduce_factor
self.dynamic_a_sel = self.a_sel / self.sel_reduce_factor
deepmd/dpmodel/descriptor/repflows.py (1)

657-708: 🛠️ Refactor suggestion

Protect against empty edge sets in _cal_hg_dynamic

The _cal_hg_dynamic function doesn't handle the case where n_edge=0, which can occur with extreme exclusions. This could cause the aggregate function to fail with empty input.

def _cal_hg_dynamic(
    flat_edge_ebd: np.ndarray,
    flat_h2: np.ndarray,
    flat_sw: np.ndarray,
    owner: np.ndarray,
    num_owner: int,
    nb: int,
    nloc: int,
    scale_factor: float,
) -> np.ndarray:
    xp = array_api_compat.array_namespace(flat_edge_ebd, flat_h2, flat_sw, owner)
    n_edge, e_dim = flat_edge_ebd.shape
+    
+    # Handle empty edge case
+    if n_edge == 0:
+        return xp.zeros((nb, nloc, 3, e_dim), dtype=flat_edge_ebd.dtype)
🧹 Nitpick comments (3)
deepmd/pt/model/descriptor/repflow_layer.py (2)

746-746: Remove unused variable assignment

The variable nall is assigned but never used in this scope.

-nb, nloc, nnei = nlist.shape
-nall = node_ebd_ext.shape[1]
+nb, nloc, nnei = nlist.shape
🧰 Tools
🪛 Ruff (0.11.9)

746-746: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)


1049-1057: Consider handling empty edge sets in dynamic aggregation

While this code correctly calculates edge angle updates with dynamic selection, it doesn't handle the edge case where there are no angles to process (n_angle=0). This could happen in systems with extreme exclusions.

 # n_angle x e_dim
 weighted_edge_angle_update = edge_angle_update * a_sw.unsqueeze(-1)
 # n_edge x e_dim
+if weighted_edge_angle_update.shape[0] == 0:
+    # Handle empty case
+    padding_edge_angle_update = torch.zeros((n_edge, self.e_dim), 
+                               dtype=edge_ebd.dtype, 
+                               device=edge_ebd.device)
+else:
 padding_edge_angle_update = aggregate(
     weighted_edge_angle_update,
     eij2a_index,
     average=False,
     num_owner=n_edge,
 ) / (self.dynamic_a_sel**0.5)
deepmd/dpmodel/descriptor/repflows.py (1)

1287-1287: Remove unused variable assignment

The variable nall is assigned but never used in this scope.

-nb, nloc, nnei = nlist.shape
-nall = node_ebd_ext.shape[1]
+nb, nloc, nnei = nlist.shape
🧰 Tools
🪛 Ruff (0.11.9)

1287-1287: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ccb3a6 and b67222b.

📒 Files selected for processing (6)
  • deepmd/dpmodel/descriptor/repflows.py (27 hunks)
  • deepmd/dpmodel/utils/__init__.py (2 hunks)
  • deepmd/dpmodel/utils/network.py (1 hunks)
  • deepmd/pt/model/descriptor/repflow_layer.py (19 hunks)
  • source/tests/consistent/descriptor/test_dpa3.py (10 hunks)
  • source/tests/pt/model/test_dynamic_sel.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • deepmd/dpmodel/utils/network.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • deepmd/dpmodel/utils/init.py
  • source/tests/pt/model/test_dynamic_sel.py
🧰 Additional context used
🪛 Ruff (0.11.9)
deepmd/dpmodel/descriptor/repflows.py

1287-1287: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

deepmd/pt/model/descriptor/repflow_layer.py

746-746: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

🪛 GitHub Check: CodeQL
source/tests/consistent/descriptor/test_dpa3.py

[notice] 137-137: Unused local variable
Variable use_dynamic_sel is not used.


[notice] 173-173: Unused local variable
Variable use_dynamic_sel is not used.


[notice] 190-190: Unused local variable
Variable use_dynamic_sel is not used.


[notice] 249-249: Unused local variable
Variable use_dynamic_sel is not used.

⏰ Context from checks skipped due to timeout of 90000ms (25)
  • GitHub Check: Build wheels for cp310-manylinux_aarch64
  • GitHub Check: Build wheels for cp311-win_amd64
  • GitHub Check: Build wheels for cp311-macosx_arm64
  • GitHub Check: Build wheels for cp311-macosx_x86_64
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Test Python (6, 3.12)
  • GitHub Check: Test Python (6, 3.9)
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Test Python (5, 3.12)
  • GitHub Check: Test Python (5, 3.9)
  • GitHub Check: Test Python (4, 3.12)
  • GitHub Check: Test Python (4, 3.9)
  • GitHub Check: Test C++ (false)
  • GitHub Check: Test Python (3, 3.12)
  • GitHub Check: Test Python (3, 3.9)
  • GitHub Check: Test C++ (true)
  • GitHub Check: Test Python (2, 3.12)
  • GitHub Check: Test Python (2, 3.9)
  • GitHub Check: Test Python (1, 3.12)
  • GitHub Check: Build C++ (cuda120, cuda)
  • GitHub Check: Test Python (1, 3.9)
  • GitHub Check: Build C++ (cuda, cuda)
  • GitHub Check: Build C library (2.14, >=2.5.0rc0,<2.15, libdeepmd_c_cu11.tar.gz)
  • GitHub Check: Build C library (2.18, libdeepmd_c.tar.gz)
  • GitHub Check: Analyze (c-cpp)
🔇 Additional comments (9)
source/tests/consistent/descriptor/test_dpa3.py (3)

66-68: Test parameters updated correctly to include use_dynamic_sel

The parameterization has been updated to include the new dynamic selection feature, with appropriate constraints:

  1. a_compress_use_split is now fixed to True only
  2. use_dynamic_sel is added with True and False options

This ensures proper test coverage of the new dynamic neighbor selection feature.


159-161: PD backend appropriately skipped for dynamic selection

The skip condition correctly excludes tests with use_dynamic_sel=True for the PD backend, properly documenting that this functionality isn't yet supported in that backend.


108-109: New smooth_edge_update parameter added correctly

The smooth_edge_update parameter is properly added to the repflow configuration with a value of True, which is necessary for dynamic selection to work correctly, as seen in the implementation where dynamic selection requires smooth edge updates.

deepmd/pt/model/descriptor/repflow_layer.py (3)

330-384: Implementation of dynamic neighbor computation looks correct

The new method _cal_hg_dynamic provides a parallel implementation that operates on flattened neighborhoods rather than padded tensors. The scale factor calculation and aggregation operations look properly implemented.

One note is that for large molecular systems with many neighbors, this approach should significantly reduce memory usage compared to the padded version.


535-558: Fixed tensor split order in angle update

The code now correctly splits the matrix weights and applies them to the corresponding embeddings. The previous implementation had an issue with the order of these operations.


879-891: Dynamic selection implementation handles aggregation properly

The implementation correctly uses the aggregate utility function to gather data from flattened tensors back into per-atom representations, applying proper scaling based on dynamic_e_sel. This is a key aspect of the dynamic selection approach.

deepmd/dpmodel/descriptor/repflows.py (3)

128-138: Documentation comprehensively explains the new parameters

The documentation for use_dynamic_sel and sel_reduce_factor is very thorough and clearly explains:

  1. The purpose of dynamic selection (avoiding fixed padding)
  2. The impact on neighbor selection counts
  3. The relationship with normalization factors

This is excellent practice for API documentation.


837-841: Dynamic selection implementation reduces memory usage with flattened tensors

By using flattened tensors for edge and angle embeddings with dynamic selection, this implementation avoids the memory overhead of padded tensors. For systems with many atoms but varying coordination numbers, this can significantly reduce memory usage while maintaining numerical accuracy.


1631-1635: Good error handling for required smooth edge updates

The implementation correctly enforces that smooth_edge_update must be True when using dynamic selection by raising a clear NotImplementedError exception. This prevents users from using an unsupported configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
deepmd/pt/model/descriptor/repflow_layer.py (2)

106-109: Add missing validation for sel_reduce_factor

sel_reduce_factor needs validation to prevent division by zero or negative values, which would cause errors when used in expressions like self.dynamic_e_sel ** (-0.5).

self.sel_reduce_factor = sel_reduce_factor
+if self.sel_reduce_factor <= 0:
+    raise ValueError(f"`sel_reduce_factor` must be > 0, got {self.sel_reduce_factor}")
self.dynamic_e_sel = self.nnei / self.sel_reduce_factor
self.dynamic_a_sel = self.a_sel / self.sel_reduce_factor

1060-1064: Document dynamic selection requirement

The code requires smooth_edge_update=True when using dynamic selection, but this constraint isn't documented in user-facing documentation.

Please update the user documentation to clearly state that when use_dynamic_sel=True, smooth_edge_update must also be True to avoid the NotImplementedError.

deepmd/dpmodel/descriptor/repflows.py (2)

837-840: Add missing validation for sel_reduce_factor

sel_reduce_factor needs validation to prevent division by zero or negative values, which would cause errors when used in expressions like self.dynamic_e_sel ** (-0.5).

self.use_dynamic_sel = use_dynamic_sel
self.sel_reduce_factor = sel_reduce_factor
+if self.sel_reduce_factor <= 0:
+    raise ValueError(f"`sel_reduce_factor` must be > 0, got {self.sel_reduce_factor}")
self.dynamic_e_sel = self.nnei / self.sel_reduce_factor
self.dynamic_a_sel = self.a_sel / self.sel_reduce_factor

1631-1635: Document dynamic selection requirement

The code requires smooth_edge_update=True when using dynamic selection, but this constraint isn't documented in user-facing documentation.

Please update the user documentation to clearly state that when use_dynamic_sel=True, smooth_edge_update must also be True to avoid the NotImplementedError.

🧹 Nitpick comments (3)
deepmd/pt/model/descriptor/repflow_layer.py (1)

674-676: Fix shape reference inconsistency

There's a small inconsistency in the tensor shape reference.

sub_node_ext_update = torch.index_select(
-    sub_node_ext_update.reshape(nf * nall, sub_node_update.shape[-1]),
+    sub_node_ext_update.reshape(nf * nall, sub_node_ext_update.shape[-1]),
    0,
    n_ext2e_index,
)

While both shape references likely yield the same value, using the shape of the tensor being reshaped is more consistent and self-documenting.

deepmd/dpmodel/descriptor/repflows.py (2)

1202-1205: Fix shape reference inconsistency

There's a small inconsistency in the tensor shape reference.

sub_node_ext_update = xp.take(
-    xp.reshape(sub_node_ext_update, (nf * nall, sub_node_update.shape[-1])),
+    xp.reshape(sub_node_ext_update, (nf * nall, sub_node_ext_update.shape[-1])),
    n_ext2e_index,
    axis=0,
)

While both shape references likely yield the same value, using the shape of the tensor being reshaped is more consistent and self-documenting.


1286-1288: Remove redundant computation of nall

The variable nall is computed twice, creating potential for divergence.

nb, nloc, nnei = nlist.shape
-nall = node_ebd_ext.shape[1]
+# nall is already available from node_ebd_ext.shape[1]
n_edge = int(nlist_mask.sum().item())

The nall is computed earlier at line 438 differently as nall = xp.reshape(coord_ext, (nframes, -1)).shape[1] // 3, but then assigned again here. Since you're already using node_ebd_ext.shape[1] elsewhere (like in line 1183), consider using just one consistent way of accessing this value throughout the code.

🧰 Tools
🪛 Ruff (0.11.9)

1287-1287: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b67222b and 07bffcf.

📒 Files selected for processing (2)
  • deepmd/dpmodel/descriptor/repflows.py (29 hunks)
  • deepmd/pt/model/descriptor/repflow_layer.py (21 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
deepmd/dpmodel/descriptor/repflows.py (7)
deepmd/dpmodel/utils/network.py (2)
  • aggregate (962-1000)
  • get_graph_index (1003-1097)
deepmd/pt/model/network/utils.py (2)
  • aggregate (10-45)
  • get_graph_index (49-135)
deepmd/pt/model/descriptor/repflow_layer.py (6)
  • _cal_grrg (386-410)
  • _cal_hg (288-328)
  • _cal_hg_dynamic (331-383)
  • symmetrization_op_dynamic (457-512)
  • optim_angle_update_dynamic (556-604)
  • optim_edge_update_dynamic (641-683)
deepmd/dpmodel/descriptor/repformers.py (3)
  • _cal_grrg (750-775)
  • _cal_hg (684-747)
  • _make_nei_g1 (615-645)
deepmd/utils/version.py (1)
  • check_version_compatibility (2-27)
source/tests/consistent/descriptor/test_dpa3.py (1)
  • data (75-125)
source/tests/consistent/descriptor/test_se_e2_a.py (2)
  • data (77-97)
  • data (321-341)
🪛 Ruff (0.11.9)
deepmd/dpmodel/descriptor/repflows.py

1287-1287: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

deepmd/pt/model/descriptor/repflow_layer.py

746-746: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

⏰ Context from checks skipped due to timeout of 90000ms (30)
  • GitHub Check: Test C++ (false)
  • GitHub Check: Test C++ (true)
  • GitHub Check: Test Python (6, 3.12)
  • GitHub Check: Test Python (6, 3.9)
  • GitHub Check: Test Python (5, 3.12)
  • GitHub Check: Test Python (5, 3.9)
  • GitHub Check: Test Python (4, 3.12)
  • GitHub Check: Test Python (4, 3.9)
  • GitHub Check: Test Python (3, 3.12)
  • GitHub Check: Test Python (3, 3.9)
  • GitHub Check: Test Python (2, 3.12)
  • GitHub Check: Test Python (2, 3.9)
  • GitHub Check: Test Python (1, 3.12)
  • GitHub Check: Test Python (1, 3.9)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (c-cpp)
  • GitHub Check: Build wheels for cp310-manylinux_aarch64
  • GitHub Check: Build wheels for cp311-win_amd64
  • GitHub Check: Build wheels for cp311-macosx_arm64
  • GitHub Check: Build wheels for cp311-macosx_x86_64
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Build C library (2.14, >=2.5.0rc0,<2.15, libdeepmd_c_cu11.tar.gz)
  • GitHub Check: Build C library (2.18, libdeepmd_c.tar.gz)
  • GitHub Check: Build C++ (clang, clang)
  • GitHub Check: Build C++ (rocm, rocm)
  • GitHub Check: Build C++ (cuda120, cuda)
  • GitHub Check: Build C++ (cuda, cuda)
  • GitHub Check: Build C++ (cpu, cpu)
🔇 Additional comments (1)
deepmd/dpmodel/descriptor/repflows.py (1)

695-708:

Details

❌ Incorrect review comment

Handle empty edge case in dynamic calculation

The current implementation doesn't guard against the case when n_edge==0, which is rare but possible with extreme exclusions.

xp = array_api_compat.array_namespace(flat_edge_ebd, flat_h2, flat_sw, owner)
n_edge, e_dim = flat_edge_ebd.shape
+# Early return for empty edge case to avoid issues in aggregate function
+if n_edge == 0:
+    return xp.zeros((nb, nloc, 3, e_dim), dtype=flat_edge_ebd.dtype)

# n_edge x e_dim
flat_edge_ebd = flat_edge_ebd * xp.expand_dims(flat_sw, axis=-1)

Run the following to verify if there are tests covering the empty edge case:


🏁 Script executed:

#!/bin/bash
# Check for any tests that might test the dynamic selection with empty edges
grep -r "test.*dynamic.*sel" source/tests/
grep -r "empty.*edge" source/tests/
grep -r "n_edge.*==.*0" source/tests/
grep -r "no.*neighbor" source/tests/

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Locate the definition of `aggregate` in the descriptor module
grep -R "def aggregate" -n deepmd/dpmodel/descriptor

# If it's in common.py, show the first 200 lines to inspect handling of empty input
sed -n '1,200p' deepmd/dpmodel/descriptor/common.py

Length of output: 185


🏁 Script executed:

#!/bin/bash
# List descriptor directory contents to locate files
ls -R deepmd/dpmodel/descriptor

# Search for all references to `aggregate` across the repo to find its definition or import
grep -R "aggregate" -n .

# Show the top of repflows.py to see how `aggregate` is imported
sed -n '1,100p' deepmd/dpmodel/descriptor/repflows.py

Length of output: 12303


🏁 Script executed:

#!/bin/bash
# Locate and display the aggregate function definition to check empty-input handling
grep -n "def aggregate" -n deepmd/dpmodel/utils/network.py
sed -n '950,1040p' deepmd/dpmodel/utils/network.py

Length of output: 2924


🏁 Script executed:

#!/bin/bash
# Locate where `owner` and `num_owner` are defined in repflows.py
grep -n "owner\s*=" -n deepmd/dpmodel/descriptor/repflows.py
grep -n "num_owner\s*=" -n deepmd/dpmodel/descriptor/repflows.py

# Show the block defining these variables (around line 650)
sed -n '630,700p' deepmd/dpmodel/descriptor/repflows.py

Length of output: 3499


No need for an empty-edge guard in _cal_hg_dynamic
The aggregate implementation in deepmd/dpmodel/utils/network.py already handles n_edge == 0 when you pass num_owner—it will pad the bin count to num_owner and return a zero tensor of shape (num_owner, feature_dim). That output reshapes naturally to (nb, nloc, 3, e_dim) and scales to zero without error. No additional early‐return is required here.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
deepmd/pt/model/network/utils.py (2)

96-107: ⚠️ Potential issue

Add explicit boolean casting to avoid potential indexing errors

The function currently assumes nlist_mask is a boolean tensor when used for indexing, but this isn't verified, which could lead to runtime errors if a different dtype is provided.

Apply this fix to ensure robust indexing:

-    n2e_index = n2e_index[nlist_mask]  # graph node index, atom_graph[:, 0]
+    mask = nlist_mask.bool()
+    n2e_index = n2e_index[mask]  # graph node index, atom_graph[:, 0]

-    n_ext2e_index = shifted_nlist[nlist_mask]  # graph neighbor index, atom_graph[:, 1]
+    n_ext2e_index = shifted_nlist[mask]  # graph neighbor index, atom_graph[:, 1]

Similar changes should be applied to other boolean indexing operations at lines 112, 118, 123, and 128.


117-119: ⚠️ Potential issue

Add explicit boolean casting for edge_index assignment

Similar to the previous issue, the boolean indexing for assignment at line 118 should use explicit boolean casting to avoid dtype-related errors.

Apply this fix:

-    edge_index[nlist_mask] = edge_id
+    edge_index[mask] = edge_id  # Reuse the mask variable created earlier
deepmd/dpmodel/descriptor/repflows.py (1)

666-718: ⚠️ Potential issue

Missing guard against empty edge sets in _cal_hg_dynamic.

When n_edge==0 (which can happen with extreme exclusions), the function will attempt to process an empty tensor which might cause issues in the underlying implementation.

Add an early return statement to handle the case when no edges are found:

def _cal_hg_dynamic(
    flat_edge_ebd: np.ndarray,
    flat_h2: np.ndarray,
    flat_sw: np.ndarray,
    owner: np.ndarray,
    num_owner: int,
    nb: int,
    nloc: int,
    scale_factor: float,
) -> np.ndarray:
    xp = array_api_compat.array_namespace(flat_edge_ebd, flat_h2, flat_sw, owner)
    n_edge, e_dim = flat_edge_ebd.shape
+    # Return early if no edges are found
+    if n_edge == 0:
+        return xp.zeros((nb, nloc, 3, e_dim))
    # n_edge x e_dim
    flat_edge_ebd = flat_edge_ebd * xp.expand_dims(flat_sw, axis=-1)
🧹 Nitpick comments (2)
deepmd/pt/model/descriptor/repflows.py (1)

484-504: Consider extracting common graph index logic to a separate method

The dynamic selection branch adds complexity to the forward method. For better maintainability, consider extracting the dynamic selection logic to a separate helper method.

+ def _prepare_dynamic_selection(
+     self,
+     nlist,
+     nlist_mask,
+     a_nlist_mask,
+     nall,
+     edge_input,
+     h2,
+     sw,
+     angle_input
+ ):
+     # get graph index
+     edge_index, angle_index = get_graph_index(
+         nlist, nlist_mask, a_nlist_mask, nall
+     )
+     # flat all the tensors
+     # n_edge x 1
+     edge_input = edge_input[nlist_mask]
+     # n_edge x 3
+     h2 = h2[nlist_mask]
+     # n_edge x 1
+     sw = sw[nlist_mask]
+     # nb x nloc x a_nnei x a_nnei
+     a_nlist_mask_3d = a_nlist_mask[:, :, :, None] & a_nlist_mask[:, :, None, :]
+     # n_angle x 1
+     angle_input = angle_input[a_nlist_mask_3d]
+     # n_angle x 1
+     a_sw = (a_sw[:, :, :, None] * a_sw[:, :, None, :])[a_nlist_mask_3d]
+     
+     return edge_index, angle_index, edge_input, h2, sw, angle_input, a_sw, a_nlist_mask_3d

Then in the forward method:

      if self.use_dynamic_sel:
-         # get graph index
-         edge_index, angle_index = get_graph_index(
-             nlist, nlist_mask, a_nlist_mask, nall
-         )
-         # flat all the tensors
-         # n_edge x 1
-         edge_input = edge_input[nlist_mask]
-         # n_edge x 3
-         h2 = h2[nlist_mask]
-         # n_edge x 1
-         sw = sw[nlist_mask]
-         # nb x nloc x a_nnei x a_nnei
-         a_nlist_mask = a_nlist_mask[:, :, :, None] & a_nlist_mask[:, :, None, :]
-         # n_angle x 1
-         angle_input = angle_input[a_nlist_mask]
-         # n_angle x 1
-         a_sw = (a_sw[:, :, :, None] * a_sw[:, :, None, :])[a_nlist_mask]
+         edge_index, angle_index, edge_input, h2, sw, angle_input, a_sw, a_nlist_mask_3d = self._prepare_dynamic_selection(
+             nlist, nlist_mask, a_nlist_mask, nall, edge_input, h2, sw, angle_input, a_sw
+         )
+         a_nlist_mask = a_nlist_mask_3d
deepmd/dpmodel/descriptor/repflows.py (1)

1296-1296: Unused variable nall detected.

The local variable nall is assigned on line 1296 but never used in this scope.

-    nb, nloc, nnei = nlist.shape
-    nall = xp.reshape(coord_ext, (nframes, -1)).shape[1] // 3
+    nb, nloc, nnei = nlist.shape
🧰 Tools
🪛 Ruff (0.11.9)

1296-1296: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07bffcf and 8a350d9.

📒 Files selected for processing (10)
  • deepmd/dpmodel/descriptor/dpa3.py (5 hunks)
  • deepmd/dpmodel/descriptor/repflows.py (29 hunks)
  • deepmd/dpmodel/utils/network.py (1 hunks)
  • deepmd/pd/model/descriptor/dpa3.py (1 hunks)
  • deepmd/pd/model/descriptor/repflow_layer.py (5 hunks)
  • deepmd/pd/model/descriptor/repflows.py (3 hunks)
  • deepmd/pt/model/descriptor/repflows.py (8 hunks)
  • deepmd/pt/model/network/utils.py (1 hunks)
  • deepmd/utils/argcheck.py (2 hunks)
  • source/tests/universal/dpmodel/descriptor/test_descriptor.py (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • deepmd/dpmodel/utils/network.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • source/tests/universal/dpmodel/descriptor/test_descriptor.py
  • deepmd/dpmodel/descriptor/dpa3.py
  • deepmd/utils/argcheck.py
🧰 Additional context used
🧬 Code Graph Analysis (2)
deepmd/pd/model/descriptor/repflow_layer.py (2)
deepmd/utils/version.py (1)
  • check_version_compatibility (2-27)
source/tests/consistent/descriptor/test_dpa3.py (1)
  • data (75-125)
deepmd/pt/model/network/utils.py (1)
deepmd/dpmodel/utils/network.py (2)
  • aggregate (962-1000)
  • get_graph_index (1003-1097)
🪛 Ruff (0.11.9)
deepmd/dpmodel/descriptor/repflows.py

1296-1296: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

⏰ Context from checks skipped due to timeout of 90000ms (28)
  • GitHub Check: Test Python (6, 3.12)
  • GitHub Check: Test Python (6, 3.9)
  • GitHub Check: Test Python (5, 3.12)
  • GitHub Check: Test Python (5, 3.9)
  • GitHub Check: Test Python (4, 3.12)
  • GitHub Check: Test Python (4, 3.9)
  • GitHub Check: Build wheels for cp310-manylinux_aarch64
  • GitHub Check: Test Python (3, 3.12)
  • GitHub Check: Build wheels for cp311-win_amd64
  • GitHub Check: Build C++ (clang, clang)
  • GitHub Check: Test Python (3, 3.9)
  • GitHub Check: Build wheels for cp311-macosx_arm64
  • GitHub Check: Build C++ (rocm, rocm)
  • GitHub Check: Test Python (2, 3.12)
  • GitHub Check: Build wheels for cp311-macosx_x86_64
  • GitHub Check: Build C++ (cuda120, cuda)
  • GitHub Check: Test Python (2, 3.9)
  • GitHub Check: Build C library (2.14, >=2.5.0rc0,<2.15, libdeepmd_c_cu11.tar.gz)
  • GitHub Check: Build C++ (cuda, cuda)
  • GitHub Check: Test Python (1, 3.12)
  • GitHub Check: Test C++ (false)
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Build C++ (cpu, cpu)
  • GitHub Check: Build C library (2.18, libdeepmd_c.tar.gz)
  • GitHub Check: Test C++ (true)
  • GitHub Check: Test Python (1, 3.9)
  • GitHub Check: Analyze (c-cpp)
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
🔇 Additional comments (28)
deepmd/pt/model/network/utils.py (2)

9-45: Efficient implementation of row aggregation by owner indices

The aggregate function provides an efficient implementation for aggregating rows of a tensor by owner indices, supporting both summation and averaging modes. The code correctly handles edge cases like avoiding division by zero and ensuring proper device placement.


48-83: Well-documented function for graph index mapping

The get_graph_index function provides comprehensive documentation of parameters and return values, making it clear how the function constructs indices for edge and angle graphs from neighbor lists.

deepmd/pt/model/descriptor/repflows.py (4)

139-150: Well-documented dynamic neighbor selection feature

The documentation clearly explains the purpose and benefits of the dynamic neighbor selection feature, including how it allows for larger selection numbers and removes the need for fixed padding.


201-244: Good parameter validation and dependency checking

The code properly validates the new parameters: checking that smooth_edge_update must be True when use_dynamic_sel is True, and ensuring sel_reduce_factor is positive.


297-299: Consistent parameter propagation to layers

The new parameters are correctly propagated to each RepFlowLayer instance, ensuring consistent configuration throughout the model.


599-612: Effective conditional calculation of rotation matrix embedding

The code properly switches between static and dynamic calculation methods for the rotation matrix embedding, applying the scaling factor for normalization in the dynamic case.

deepmd/pd/model/descriptor/dpa3.py (1)

153-155: Parameter propagation correctly implemented

The new parameters are properly passed from the repflow_args to the DescrptBlockRepflows constructor.

deepmd/pd/model/descriptor/repflows.py (3)

162-164: Parameters added with appropriate defaults

The new parameters for dynamic neighbor selection are added with the same default values as in the PyTorch implementation.


196-198: Properly disabling unsupported feature with clear error message

The code explicitly asserts that dynamic selection is not supported in this implementation, preventing users from enabling a feature that isn't fully implemented.


251-253: Parameter propagation to layer instances

The new parameters are correctly passed to each RepFlowLayer instance, preparing for future support of the feature.

deepmd/pd/model/descriptor/repflow_layer.py (5)

55-56: New parameters for dynamic neighbor selection look good.

These parameters enable the dynamic neighbor selection feature, allowing for variable-sized neighbor lists without fixed padding.


103-106: Well-implemented instance variables for dynamic selection.

The scaling factors dynamic_e_sel and dynamic_a_sel are properly derived from the original selection sizes, allowing for proper normalization when using dynamic selection.


821-821: Version increment is appropriate for the API change.

Properly incrementing the version number from 1 to 2 ensures backward compatibility can be maintained during deserialization.


845-846: New parameters correctly added to serialization.

The dynamic selection parameters are properly included in the serialized data structure.


889-889: Correctly updated version compatibility check.

The check_version_compatibility function now correctly handles both version 1 and 2 models during deserialization.

deepmd/dpmodel/descriptor/repflows.py (13)

24-25: Added utility imports for dynamic selection functionality.

These imported functions support the new dynamic selection feature, providing graph indexing and aggregation capabilities.


128-139: Well-documented new parameters for dynamic selection.

The docstring clearly explains the purpose and behavior of the dynamic neighbor selection feature, including the recommendation to set larger selection numbers when this feature is enabled.


221-230: Good validation for dynamic selection parameters.

The code properly validates that:

  1. smooth_edge_update must be True when use_dynamic_sel is True
  2. sel_reduce_factor must be greater than 0

This prevents invalid configurations that could lead to runtime errors.


483-512: More explicit handling of neighbor list index padding.

The code now explicitly sets padding positions to index 0 and adds clear comments, improving code readability and maintainability.


514-536: Clean implementation of dynamic selection logic.

The conditional branch for dynamic selection properly:

  1. Gets graph indices
  2. Flattens tensors based on masks
  3. Creates angle masks by combining atomic masks
  4. Uses zero indices for the static case

This effectively handles both dynamic and static selection modes.


565-578: Rotation matrix calculation handles both modes correctly.

The code elegantly branches between static and dynamic rotation matrix calculations, using the appropriate scale factor for normalization in the dynamic case.


721-775: Proper symmetrization operator for dynamic selection mode.

The implementation correctly adapts the symmetrization operation to work with flattened dynamic data instead of fixed-size tensors.


1071-1128: Well-implemented angle update for dynamic selection.

The dynamic angle update method properly handles flattened tensors and uses index arrays for data lookup and reduction.


1173-1219: Effective edge update for dynamic selection.

The dynamic edge update method implements the equivalent operations to the static version but adapts them for flattened data structures.


1435-1450: Elegant normalization for dynamic selection.

The normalization in the dynamic case properly uses the dynamic_e_sel value instead of the raw nnei, ensuring consistent scaling behavior between static and dynamic modes.


1640-1644: Good error handling for incompatible configurations.

The code properly blocks invalid configurations (using dynamic selection without smooth edge updates) with a clear error message explaining the restriction.


1781-1782: New parameters correctly added to serialization structure.

The dynamic selection parameters are properly incorporated into the serialization format.


1825-1825: Correctly updated version compatibility check.

Version checking now properly handles both version 1 and 2 models during deserialization.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
deepmd/dpmodel/descriptor/repflows.py (1)

666-718: ⚠️ Potential issue

Missing check for empty edge sets in _cal_hg_dynamic

This function correctly calculates the transposed rotation matrix for dynamic selection, but it doesn't handle the case when n_edge==0, which can happen with extreme exclusions and cause crashes in the underlying implementation.

Add the following early return at the beginning of the function, after line 705:

def _cal_hg_dynamic(
    flat_edge_ebd: np.ndarray,
    flat_h2: np.ndarray,
    flat_sw: np.ndarray,
    owner: np.ndarray,
    num_owner: int,
    nb: int,
    nloc: int,
    scale_factor: float,
) -> np.ndarray:
    xp = array_api_compat.array_namespace(flat_edge_ebd, flat_h2, flat_sw, owner)
    n_edge, e_dim = flat_edge_ebd.shape
+    # Handle empty edge set case
+    if n_edge == 0:
+        return xp.zeros((nb, nloc, 3, e_dim))
    # n_edge x e_dim
    flat_edge_ebd = flat_edge_ebd * xp.expand_dims(flat_sw, axis=-1)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a350d9 and 45d1287.

📒 Files selected for processing (2)
  • deepmd/dpmodel/descriptor/repflows.py (29 hunks)
  • deepmd/dpmodel/utils/network.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • deepmd/dpmodel/utils/network.py
🧰 Additional context used
🪛 Ruff (0.11.9)
deepmd/dpmodel/descriptor/repflows.py

1296-1296: Local variable nall is assigned to but never used

Remove assignment to unused variable nall

(F841)

⏰ Context from checks skipped due to timeout of 90000ms (29)
  • GitHub Check: Test Python (6, 3.12)
  • GitHub Check: Test Python (6, 3.9)
  • GitHub Check: Test Python (5, 3.12)
  • GitHub Check: Test Python (5, 3.9)
  • GitHub Check: Test Python (4, 3.12)
  • GitHub Check: Build wheels for cp310-manylinux_aarch64
  • GitHub Check: Test Python (4, 3.9)
  • GitHub Check: Build wheels for cp311-win_amd64
  • GitHub Check: Test Python (3, 3.12)
  • GitHub Check: Build wheels for cp311-macosx_arm64
  • GitHub Check: Build C++ (clang, clang)
  • GitHub Check: Test Python (3, 3.9)
  • GitHub Check: Build wheels for cp311-macosx_x86_64
  • GitHub Check: Build C++ (rocm, rocm)
  • GitHub Check: Test Python (2, 3.12)
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Build C++ (cuda120, cuda)
  • GitHub Check: Test Python (2, 3.9)
  • GitHub Check: Analyze (python)
  • GitHub Check: Build wheels for cp311-manylinux_x86_64
  • GitHub Check: Test Python (1, 3.12)
  • GitHub Check: Build C++ (cuda, cuda)
  • GitHub Check: Build C library (2.14, >=2.5.0rc0,<2.15, libdeepmd_c_cu11.tar.gz)
  • GitHub Check: Test Python (1, 3.9)
  • GitHub Check: Test C++ (false)
  • GitHub Check: Build C++ (cpu, cpu)
  • GitHub Check: Analyze (c-cpp)
  • GitHub Check: Build C library (2.18, libdeepmd_c.tar.gz)
  • GitHub Check: Test C++ (true)
🔇 Additional comments (9)
deepmd/dpmodel/descriptor/repflows.py (9)

128-139: Implementation for dynamic neighbor selection looks good

The dynamic selection feature allows for more accurate handling of neighbors by using variable numbers without padding. This is more efficient and accurate than fixed padding.


221-230: Good validation for the feature requirements

The validation ensures that dynamic selection is used with smooth edge updates and properly validates the reduction factor parameter.


514-535: The dynamic selection branch correctly handles the graph index and tensor flattening

This implementation properly handles both the standard and dynamic selection approaches, flattening the tensors when using dynamic selection and using masks appropriately.


847-849: Normalization factors look good

The calculation of dynamic_e_sel and dynamic_a_sel from the selection counts divided by the reduction factor is appropriate for normalization in the dynamic selection case.


1071-1129: Optimized angle update method for dynamic selection is well structured

The dynamic version of the angle update method properly handles flattened tensors and index arrays, maintaining a similar logic to the original version but adapted for dynamic selection.


1173-1220: Optimized edge update method for dynamic selection is well implemented

The dynamic version of the edge update method correctly uses indexing operations to achieve the same result as the original approach but with dynamic selection.


1435-1450: Good scaling adjustment for dynamic selection

The implementation properly adjusts the scaling factor when using dynamic selection by dividing by dynamic_e_sel instead of the fixed nnei value, ensuring consistent normalization.


1781-1782: Version increment and serialization handled properly

The serialization of the new parameters and the version increment to 2 ensures backward compatibility and proper handling of the new feature.


1825-1825: Good version compatibility check

This ensures backward compatibility when deserializing models, allowing older models to be loaded with the new code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
deepmd/dpmodel/utils/network.py (1)

962-1027: Well-structured implementation for backend-agnostic aggregation

The aggregate function provides a robust solution for aggregating rows in data tensors according to owner indices. The implementation effectively handles different backend arrays through array_api_compat and provides fallback implementations for environments lacking native support.

Consider using a ternary operator for better readability:

-    if hasattr(xp, "bincount"):
-        bin_count = xp.bincount(owners)
-    else:
-        # for array_api_strict
-        bin_count = bincount(owners)
+    # for array_api_strict if native bincount is not available
+    bin_count = xp.bincount(owners) if hasattr(xp, "bincount") else bincount(owners)
🧰 Tools
🪛 Ruff (0.11.9)

1005-1009: Use ternary operator bin_count = xp.bincount(owners) if hasattr(xp, "bincount") else bincount(owners) instead of if-else-block

(SIM108)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51702ea and e3ea98b.

📒 Files selected for processing (1)
  • deepmd/dpmodel/utils/network.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
deepmd/dpmodel/utils/network.py

1005-1009: Use ternary operator bin_count = xp.bincount(owners) if hasattr(xp, "bincount") else bincount(owners) instead of if-else-block

(SIM108)

⏰ Context from checks skipped due to timeout of 90000ms (23)
  • GitHub Check: Test Python (6, 3.12)
  • GitHub Check: Test Python (6, 3.9)
  • GitHub Check: Test Python (5, 3.12)
  • GitHub Check: Test Python (5, 3.9)
  • GitHub Check: Test Python (4, 3.12)
  • GitHub Check: Test Python (4, 3.9)
  • GitHub Check: Test Python (3, 3.12)
  • GitHub Check: Test Python (3, 3.9)
  • GitHub Check: Test Python (2, 3.12)
  • GitHub Check: Build C library (2.14, >=2.5.0rc0,<2.15, libdeepmd_c_cu11.tar.gz)
  • GitHub Check: Test Python (2, 3.9)
  • GitHub Check: Build C library (2.18, libdeepmd_c.tar.gz)
  • GitHub Check: Test Python (1, 3.12)
  • GitHub Check: Test Python (1, 3.9)
  • GitHub Check: Test C++ (false)
  • GitHub Check: Test C++ (true)
  • GitHub Check: Build C++ (clang, clang)
  • GitHub Check: Build C++ (rocm, rocm)
  • GitHub Check: Build C++ (cuda120, cuda)
  • GitHub Check: Build C++ (cuda, cuda)
  • GitHub Check: Build C++ (cpu, cpu)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (c-cpp)
🔇 Additional comments (1)
deepmd/dpmodel/utils/network.py (1)

1029-1123:

Details

✅ Verification successful

Thorough implementation for graph indexing with proper type handling

The get_graph_index function correctly computes index mappings for edge and angle graphs from neighbor lists and masks. It properly handles type conversions and efficiently generates the necessary indices for graph-based operations.

I noticed you correctly addressed the previous review comment by using int(xp.sum(xp.astype(nlist_mask, xp.int32))) at line 1073 to convert the array scalar to a Python integer. This change ensures compatibility with JIT and non-NumPy backends.

Let's verify that all other array scalar conversions in this function are handled properly:


🏁 Script executed:

#!/bin/bash
# Check for other potential array scalar to integer conversions in the function

# Look for direct usage of array values in arange or similar functions
rg "xp\.arange\([^)]+\)" deepmd/dpmodel/utils/network.py

# Look for other array sum operations that might need int conversion
rg "xp\.sum\(" deepmd/dpmodel/utils/network.py

Length of output: 382


All scalar conversions in get_graph_index are correct

I verified that:

  • nf and nloc (from nlist.shape) are Python ints when used in xp.arange(nf * nloc, …).
  • nall is a Python int and only scales an array, not used where an int is required.
  • n_edge is explicitly converted to a Python int via int(xp.sum(...)) before being passed to xp.arange.
  • No other xp.sum results are used in contexts that require Python ints.

No changes are needed here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
deepmd/dpmodel/utils/network.py (1)

964-1001: Clean implementation of data aggregation function

The function properly handles aggregation of data by owner indices with good edge case handling.

The division operation in line 999 could be simplified using broadcasting rather than double transposition:

- output = xp.transpose(xp.transpose(output) / bin_count)
+ output = output / bin_count[:, xp.newaxis]

This would make the code more readable and potentially more efficient.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3ea98b and a491ef5.

📒 Files selected for processing (2)
  • deepmd/dpmodel/array_api.py (1 hunks)
  • deepmd/dpmodel/utils/network.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
deepmd/dpmodel/utils/network.py (2)
deepmd/dpmodel/array_api.py (2)
  • add_at (97-115)
  • bincount (118-128)
deepmd/pt/model/network/utils.py (2)
  • aggregate (10-45)
  • get_graph_index (49-135)
🔇 Additional comments (3)
deepmd/dpmodel/array_api.py (2)

97-116: Well-implemented array indexing add operation across backends

This function elegantly handles the differences between NumPy's in-place add.at, JAX's functional updates, and provides a reasonable fallback implementation for other array types.

The code is well-structured with appropriate comments explaining the backend-specific implementations. The fallback implementation using a loop is a reasonable approach, though it may be less efficient for large arrays.


118-128: Effective implementation of bincount with multi-backend support

The function provides a consistent interface for bincount operations across backends, with appropriate handling of edge cases.

Good job on handling the case where weights are None and ensuring the output has the correct length using max(minlength, int(xp.max(x)) + 1). The fallback implementation using add_at is particularly clean.

deepmd/dpmodel/utils/network.py (1)

1004-1098: Robust implementation of graph index mapping with array scalar fix

This function correctly constructs index mappings for complex graph operations, addressing the array scalar issue noted in previous reviews.

Line 1048 properly addresses the previous review comment by explicitly converting the sum to a Python integer:

n_edge = int(xp.sum(xp.astype(nlist_mask, xp.int32)))

This ensures compatibility with backends that return array scalars (like JAX) rather than Python integers, preventing type errors when using with xp.arange.

@iProzd iProzd requested a review from njzjz May 22, 2025 14:54
@codecov
Copy link

codecov bot commented May 23, 2025

Codecov Report

Attention: Patch coverage is 97.36070% with 9 lines in your changes missing coverage. Please review.

Project coverage is 84.78%. Comparing base (cb78ec0) to head (a55c6e3).
Report is 3 commits behind head on devel.

Files with missing lines Patch % Lines
deepmd/dpmodel/descriptor/repflows.py 95.00% 6 Missing ⚠️
deepmd/pt/model/descriptor/repflows.py 93.33% 2 Missing ⚠️
deepmd/dpmodel/utils/network.py 97.43% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            devel    #4754      +/-   ##
==========================================
+ Coverage   84.73%   84.78%   +0.04%     
==========================================
  Files         697      698       +1     
  Lines       67424    67709     +285     
  Branches     3541     3541              
==========================================
+ Hits        57134    57406     +272     
- Misses       9160     9170      +10     
- Partials     1130     1133       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@iProzd iProzd added this pull request to the merge queue May 23, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks May 23, 2025
@iProzd iProzd added this pull request to the merge queue May 25, 2025
Merged via the queue into deepmodeling:devel with commit 95ca4ad May 25, 2025
60 checks passed
@iProzd iProzd deleted the D0516_dynamic_sel branch May 25, 2025 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants