3939
4040import array_api_compat
4141
42+ from .csr import (
43+ build_edge_csr ,
44+ )
4245from .graph import (
4346 GraphLayout ,
4447 NeighborGraph ,
@@ -57,6 +60,9 @@ def from_dense_quartet(
5760 mapping : Array ,
5861 layout : GraphLayout | None = None ,
5962 compact : bool = True ,
63+ * ,
64+ with_csr : bool = False ,
65+ canonicalize : bool = False ,
6066) -> NeighborGraph :
6167 """Convert a legacy extended quartet into a ghost-free NeighborGraph (CONVERTER).
6268
@@ -98,6 +104,12 @@ def from_dense_quartet(
98104 ``src`` pointing at the center (in-range, masked) -- so no ``nonzero`` is
99105 used and the converter is jit/export-traceable. The masked edges contribute
100106 zero in a downstream ``segment_sum``, so the descriptor output is unchanged.
107+ with_csr
108+ Whether to construct destination/source CSR views for a consumer that
109+ requires edge-grouped reductions.
110+ canonicalize
111+ Whether to reorder every edge field into destination-major form. Implies
112+ ``with_csr=True``.
101113
102114 Returns
103115 -------
@@ -108,6 +120,7 @@ def from_dense_quartet(
108120 """
109121 if layout is None :
110122 layout = GraphLayout ()
123+ with_csr = with_csr or canonicalize
111124 xp = array_api_compat .array_namespace (extended_coord , nlist , mapping )
112125 dev = array_api_compat .device (extended_coord )
113126 nf , nloc , nsel = nlist .shape
@@ -148,11 +161,38 @@ def from_dense_quartet(
148161 edge_index = xp .astype (xp .stack ([src , dst ], axis = 0 ), xp .int64 )
149162 edge_mask = valid
150163 n_node = xp .full ((nf ,), nloc , dtype = xp .int64 , device = dev )
164+ if not with_csr :
165+ return NeighborGraph (
166+ n_node = n_node ,
167+ edge_index = edge_index ,
168+ edge_vec = edge_vec ,
169+ edge_mask = edge_mask ,
170+ )
171+ (
172+ edge_index ,
173+ edge_vec ,
174+ edge_mask ,
175+ destination_order ,
176+ destination_row_ptr ,
177+ source_row_ptr ,
178+ source_order ,
179+ ) = build_edge_csr (
180+ edge_index ,
181+ edge_vec ,
182+ edge_mask ,
183+ nf * nloc ,
184+ canonicalize = canonicalize ,
185+ )
151186 return NeighborGraph (
152187 n_node = n_node ,
153188 edge_index = edge_index ,
154189 edge_vec = edge_vec ,
155190 edge_mask = edge_mask ,
191+ destination_order = destination_order ,
192+ destination_row_ptr = destination_row_ptr ,
193+ source_row_ptr = source_row_ptr ,
194+ source_order = source_order ,
195+ destination_sorted = canonicalize ,
156196 )
157197 else :
158198 # COMPACT: drop invalid slots via nonzero (dynamic shape -> eager only,
@@ -189,11 +229,38 @@ def from_dense_quartet(
189229 edge_index , edge_vec , layout .edge_capacity , layout .min_edges
190230 )
191231 n_node = xp .full ((nf ,), nloc , dtype = xp .int64 , device = dev )
232+ if not with_csr :
233+ return NeighborGraph (
234+ n_node = n_node ,
235+ edge_index = edge_index ,
236+ edge_vec = edge_vec ,
237+ edge_mask = edge_mask ,
238+ )
239+ (
240+ edge_index ,
241+ edge_vec ,
242+ edge_mask ,
243+ destination_order ,
244+ destination_row_ptr ,
245+ source_row_ptr ,
246+ source_order ,
247+ ) = build_edge_csr (
248+ edge_index ,
249+ edge_vec ,
250+ edge_mask ,
251+ nf * nloc ,
252+ canonicalize = canonicalize ,
253+ )
192254 return NeighborGraph (
193255 n_node = n_node ,
194256 edge_index = edge_index ,
195257 edge_vec = edge_vec ,
196258 edge_mask = edge_mask ,
259+ destination_order = destination_order ,
260+ destination_row_ptr = destination_row_ptr ,
261+ source_row_ptr = source_row_ptr ,
262+ source_order = source_order ,
263+ destination_sorted = canonicalize ,
197264 )
198265
199266
@@ -203,6 +270,9 @@ def build_neighbor_graph(
203270 box : Array | None ,
204271 rcut : float ,
205272 layout : GraphLayout | None = None ,
273+ * ,
274+ with_csr : bool = False ,
275+ canonicalize : bool = False ,
206276) -> NeighborGraph :
207277 """Build a CARRY-ALL NeighborGraph DIRECTLY from coordinates (``dense`` search).
208278
@@ -236,6 +306,12 @@ def build_neighbor_graph(
236306 at non-binding ``sel``).
237307 layout
238308 edge-axis length policy; ``None`` => dynamic (torch) with ``min_edges`` guards.
309+ with_csr
310+ Whether to construct destination/source CSR views for a consumer that
311+ requires edge-grouped reductions.
312+ canonicalize
313+ Whether to reorder every edge field into destination-major form. Implies
314+ ``with_csr=True``.
239315 """
240316 from deepmd .dpmodel .utils .nlist import (
241317 extend_coord_with_ghosts ,
@@ -246,6 +322,7 @@ def build_neighbor_graph(
246322
247323 if layout is None :
248324 layout = GraphLayout ()
325+ with_csr = with_csr or canonicalize
249326 xp = array_api_compat .array_namespace (coord , atype )
250327 dev = array_api_compat .device (coord )
251328 nf , nloc = atype .shape [:2 ]
@@ -298,9 +375,36 @@ def build_neighbor_graph(
298375 edge_index , edge_vec , layout .edge_capacity , layout .min_edges
299376 )
300377 n_node = xp .full ((nf ,), nloc , dtype = xp .int64 , device = dev )
378+ if not with_csr :
379+ return NeighborGraph (
380+ n_node = n_node ,
381+ edge_index = edge_index ,
382+ edge_vec = edge_vec ,
383+ edge_mask = edge_mask ,
384+ )
385+ (
386+ edge_index ,
387+ edge_vec ,
388+ edge_mask ,
389+ destination_order ,
390+ destination_row_ptr ,
391+ source_row_ptr ,
392+ source_order ,
393+ ) = build_edge_csr (
394+ edge_index ,
395+ edge_vec ,
396+ edge_mask ,
397+ nf * nloc ,
398+ canonicalize = canonicalize ,
399+ )
301400 return NeighborGraph (
302401 n_node = n_node ,
303402 edge_index = edge_index ,
304403 edge_vec = edge_vec ,
305404 edge_mask = edge_mask ,
405+ destination_order = destination_order ,
406+ destination_row_ptr = destination_row_ptr ,
407+ source_row_ptr = source_row_ptr ,
408+ source_order = source_order ,
409+ destination_sorted = canonicalize ,
306410 )
0 commit comments