Skip to content

Commit ca7f708

Browse files
authored
fix(tf): handle multiframe DeepSpin extension (deepmodeling#5851)
Closes deepmodeling#5660. ## Summary - build TensorFlow DeepSpin physical and virtual coordinate buffers for every input frame in both direct and external-neighbor-list paths - separate extended coordinate frame strides from original-atom `aparam` strides during NULL-atom selection - copy physical force, magnetic force, atomic energy, and atomic virial outputs with explicit source/destination frame offsets - derive the atomic-energy source width from the TensorFlow tensor, since spin models emit energies only for physical atoms while C++ preserves extended virtual slots - add distinct two-frame direct and InputNlist atomic regressions for both `double` and `float` ## Why existing tests missed this All TensorFlow DeepSpin C++ tests used scalar-energy, single-frame calls. The extension helpers therefore only needed frame-zero buffers, and frame-zero copyback filled every asserted output. No test exercised the documented vector-energy overload with `nframes > 1`, so undersized extension buffers, incorrect inferred frame counts, and missing output frame offsets remained invisible. The new tests perturb both coordinates and spins in frame 2, evaluate each frame separately, and compare every batched slice against its one-frame reference. They also assert the frame references differ, preventing duplicated-frame behavior from passing. ## Validation - `ruff format .` - `ruff check .` - `clang-format --dry-run --Werror` on all changed C++ files - built `deepmd_op`, `deepmd_backend_tf`, and `runUnitTests_cc` - all 16 `TestInferDeepSpin` and `TestInferDeepSpinNopbc` typed tests passed - focused four-case two-frame matrix passed: direct/InputNlist × double/float Coding agent: Codex Codex version: codex-cli 0.144.4 Model: gpt-5.6-sol Reasoning effort: xhigh <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for processing multiple frames in spin-model calculations. * Extended coordinate and neighbor-list handling to support frame-aware inputs. * Preserved correct real-atom outputs across force, magnetic-force, energy, and virial results. * **Bug Fixes** * Improved validation of coordinate, spin, and output dimensions. * Corrected handling of virtual-atom slots and per-frame output layouts. * Fixed multi-frame behavior for standard and LMP neighbor-list calculations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: njzjz-bot <njzjz.bot@gmail.com> Co-authored-by: njzjz-bot <njzjz-bot@users.noreply.github.com>
1 parent 733f94a commit ca7f708

4 files changed

Lines changed: 302 additions & 72 deletions

File tree

source/api_cc/include/DeepSpinTF.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,16 @@ class DeepSpinTF : public DeepSpinBackend {
259259
const int nghost,
260260
const std::vector<VALUETYPE>& spin,
261261
const int numb_types,
262-
const int numb_types_spin);
262+
const int numb_types_spin,
263+
const int nframes);
263264

264265
template <typename VALUETYPE>
265266
void extend_nlist(std::vector<VALUETYPE>& extend_dcoord,
266267
std::vector<int>& extend_atype,
267268
const std::vector<VALUETYPE>& dcoord_,
268269
const std::vector<VALUETYPE>& dspin_,
269-
const std::vector<int>& datype_);
270+
const std::vector<int>& datype_,
271+
const int nframes);
270272

271273
void cum_sum(std::map<int, int>&, std::map<int, int>&);
272274

source/api_cc/src/DeepSpinTF.cc

Lines changed: 134 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,23 @@ static void run_model(
176176
for (size_t ii = 0; ii < static_cast<size_t>(nframes) * nall * 3; ++ii) {
177177
dforce[ii] = of(ii);
178178
}
179+
if (output_ae.NumElements() % nframes != 0) {
180+
throw deepmd::deepmd_exception(
181+
"TensorFlow atomic-energy output is not divisible by nframes.");
182+
}
183+
const size_t nloc_energy = output_ae.NumElements() / nframes;
184+
if (nloc_energy > nall) {
185+
throw deepmd::deepmd_exception(
186+
"TensorFlow atomic-energy output has more atoms than the extended "
187+
"DeepSpin system.");
188+
}
189+
// Spin models emit atomic energies for physical atoms only. The extended
190+
// virtual atoms sort after all physical types, so keep their slots zero and
191+
// use the actual output width as the per-frame source stride.
179192
for (int ii = 0; ii < nframes; ++ii) {
180-
for (int jj = 0; jj < nloc; ++jj) {
181-
datom_energy[ii * nall + jj] = oae(ii * nloc + jj);
193+
for (size_t jj = 0; jj < nloc_energy; ++jj) {
194+
datom_energy[static_cast<size_t>(ii) * nall + jj] =
195+
oae(static_cast<size_t>(ii) * nloc_energy + jj);
182196
}
183197
}
184198
for (size_t ii = 0; ii < static_cast<size_t>(nframes) * nall * 9; ++ii) {
@@ -614,7 +628,7 @@ void DeepSpinTF::compute(ENERGYVTYPE& dener,
614628

615629
std::vector<VALUETYPE> extend_dcoord;
616630
std::vector<int> extend_atype;
617-
extend_nlist(extend_dcoord, extend_atype, dcoord_, dspin_, datype_);
631+
extend_nlist(extend_dcoord, extend_atype, dcoord_, dspin_, datype_, nframes);
618632

619633
atommap = deepmd::AtomMap(extend_atype.begin(), extend_atype.end());
620634

@@ -645,16 +659,43 @@ void DeepSpinTF::compute(ENERGYVTYPE& dener,
645659
atommap, nframes);
646660
}
647661
}
648-
// backward force and mag.
662+
// Atomic outputs from the TensorFlow graph include the appended virtual spin
663+
// atoms. Keep a temporary copy so the public API can return the documented
664+
// real-atom layout, matching the neighbor-list overload below.
665+
std::vector<VALUETYPE> datom_energy_tmp, datom_virial_tmp;
666+
if (atomic) {
667+
datom_energy_tmp.swap(datom_energy_);
668+
datom_virial_tmp.swap(datom_virial_);
669+
datom_energy_.resize(static_cast<size_t>(nframes) * nloc);
670+
datom_virial_.resize(static_cast<size_t>(nframes) * nloc * 9);
671+
}
672+
673+
// Backward force, magnetic force, and optional atomic outputs.
649674
dforce_.resize(static_cast<size_t>(nframes) * nloc * 3);
650675
dforce_mag_.resize(static_cast<size_t>(nframes) * nloc * 3);
651-
for (int ii = 0; ii < nloc; ++ii) {
652-
for (int dd = 0; dd < 3; ++dd) {
653-
dforce_[3 * ii + dd] = dforce_tmp[3 * ii + dd];
654-
if (datype_[ii] < ntypes_spin) {
655-
dforce_mag_[3 * ii + dd] = dforce_tmp[3 * (ii + nloc) + dd];
656-
} else {
657-
dforce_mag_[3 * ii + dd] = 0.0;
676+
const size_t extend_nall = extend_atype.size();
677+
for (int ff = 0; ff < nframes; ++ff) {
678+
for (int ii = 0; ii < nloc; ++ii) {
679+
const size_t output_atom = static_cast<size_t>(ff) * nloc + ii;
680+
const size_t extended_atom = static_cast<size_t>(ff) * extend_nall + ii;
681+
if (atomic) {
682+
datom_energy_[output_atom] = datom_energy_tmp[extended_atom];
683+
}
684+
for (int dd = 0; dd < 3; ++dd) {
685+
dforce_[output_atom * 3 + dd] = dforce_tmp[extended_atom * 3 + dd];
686+
if (datype_[ii] < ntypes_spin) {
687+
const size_t virtual_atom =
688+
static_cast<size_t>(ff) * extend_nall + ii + nloc;
689+
dforce_mag_[output_atom * 3 + dd] = dforce_tmp[virtual_atom * 3 + dd];
690+
} else {
691+
dforce_mag_[output_atom * 3 + dd] = 0.0;
692+
}
693+
}
694+
if (atomic) {
695+
for (int dd = 0; dd < 9; ++dd) {
696+
datom_virial_[output_atom * 9 + dd] =
697+
datom_virial_tmp[extended_atom * 9 + dd];
698+
}
658699
}
659700
}
660701
}
@@ -747,7 +788,7 @@ void DeepSpinTF::compute(ENERGYVTYPE& dener,
747788
extend(extend_inum, extend_ilist, extend_numneigh, extend_neigh,
748789
extend_firstneigh, extend_dcoord, extend_dtype, extend_nghost,
749790
new_idx_map, old_idx_map, lmp_list, dcoord_, datype_, nghost, dspin_,
750-
ntypes, ntypes_spin);
791+
ntypes, ntypes_spin, nframes);
751792
InputNlist extend_lmp_list(extend_inum, &extend_ilist[0], &extend_numneigh[0],
752793
&extend_firstneigh[0]);
753794
extend_lmp_list.set_mask(lmp_list.mask);
@@ -820,22 +861,30 @@ void DeepSpinTF::compute(ENERGYVTYPE& dener,
820861
dforce_mag_.resize(static_cast<size_t>(nframes) * nall * 3);
821862
datom_energy_.resize(static_cast<size_t>(nframes) * nall);
822863
datom_virial_.resize(static_cast<size_t>(nframes) * nall * 9);
823-
for (int ii = 0; ii < nall; ++ii) {
824-
int new_idx = new_idx_map[ii];
825-
for (int dd = 0; dd < 3; ++dd) {
826-
dforce_[3 * ii + dd] = dforce_tmp[3 * new_idx + dd];
827-
datom_energy_[ii] = datom_energy_tmp[new_idx];
828-
829-
if (datype_[ii] < ntypes_spin && ii < nloc) {
830-
dforce_mag_[3 * ii + dd] = dforce_tmp[3 * (new_idx + nloc) + dd];
831-
} else if (datype_[ii] < ntypes_spin) {
832-
dforce_mag_[3 * ii + dd] = dforce_tmp[3 * (new_idx + nghost) + dd];
833-
} else {
834-
dforce_mag_[3 * ii + dd] = 0.0;
864+
const size_t extended_nall = fwd_map.size();
865+
for (int ff = 0; ff < nframes; ++ff) {
866+
for (int ii = 0; ii < nall; ++ii) {
867+
const int new_idx = new_idx_map[ii];
868+
const size_t output_atom = static_cast<size_t>(ff) * nall + ii;
869+
const size_t extended_atom =
870+
static_cast<size_t>(ff) * extended_nall + new_idx;
871+
datom_energy_[output_atom] = datom_energy_tmp[extended_atom];
872+
for (int dd = 0; dd < 3; ++dd) {
873+
dforce_[output_atom * 3 + dd] = dforce_tmp[extended_atom * 3 + dd];
874+
875+
if (datype_[ii] < ntypes_spin) {
876+
const int virtual_idx = new_idx + (ii < nloc ? nloc : nghost);
877+
const size_t virtual_atom =
878+
static_cast<size_t>(ff) * extended_nall + virtual_idx;
879+
dforce_mag_[output_atom * 3 + dd] = dforce_tmp[virtual_atom * 3 + dd];
880+
} else {
881+
dforce_mag_[output_atom * 3 + dd] = 0.0;
882+
}
883+
}
884+
for (int dd = 0; dd < 9; ++dd) {
885+
datom_virial_[output_atom * 9 + dd] =
886+
datom_virial_tmp[extended_atom * 9 + dd];
835887
}
836-
}
837-
for (int dd = 0; dd < 9; ++dd) {
838-
datom_virial_[ii * 9 + dd] = datom_virial_tmp[new_idx * 9 + dd];
839888
}
840889
}
841890
}
@@ -1016,7 +1065,8 @@ void DeepSpinTF::extend(int& extend_inum,
10161065
const int nghost,
10171066
const std::vector<VALUETYPE>& spin,
10181067
const int numb_types,
1019-
const int numb_types_spin) {
1068+
const int numb_types_spin,
1069+
const int nframes) {
10201070
extend_ilist.clear();
10211071
extend_numneigh.clear();
10221072
extend_neigh.clear();
@@ -1033,8 +1083,10 @@ void DeepSpinTF::extend(int& extend_inum,
10331083
get_vector<float>(spin_norm, "spin_attr/spin_norm");
10341084
}
10351085

1036-
int nall = dcoord.size() / 3;
1086+
int nall = atype.size();
10371087
int nloc = nall - nghost;
1088+
assert(static_cast<size_t>(nframes) * nall * 3 == dcoord.size());
1089+
assert(dcoord.size() == spin.size());
10381090
assert(nloc == lmp_list.inum);
10391091

10401092
// record numb_types_real and nloc_virt
@@ -1140,26 +1192,36 @@ void DeepSpinTF::extend(int& extend_inum,
11401192
}
11411193

11421194
// extend coord
1143-
extend_dcoord.resize(static_cast<size_t>(extend_nall) * 3);
1144-
for (int ii = 0; ii < nloc; ii++) {
1145-
for (int jj = 0; jj < 3; jj++) {
1146-
extend_dcoord[new_idx_map[ii] * 3 + jj] = dcoord[ii * 3 + jj];
1147-
if (atype[ii] < numb_types_spin) {
1148-
double temp_dcoord = dcoord[ii * 3 + jj] + spin[ii * 3 + jj] /
1149-
spin_norm[atype[ii]] *
1150-
virtual_len[atype[ii]];
1151-
extend_dcoord[(new_idx_map[ii] + nloc) * 3 + jj] = temp_dcoord;
1195+
extend_dcoord.resize(static_cast<size_t>(nframes) * extend_nall * 3);
1196+
for (int ff = 0; ff < nframes; ++ff) {
1197+
const size_t input_offset = static_cast<size_t>(ff) * nall * 3;
1198+
const size_t output_offset = static_cast<size_t>(ff) * extend_nall * 3;
1199+
for (int ii = 0; ii < nloc; ii++) {
1200+
for (int jj = 0; jj < 3; jj++) {
1201+
extend_dcoord[output_offset + new_idx_map[ii] * 3 + jj] =
1202+
dcoord[input_offset + ii * 3 + jj];
1203+
if (atype[ii] < numb_types_spin) {
1204+
const VALUETYPE temp_dcoord = dcoord[input_offset + ii * 3 + jj] +
1205+
spin[input_offset + ii * 3 + jj] /
1206+
spin_norm[atype[ii]] *
1207+
virtual_len[atype[ii]];
1208+
extend_dcoord[output_offset + (new_idx_map[ii] + nloc) * 3 + jj] =
1209+
temp_dcoord;
1210+
}
11521211
}
11531212
}
1154-
}
1155-
for (int ii = nloc; ii < nall; ii++) {
1156-
for (int jj = 0; jj < 3; jj++) {
1157-
extend_dcoord[new_idx_map[ii] * 3 + jj] = dcoord[ii * 3 + jj];
1158-
if (atype[ii] < numb_types_spin) {
1159-
double temp_dcoord = dcoord[ii * 3 + jj] + spin[ii * 3 + jj] /
1160-
spin_norm[atype[ii]] *
1161-
virtual_len[atype[ii]];
1162-
extend_dcoord[(new_idx_map[ii] + nghost) * 3 + jj] = temp_dcoord;
1213+
for (int ii = nloc; ii < nall; ii++) {
1214+
for (int jj = 0; jj < 3; jj++) {
1215+
extend_dcoord[output_offset + new_idx_map[ii] * 3 + jj] =
1216+
dcoord[input_offset + ii * 3 + jj];
1217+
if (atype[ii] < numb_types_spin) {
1218+
const VALUETYPE temp_dcoord = dcoord[input_offset + ii * 3 + jj] +
1219+
spin[input_offset + ii * 3 + jj] /
1220+
spin_norm[atype[ii]] *
1221+
virtual_len[atype[ii]];
1222+
extend_dcoord[output_offset + (new_idx_map[ii] + nghost) * 3 + jj] =
1223+
temp_dcoord;
1224+
}
11631225
}
11641226
}
11651227
}
@@ -1195,7 +1257,8 @@ template void DeepSpinTF::extend<double>(
11951257
const int nghost,
11961258
const std::vector<double>& spin,
11971259
const int numb_types,
1198-
const int numb_types_spin);
1260+
const int numb_types_spin,
1261+
const int nframes);
11991262

12001263
template void DeepSpinTF::extend<float>(
12011264
int& extend_inum,
@@ -1214,14 +1277,16 @@ template void DeepSpinTF::extend<float>(
12141277
const int nghost,
12151278
const std::vector<float>& spin,
12161279
const int numb_types,
1217-
const int numb_types_spin);
1280+
const int numb_types_spin,
1281+
const int nframes);
12181282

12191283
template <typename VALUETYPE>
12201284
void DeepSpinTF::extend_nlist(std::vector<VALUETYPE>& extend_dcoord,
12211285
std::vector<int>& extend_atype,
12221286
const std::vector<VALUETYPE>& dcoord_,
12231287
const std::vector<VALUETYPE>& dspin_,
1224-
const std::vector<int>& datype_) {
1288+
const std::vector<int>& datype_,
1289+
const int nframes) {
12251290
if (dtype == tensorflow::DT_DOUBLE) {
12261291
get_vector<double>(virtual_len, "spin_attr/virtual_len");
12271292
get_vector<double>(spin_norm, "spin_attr/spin_norm");
@@ -1240,20 +1305,27 @@ void DeepSpinTF::extend_nlist(std::vector<VALUETYPE>& extend_dcoord,
12401305
}
12411306
}
12421307
int extend_nall = nloc + nloc_spin;
1243-
extend_dcoord.resize(static_cast<size_t>(extend_nall) * 3);
1308+
assert(static_cast<size_t>(nframes) * nloc * 3 == dcoord_.size());
1309+
assert(dcoord_.size() == dspin_.size());
1310+
extend_dcoord.resize(static_cast<size_t>(nframes) * extend_nall * 3);
12441311
extend_atype.resize(extend_nall);
12451312
for (int ii = 0; ii < nloc; ii++) {
12461313
extend_atype[ii] = datype_[ii];
12471314
if (datype_[ii] < ntypes_spin) {
12481315
extend_atype[ii + nloc] = datype_[ii] + ntypes - ntypes_spin;
12491316
}
1250-
for (int jj = 0; jj < 3; jj++) {
1251-
extend_dcoord[ii * 3 + jj] = dcoord_[ii * 3 + jj];
1252-
if (datype_[ii] < ntypes_spin) {
1253-
extend_dcoord[(ii + nloc) * 3 + jj] =
1254-
dcoord_[ii * 3 + jj] + dspin_[ii * 3 + jj] /
1255-
spin_norm[datype_[ii]] *
1256-
virtual_len[datype_[ii]];
1317+
for (int ff = 0; ff < nframes; ++ff) {
1318+
const size_t input_offset = static_cast<size_t>(ff) * nloc * 3;
1319+
const size_t output_offset = static_cast<size_t>(ff) * extend_nall * 3;
1320+
for (int jj = 0; jj < 3; jj++) {
1321+
extend_dcoord[output_offset + ii * 3 + jj] =
1322+
dcoord_[input_offset + ii * 3 + jj];
1323+
if (datype_[ii] < ntypes_spin) {
1324+
extend_dcoord[output_offset + (ii + nloc) * 3 + jj] =
1325+
dcoord_[input_offset + ii * 3 + jj] +
1326+
dspin_[input_offset + ii * 3 + jj] / spin_norm[datype_[ii]] *
1327+
virtual_len[datype_[ii]];
1328+
}
12571329
}
12581330
}
12591331
}
@@ -1264,11 +1336,13 @@ template void DeepSpinTF::extend_nlist<double>(
12641336
std::vector<int>& extend_atype,
12651337
const std::vector<double>& dcoord_,
12661338
const std::vector<double>& dspin_,
1267-
const std::vector<int>& datype_);
1339+
const std::vector<int>& datype_,
1340+
const int nframes);
12681341

12691342
template void DeepSpinTF::extend_nlist<float>(std::vector<float>& extend_dcoord,
12701343
std::vector<int>& extend_atype,
12711344
const std::vector<float>& dcoord_,
12721345
const std::vector<float>& dspin_,
1273-
const std::vector<int>& datype_);
1346+
const std::vector<int>& datype_,
1347+
const int nframes);
12741348
#endif

source/api_cc/src/common.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,14 @@ void deepmd::select_real_atoms_coord(std::vector<VALUETYPE>& dcoord,
184184
nloc_real = nall_real - nghost_real;
185185
dcoord.resize(static_cast<size_t>(nframes) * nall_real * 3);
186186
datype.resize(nall_real);
187-
// fwd map
188-
select_map<VALUETYPE>(dcoord, dcoord_, fwd_map, 3, nframes, nall_real, nall);
187+
// Coordinate buffers can contain an extended atom set while aparam keeps
188+
// the caller's original atom stride (for example DeepSpin virtual atoms).
189+
// Infer the coordinate stride from its own frame-major buffer instead of
190+
// reusing the aparam atom count supplied through ``nall``.
191+
const int coord_nall = dcoord_.size() / static_cast<size_t>(nframes) / 3;
192+
assert(static_cast<size_t>(nframes) * coord_nall * 3 == dcoord_.size());
193+
select_map<VALUETYPE>(dcoord, dcoord_, fwd_map, 3, nframes, nall_real,
194+
coord_nall);
189195
select_map<int>(datype, datype_, fwd_map, 1);
190196
// aparam
191197
if (daparam > 0) {

0 commit comments

Comments
 (0)