Skip to content

Commit 1f7d45f

Browse files
committed
fix(api_cc): honour the multi-frame contract in standalone native-spin inference
`DeepSpinBackend::computew` takes coordinates and spins sized nframes x natoms x 3 and a cell per frame, and `DP_DeepSpinCompute2` builds exactly those. The standalone native-spin path read them as one frame, so a multi-frame call was answered from its first frame alone. The evaluation of one frame is unchanged and becomes `compute_frame`. The entry point derives the frame count from the coordinates, validates every input against it, and evaluates the frames in turn: each carries its own cell and therefore its own ghost set, which no batched forward would share.
1 parent 80a4d0b commit 1f7d45f

2 files changed

Lines changed: 136 additions & 6 deletions

File tree

source/api_cc/include/NativeSpinPTExpt.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,12 @@ class NativeSpinPTExpt : public DeepSpinBackend {
349349
const std::vector<VALUETYPE>& aparam,
350350
const bool atomic);
351351
/**
352-
* @brief Evaluate without a neighbor list: build one, then fold ghost
353-
* contributions back onto their local owners.
352+
* @brief Evaluate frames that arrive without a neighbor list.
353+
*
354+
* The inputs carry the frame count implicitly, in the length of the
355+
* coordinates; the outputs are the frames' results laid end to end. Every
356+
* frame brings its own cell and therefore its own ghost set, so they are
357+
* evaluated one at a time.
354358
**/
355359
template <typename VALUETYPE, typename ENERGYVTYPE>
356360
void compute(ENERGYVTYPE& ener,
@@ -367,6 +371,25 @@ class NativeSpinPTExpt : public DeepSpinBackend {
367371
const std::vector<VALUETYPE>& aparam,
368372
const bool atomic);
369373

374+
/**
375+
* @brief Evaluate one frame: build a neighbor list, then fold ghost
376+
* contributions back onto their local owners.
377+
**/
378+
template <typename VALUETYPE, typename ENERGYVTYPE>
379+
void compute_frame(ENERGYVTYPE& ener,
380+
std::vector<VALUETYPE>& force,
381+
std::vector<VALUETYPE>& force_mag,
382+
std::vector<VALUETYPE>& virial,
383+
std::vector<VALUETYPE>& atom_energy,
384+
std::vector<VALUETYPE>& atom_virial,
385+
const std::vector<VALUETYPE>& coord,
386+
const std::vector<VALUETYPE>& spin,
387+
const std::vector<int>& atype,
388+
const std::vector<VALUETYPE>& box,
389+
const std::vector<VALUETYPE>& fparam,
390+
const std::vector<VALUETYPE>& aparam,
391+
const bool atomic);
392+
370393
/**
371394
* @brief Run the nine-input compact canonical native-spin forward.
372395
*

source/api_cc/src/NativeSpinPTExpt.cc

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,61 @@ void validate_cartesian_input(const std::vector<VALUETYPE>& values,
8181
}
8282
}
8383

84+
/**
85+
* @brief Derive the frame count carried by a per-atom Cartesian input.
86+
*
87+
* The standalone entry points take the frame count nowhere in their
88+
* signature; it is the coordinates that hold it, as a whole multiple of the
89+
* atom count.
90+
*/
91+
template <typename VALUETYPE>
92+
int frame_count(const std::vector<VALUETYPE>& coord,
93+
const std::size_t atom_count) {
94+
const std::size_t stride = atom_count * 3;
95+
if (coord.size() % stride != 0 || coord.empty()) {
96+
throw deepmd::deepmd_exception(
97+
"coord holds " + std::to_string(coord.size()) +
98+
" values, which is not a whole number of frames of " +
99+
std::to_string(atom_count) + " atoms");
100+
}
101+
return static_cast<int>(coord.size() / stride);
102+
}
103+
104+
/**
105+
* @brief Validate an input laid out as one block of @p stride per frame.
106+
*
107+
* An empty input stands for an absent one and is left to the frame to
108+
* interpret.
109+
*/
110+
template <typename VALUETYPE>
111+
void validate_frame_blocks(const std::vector<VALUETYPE>& values,
112+
const int nframes,
113+
const std::size_t stride,
114+
const char* name) {
115+
const std::size_t expected = static_cast<std::size_t>(nframes) * stride;
116+
if (!values.empty() && values.size() != expected) {
117+
throw deepmd::deepmd_exception(
118+
std::string(name) + " holds " + std::to_string(values.size()) +
119+
" values but " + std::to_string(nframes) + " frames require " +
120+
std::to_string(expected));
121+
}
122+
}
123+
124+
/**
125+
* @brief Take the block one frame owns, or nothing when the input is absent.
126+
*/
127+
template <typename T>
128+
std::vector<T> frame_block(const std::vector<T>& values,
129+
const int frame,
130+
const std::size_t stride) {
131+
if (values.empty()) {
132+
return {};
133+
}
134+
const auto first = values.begin() + static_cast<std::ptrdiff_t>(frame) *
135+
static_cast<std::ptrdiff_t>(stride);
136+
return std::vector<T>(first, first + static_cast<std::ptrdiff_t>(stride));
137+
}
138+
84139
/**
85140
* @brief Build the frame-parameter input of the conditional graph tail.
86141
*
@@ -813,17 +868,69 @@ void NativeSpinPTExpt::compute(ENERGYVTYPE& ener,
813868
const std::vector<VALUETYPE>& fparam,
814869
const std::vector<VALUETYPE>& aparam,
815870
const bool atomic) {
871+
if (atype.empty()) {
872+
throw deepmd::deepmd_exception(
873+
"standalone native-spin inference requires at least one atom");
874+
}
875+
const std::size_t nloc = atype.size();
876+
const int nframes = frame_count(coord, nloc);
877+
validate_frame_blocks(spin, nframes, nloc * 3, "spin");
878+
validate_frame_blocks(box, nframes, 9, "box");
879+
validate_frame_blocks(fparam, nframes, static_cast<std::size_t>(dfparam),
880+
"fparam");
881+
validate_frame_blocks(aparam, nframes,
882+
nloc * static_cast<std::size_t>(daparam), "aparam");
883+
884+
ener.clear();
885+
force.clear();
886+
force_mag.clear();
887+
virial.clear();
888+
atom_energy.clear();
889+
atom_virial.clear();
890+
for (int ff = 0; ff < nframes; ++ff) {
891+
ENERGYVTYPE frame_ener;
892+
std::vector<VALUETYPE> frame_force, frame_force_mag, frame_virial,
893+
frame_atom_energy, frame_atom_virial;
894+
compute_frame(
895+
frame_ener, frame_force, frame_force_mag, frame_virial,
896+
frame_atom_energy, frame_atom_virial, frame_block(coord, ff, nloc * 3),
897+
frame_block(spin, ff, nloc * 3), atype, frame_block(box, ff, 9),
898+
frame_block(fparam, ff, static_cast<std::size_t>(dfparam)),
899+
frame_block(aparam, ff, nloc * static_cast<std::size_t>(daparam)),
900+
atomic);
901+
ener.insert(ener.end(), frame_ener.begin(), frame_ener.end());
902+
force.insert(force.end(), frame_force.begin(), frame_force.end());
903+
force_mag.insert(force_mag.end(), frame_force_mag.begin(),
904+
frame_force_mag.end());
905+
virial.insert(virial.end(), frame_virial.begin(), frame_virial.end());
906+
atom_energy.insert(atom_energy.end(), frame_atom_energy.begin(),
907+
frame_atom_energy.end());
908+
atom_virial.insert(atom_virial.end(), frame_atom_virial.begin(),
909+
frame_atom_virial.end());
910+
}
911+
}
912+
913+
template <typename VALUETYPE, typename ENERGYVTYPE>
914+
void NativeSpinPTExpt::compute_frame(ENERGYVTYPE& ener,
915+
std::vector<VALUETYPE>& force,
916+
std::vector<VALUETYPE>& force_mag,
917+
std::vector<VALUETYPE>& virial,
918+
std::vector<VALUETYPE>& atom_energy,
919+
std::vector<VALUETYPE>& atom_virial,
920+
const std::vector<VALUETYPE>& coord,
921+
const std::vector<VALUETYPE>& spin,
922+
const std::vector<int>& atype,
923+
const std::vector<VALUETYPE>& box,
924+
const std::vector<VALUETYPE>& fparam,
925+
const std::vector<VALUETYPE>& aparam,
926+
const bool atomic) {
816927
const torch::Device device = gpu_enabled ? torch::Device(torch::kCUDA, gpu_id)
817928
: torch::Device(torch::kCPU);
818929
const auto f64_options = torch::TensorOptions().dtype(torch::kFloat64);
819930
const torch::ScalarType float_type =
820931
std::is_same<VALUETYPE, float>::value ? torch::kFloat32 : torch::kFloat64;
821932
const int nloc = static_cast<int>(atype.size());
822933
const int nframes = 1;
823-
if (atype.empty()) {
824-
throw deepmd::deepmd_exception(
825-
"standalone native-spin inference requires at least one atom");
826-
}
827934
validate_cartesian_input(coord, atype.size(), "coord");
828935
validate_cartesian_input(spin, atype.size(), "spin");
829936

0 commit comments

Comments
 (0)