Skip to content

Commit 27ec5cb

Browse files
author
Han Wang
committed
feat(dpmodel): angle->edge/node segment aggregation
1 parent f7dc3be commit 27ec5cb

3 files changed

Lines changed: 98 additions & 0 deletions

File tree

deepmd/dpmodel/utils/neighbor_graph/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"""
1111

1212
from .angles import (
13+
angle_to_edge_sum,
14+
angle_to_node_sum,
1315
attach_angles,
1416
build_angle_index,
1517
)
@@ -50,6 +52,8 @@
5052
__all__ = [
5153
"GraphLayout",
5254
"NeighborGraph",
55+
"angle_to_edge_sum",
56+
"angle_to_node_sum",
5357
"attach_angles",
5458
"build_angle_index",
5559
"build_neighbor_graph",

deepmd/dpmodel/utils/neighbor_graph/angles.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from .graph import GraphLayout, NeighborGraph, pad_and_guard_angles
2121
from .pairs import center_edge_pairs
22+
from .segment import segment_sum
2223

2324

2425
def build_angle_index(
@@ -138,3 +139,49 @@ def attach_angles(
138139
layout=layout,
139140
)
140141
return dataclasses.replace(graph, angle_index=ai, angle_mask=am)
142+
143+
144+
def angle_to_edge_sum(data: Array, angle_index: Array, num_edges: int) -> Array:
145+
"""Aggregate per-angle data to the angle's query edge (edge_a).
146+
147+
Parameters
148+
----------
149+
data : Array
150+
Shape (A,) or (A, ...) per-angle data to aggregate.
151+
angle_index : Array
152+
Shape (2, A) angle index pairs into edges.
153+
num_edges : int
154+
Total number of edges (E).
155+
156+
Returns
157+
-------
158+
Array
159+
Shape (E,) or (E, ...) aggregated per-edge data.
160+
"""
161+
return segment_sum(data, angle_index[0, :], num_edges)
162+
163+
164+
def angle_to_node_sum(
165+
data: Array, angle_index: Array, edge_index: Array, num_nodes: int
166+
) -> Array:
167+
"""Aggregate per-angle data to the shared center (dst of edge_a).
168+
169+
Parameters
170+
----------
171+
data : Array
172+
Shape (A,) or (A, ...) per-angle data to aggregate.
173+
angle_index : Array
174+
Shape (2, A) angle index pairs into edges.
175+
edge_index : Array
176+
Shape (2, E) edge indices [src, dst].
177+
num_nodes : int
178+
Total number of nodes (N).
179+
180+
Returns
181+
-------
182+
Array
183+
Shape (N,) or (N, ...) aggregated per-node data.
184+
"""
185+
xp = array_api_compat.array_namespace(data)
186+
center = xp.take(edge_index[1, :], angle_index[0, :], axis=0)
187+
return segment_sum(data, center, num_nodes)

source/tests/common/dpmodel/test_angle_builder.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from deepmd.dpmodel.utils.neighbor_graph import (
55
GraphLayout,
6+
angle_to_edge_sum,
7+
angle_to_node_sum,
68
attach_angles,
79
build_angle_index,
810
build_neighbor_graph,
@@ -311,3 +313,48 @@ def test_attach_angles_with_layout_node_capacity():
311313
if ng3.angle_mask[p]
312314
}
313315
assert got == ref
316+
317+
318+
# ---------------------------------------------------------------------------
319+
# Task 4: angle aggregation (angle_to_edge_sum / angle_to_node_sum)
320+
# ---------------------------------------------------------------------------
321+
322+
323+
def test_angle_aggregation():
324+
"""Test angle->edge and angle->node aggregation."""
325+
# edges: dst=[0,0]; angles: (a=0,b=0),(a=0,b=1),(a=1,b=0)
326+
edge_index = np.array([[5, 5], [0, 0]], dtype=np.int64)
327+
angle_index = np.array([[0, 0, 1], [0, 1, 0]], dtype=np.int64)
328+
data = np.array([1.0, 2.0, 4.0]) # per-angle
329+
# angle->edge (group by edge_a): edge0 gets angles 0,1 => 3; edge1 gets angle2 => 4
330+
e = angle_to_edge_sum(data, angle_index, 2)
331+
np.testing.assert_allclose(e, [3.0, 4.0])
332+
# angle->node (center of edge_a): all 3 angles share center 0 => 7
333+
n = angle_to_node_sum(data, angle_index, edge_index, 1)
334+
np.testing.assert_allclose(n, [7.0])
335+
336+
337+
def test_angle_aggregation_torch_namespace():
338+
"""Step 4b: torch-namespace smoke test for angle aggregation."""
339+
import torch
340+
341+
# edges: dst=[0,0]; angles: (a=0,b=0),(a=0,b=1),(a=1,b=0)
342+
edge_index = np.array([[5, 5], [0, 0]], dtype=np.int64)
343+
angle_index = np.array([[0, 0, 1], [0, 1, 0]], dtype=np.int64)
344+
data = np.array([1.0, 2.0, 4.0]) # per-angle
345+
346+
# numpy reference
347+
e_np = angle_to_edge_sum(data, angle_index, 2)
348+
n_np = angle_to_node_sum(data, angle_index, edge_index, 1)
349+
350+
# torch version
351+
t_edge_index = torch.from_numpy(edge_index)
352+
t_angle_index = torch.from_numpy(angle_index)
353+
t_data = torch.from_numpy(data)
354+
355+
e_t = angle_to_edge_sum(t_data, t_angle_index, 2)
356+
n_t = angle_to_node_sum(t_data, t_angle_index, t_edge_index, 1)
357+
358+
# compare
359+
np.testing.assert_allclose(np.asarray(e_t), e_np)
360+
np.testing.assert_allclose(np.asarray(n_t), n_np)

0 commit comments

Comments
 (0)