|
xp = array_api_compat.array_namespace(coord, atype) |
|
nf, nloc = atype.shape |
|
# int64 for index |
|
aidx = xp.tile( |
|
xp.arange(nloc, dtype=xp.int64, device=array_api_compat.device(atype))[ |
|
xp.newaxis, : |
|
], |
|
(nf, 1), |
|
) |
|
if cell is None: |
|
nall = nloc |
|
extend_coord = coord |
|
extend_atype = atype |
|
extend_aidx = aidx |
|
else: |
|
coord = xp.reshape(coord, (nf, nloc, 3)) |
|
cell = xp.reshape(cell, (nf, 3, 3)) |
|
to_face = to_face_distance(cell) |
|
nbuff = xp.astype(xp.ceil(rcut / to_face), xp.int64) |
|
nbuff = xp.max(nbuff, axis=0) |
|
device = array_api_compat.device(coord) |
|
xi = _arange_nbuff(nbuff, 0, xp, device) |
|
yi = _arange_nbuff(nbuff, 1, xp, device) |
|
zi = _arange_nbuff(nbuff, 2, xp, device) |
|
xyz = xp.linalg.outer( |
|
xi, xp.asarray([1, 0, 0], device=array_api_compat.device(xi)) |
|
)[:, xp.newaxis, xp.newaxis, :] |
|
xyz = ( |
|
xyz |
|
+ xp.linalg.outer( |
|
yi, xp.asarray([0, 1, 0], device=array_api_compat.device(yi)) |
|
)[xp.newaxis, :, xp.newaxis, :] |
|
) |
|
xyz = ( |
|
xyz |
|
+ xp.linalg.outer( |
|
zi, xp.asarray([0, 0, 1], device=array_api_compat.device(zi)) |
|
)[xp.newaxis, xp.newaxis, :, :] |
|
) |
|
xyz = xp.reshape(xyz, (-1, 3)) |
|
xyz = xp.astype(xyz, coord.dtype) |
|
shift_idx = xp.take(xyz, xp.argsort(xp.linalg.vector_norm(xyz, axis=1)), axis=0) |
|
ns, _ = shift_idx.shape |
|
nall = ns * nloc |
|
if array_api_compat.is_jax_namespace(xp): |
|
# Avoid JAX internal errors in tensordot. |
|
shift_vec = xp.sum( |
|
shift_idx[xp.newaxis, :, :, xp.newaxis] * cell[:, xp.newaxis, :, :], |
|
axis=2, |
|
) |
|
else: |
|
# shift_vec = xp.einsum("sd,fdk->fsk", shift_idx, cell) |
|
shift_vec = xp.tensordot(shift_idx, cell, axes=([1], [1])) |
|
shift_vec = xp.permute_dims(shift_vec, (1, 0, 2)) |
|
extend_coord = coord[:, None, :, :] + shift_vec[:, :, None, :] |
|
extend_atype = xp.tile(atype[:, :, xp.newaxis], (1, ns, 1)) |
|
extend_aidx = xp.tile(aidx[:, :, xp.newaxis], (1, ns, 1)) |
|
|
|
return ( |
|
xp.reshape(extend_coord, (nf, nall * 3)), |
|
xp.reshape(extend_atype, (nf, nall)), |
|
xp.reshape(extend_aidx, (nf, nall)), |
|
) |
Found during a Codex global scan of
deepmodeling/deepmd-kitat commit73de44b1f94471b2e3bdb6b11f57b34d7bc791bb.Problem
NeighborStatOP.call()computes the minimum pair distance and per-center neighbor counts without consistently masking virtual atoms (atype < 0).Evidence:
deepmd-kit/deepmd/dpmodel/utils/nlist.py
Lines 89 to 117 in 73de44b
extend_coord_with_ghostspreserves negative atom types in the extended atom-type array:deepmd-kit/deepmd/dpmodel/utils/nlist.py
Lines 438 to 500 in 73de44b
NeighborStatOP.call()removes only self pairs before computingmin_rr2; it does not mask pairs where the center or neighbor atom is virtual:deepmd-kit/deepmd/dpmodel/utils/neighbor_stat.py
Lines 81 to 95 in 73de44b
deepmd-kit/deepmd/dpmodel/utils/neighbor_stat.py
Lines 96 to 108 in 73de44b
A minimal external run with one real atom and one overlapping virtual atom returned
min_rr2 == 0and nonzero neighbor counts, even though the virtual atom should not contribute to statistics.Impact
Virtual atoms can drive the reported minimum distance to zero and can inflate
max_nneithrough virtual center rows. These statistics feed automatic neighbor selection and environment-matrix range checks, so they can produce overly conservative selections or false close-contact diagnostics.Suggested Fix
Apply a real-center and real-neighbor mask before both
min_rr2and neighbor counting. Add regression tests with overlapping real/virtual atoms and with a virtual center near real atoms, for bothmixed_types=TrueandFalse.