Skip to content

feat(dpmodel): NeighborGraph 3-body angle machinery (PR-E)#5717

Open
wanghan-iapcm wants to merge 12 commits into
deepmodeling:masterfrom
wanghan-iapcm:feat-graph-angles-prE
Open

feat(dpmodel): NeighborGraph 3-body angle machinery (PR-E)#5717
wanghan-iapcm wants to merge 12 commits into
deepmodeling:masterfrom
wanghan-iapcm:feat-graph-angles-prE

Conversation

@wanghan-iapcm

@wanghan-iapcm wanghan-iapcm commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

NeighborGraph PR-E: the optional 3-body angle extension of the edge-graph neighbor-list contract (design discussion wanghan-iapcm#4). An angle is a pair of edges sharing a center (dst(edge_a) == dst(edge_b)), stored as angle_index (2, A) into [0, E) + angle_mask (A,)edge_vec stays the ONLY geometry leaf, so force/virial assembly is untouched (proven by an invariance test).

Stacked on #5715 (PR-D) — reuses its center_edge_pairs. Please merge #5715 first; this branch then rebases clean onto master. Only the last 10 commits belong to this PR.

What's added (all in deepmd/dpmodel/utils/neighbor_graph/)

  • pad_and_guard_angles (graph.py) — angle-axis padder mirroring pad_and_guard_edges (dynamic guard append / static angle_capacity with overflow ValueError).
  • angles.py (new):
    • build_angle_index(edge_index, edge_vec, edge_mask, n_total, a_rcut, *, ordered=False, include_self=False, layout=None) — unordered, no-self pairs of edges sharing a center where BOTH edges are within a_rcut; built on PR-D's center_edge_pairs; pair_mask folded into angle_mask (never discarded).
    • attach_angles(graph, a_rcut, ...) — post-hoc: edge graph in, graph with angle fields out (dataclasses.replace); default builders keep angles None.
    • angle_to_edge_sum / angle_to_node_sum — segment-sum aggregation to the query edge / shared center.
    • graph_angle_cos(angle_index, edge_vec, eps=1e-6) — per-angle cos θ mirroring dpa3 repflows cosine_ij eps placement exactly (+eps in norm denominators, *(1-eps) on the product).
    • angle_padding_fraction(graph) — mask-derived padding-waste report for static capacities.

Semantics / decisions

  • Unordered, no-self by default: dpa3's dense angle tensor is the redundant ordered a_sel x a_sel square including the j==k diagonal; the graph set keeps one entry per unordered {j,k} pair and moves the degenerate diagonal to the (a_rcut-filtered) edge channel. Dense parity is therefore asserted against the OFF-DIAGONAL cosine_ij[j,k] (j != k) at rtol/atol 1e-12 (same-math fp64), at non-binding a_sel. The ordered+self full square stays available via flags.
  • a_sel = normalization-only (carry-all within a_rcut), consistent with the edge-sel decision.
  • Second oracle: se_t dot-product convention cross-checked from coordinates in the sw == 1 regime (rtol 1e-12).

Tests

source/tests/common/dpmodel/test_angle_builder.py (21) + test_graph_angle_cos_parity.py (6): brute-force triplet oracle (all flag combinations, multi-center, static layout, node_capacity branch), dpa3 dense-parity + no-self-angle assertion, se_t coordinate oracle, force/virial bit-exact invariance with/without angles, padding-fraction (incl. total==0), torch-namespace smoke tests for every new function. Full neighbor-graph suite: 54 passed.

Known limitations

  • Machinery + angle-channel math only — no dpa3 graph descriptor here (dpa3 is message-passing; wiring = PR-G). se_t/se_t_tebd are not migrated (mixed_types=False), used as oracle only.
  • Angle enumeration is the compact eager form (nonzero in center_edge_pairs) even when a static layout is passed — angle_capacity fixes the output shape only; shape-static enumeration for export is deferred to PR-G.
  • Not bit-parity with dense dpa3 by construction (unordered/no-self reformulation; recoverable in PR-G via symmetric angle→edge 2x + edge-channel diagonal).
  • Aggregation helpers follow the mask-then-reduce convention: callers mask per-angle data by angle_mask before summing (padding angles point at edge 0).
  • numpy/torch validated; jax rides the array-API surface (no jax-specific test here); A ~ sum(deg^2) capacity overhead mitigated by a_rcut < rcut and reported by angle_padding_fraction.

Summary by CodeRabbit

  • New Features

    • Added angle-graph utilities to build, attach, and analyze angle-based relationships in neighbor graphs.
    • Introduced padding/guarding for angle indices and added cosine/aggregation helpers for angles.
    • Expanded public exports for angle/edge-pair helpers and additional segment reductions (e.g., max/softmax).
  • Tests

    • Added extensive unit coverage for angle index building, attachment, masking/aggregation correctness, padding behavior, and angle padding fraction.
    • Added parity tests to validate cosine computations against reference formulations and across NumPy/Torch inputs.

@dosubot dosubot Bot added the new feature label Jul 3, 2026
@github-actions github-actions Bot added the Python label Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f315bb66-4333-4a90-8d2f-ce72f9b695ff

📥 Commits

Reviewing files that changed from the base of the PR and between dd58c02 and 6c2c922.

📒 Files selected for processing (2)
  • source/tests/common/dpmodel/test_angle_builder.py
  • source/tests/common/dpmodel/test_graph_angle_cos_parity.py
💤 Files with no reviewable changes (1)
  • source/tests/common/dpmodel/test_graph_angle_cos_parity.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/tests/common/dpmodel/test_angle_builder.py

📝 Walkthrough

Walkthrough

This PR adds angle-graph support to NeighborGraph: angle padding/guarding, angle-index construction and attachment, cosine and aggregation helpers, package re-exports, and unit/parity tests.

Changes

Angle graph construction and aggregation

Layer / File(s) Summary
Angle padding/guard primitive
deepmd/dpmodel/utils/neighbor_graph/graph.py, source/tests/common/dpmodel/test_angle_builder.py
pad_and_guard_angles pads a real angle-index prefix with a dummy suffix, producing a boolean mask, supporting dynamic (min_angles) and static (angle_capacity) sizing with overflow checks.
Angle index construction and graph attachment
deepmd/dpmodel/utils/neighbor_graph/angles.py, source/tests/common/dpmodel/test_angle_builder.py
build_angle_index derives cutoff-gated, optionally ordered/self-inclusive edge-pair angle indices via center_edge_pairs and pad_and_guard_angles; attach_angles wires these into a NeighborGraph while preserving other fields; tests cover oracle matching, torch compatibility, multi-center behavior, and layout sizing.
Angle cosine and aggregation utilities
deepmd/dpmodel/utils/neighbor_graph/angles.py, deepmd/dpmodel/utils/neighbor_graph/__init__.py, source/tests/common/dpmodel/test_angle_builder.py, source/tests/common/dpmodel/test_graph_angle_cos_parity.py
graph_angle_cos computes epsilon-stabilized per-angle cosines; angle_to_edge_sum/angle_to_node_sum aggregate per-angle data via segment_sum; angle_padding_fraction reports padding stats; new symbols are re-exported in package __all__; parity tests cover geometry, dense-oracle matching, and torch/numpy agreement.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

  • deepmodeling/deepmd-kit#5581: Extends the same NeighborGraph foundation by building on the graph/segment/padding contract used here.
  • deepmodeling/deepmd-kit#5715: Uses the re-exported center_edge_pairs, segment_max, and segment_softmax additions from the same neighbor-graph module surface.

Suggested labels: enhancement

Suggested reviewers: iProzd

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding 3-body angle support to NeighborGraph in dpmodel.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread source/tests/common/dpmodel/test_angle_builder.py Fixed
Comment thread source/tests/common/dpmodel/test_angle_builder.py Fixed
Comment thread source/tests/common/dpmodel/test_graph_angle_cos_parity.py Fixed

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
source/tests/common/dpmodel/test_angle_builder.py (1)

326-364: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a padded-angle_index case to the aggregation tests.

Both aggregation tests here only use angle data where every slot is real (no padding). Given the padding-mask precondition gap noted in angles.py (angle_to_edge_sum/angle_to_node_sum), a test with a padded angle_index and non-zero padding data values would catch regressions if that precondition is ever violated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/tests/common/dpmodel/test_angle_builder.py` around lines 326 - 364,
The aggregation tests in test_angle_builder only cover fully real angle slots,
so add a new padded-angle case for angle_to_edge_sum and angle_to_node_sum using
a padded angle_index with non-zero data in the padded positions. Reuse the
existing test_angle_aggregation and test_angle_aggregation_torch_namespace
patterns, but assert that padding is ignored in both the numpy and
torch-namespace paths so regressions around the padding-mask precondition are
caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deepmd/dpmodel/utils/neighbor_graph/segment.py`:
- Around line 59-89: The masked softmax in segment_softmax can overflow because
shifted is computed from raw data instead of data_for_max, allowing masked
entries to produce inf and then nan when multiplied by the zero mask. Update
segment_softmax to base the subtraction on data_for_max so masked elements
remain -inf before exp, keeping segment_sum and the denom_e guard from being
poisoned; use the existing segment_max, segment_sum, and mask handling paths to
locate the fix.

---

Nitpick comments:
In `@source/tests/common/dpmodel/test_angle_builder.py`:
- Around line 326-364: The aggregation tests in test_angle_builder only cover
fully real angle slots, so add a new padded-angle case for angle_to_edge_sum and
angle_to_node_sum using a padded angle_index with non-zero data in the padded
positions. Reuse the existing test_angle_aggregation and
test_angle_aggregation_torch_namespace patterns, but assert that padding is
ignored in both the numpy and torch-namespace paths so regressions around the
padding-mask precondition are caught.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b038075f-6809-4579-8ce1-5cf5d1d41c90

📥 Commits

Reviewing files that changed from the base of the PR and between dd38b35 and 19f0cf1.

📒 Files selected for processing (17)
  • deepmd/dpmodel/descriptor/dpa1.py
  • deepmd/dpmodel/utils/neighbor_graph/__init__.py
  • deepmd/dpmodel/utils/neighbor_graph/angles.py
  • deepmd/dpmodel/utils/neighbor_graph/env.py
  • deepmd/dpmodel/utils/neighbor_graph/graph.py
  • deepmd/dpmodel/utils/neighbor_graph/pairs.py
  • deepmd/dpmodel/utils/neighbor_graph/segment.py
  • source/tests/common/dpmodel/test_angle_builder.py
  • source/tests/common/dpmodel/test_center_edge_pairs.py
  • source/tests/common/dpmodel/test_dpa1_call_graph_block.py
  • source/tests/common/dpmodel/test_dpa1_graph_attention_parity.py
  • source/tests/common/dpmodel/test_graph_angle_cos_parity.py
  • source/tests/common/dpmodel/test_segment_softmax.py
  • source/tests/pt_expt/descriptor/test_dpa1.py
  • source/tests/pt_expt/model/test_dpa1_graph_lower.py
  • source/tests/pt_expt/model/test_linear_model.py
  • source/tests/pt_expt/utils/test_neighbor_list.py

Comment thread deepmd/dpmodel/utils/neighbor_graph/segment.py
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.97959% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.18%. Comparing base (55d7e79) to head (ad73de0).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
deepmd/dpmodel/descriptor/dpa1.py 97.95% 1 Missing ⚠️
deepmd/dpmodel/utils/neighbor_graph/env.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5717      +/-   ##
==========================================
- Coverage   81.26%   81.18%   -0.09%     
==========================================
  Files         988      990       +2     
  Lines      110876   111079     +203     
  Branches     4234     4233       -1     
==========================================
+ Hits        90103    90175      +72     
- Misses      19247    19375     +128     
- Partials     1526     1529       +3     

☔ View full report in Codecov by Harness.
📢 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.

@wanghan-iapcm wanghan-iapcm force-pushed the feat-graph-angles-prE branch from ad73de0 to dd58c02 Compare July 7, 2026 08:13
- Remove unused locals flagged by CodeQL (ai_def/ai_full in the
  ordered-superset test, ev in the angle-cos parity test); strengthen the
  ordered+include_self test to assert the default pairs are an actual
  subset of the full pair set (using the previously-dead ai_def/ai_full).
- Add masked-padding aggregation tests (CodeRabbit nitpick): pin the
  precondition that angle_to_edge_sum/angle_to_node_sum ignore guard
  angles only once data is zeroed via angle_mask, in numpy and torch.
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