Skip to content

Commit 84cc1d9

Browse files
RudolfWeeberjngradclaude
authored
analysis: raise instead of NaN for empty particle types (#5372)
Mass-related analysis functions now throw an exception when no particle is found. Co-authored-by: Jean-Noël Grad <jgrad@icp.uni-stuttgart.de> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d965158 commit 84cc1d9

3 files changed

Lines changed: 54 additions & 12 deletions

File tree

src/core/analysis/statistics.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include <functional>
4848
#include <limits>
4949
#include <numbers>
50+
#include <stdexcept>
5051
#include <tuple>
5152
#include <utility>
5253
#include <vector>
@@ -173,6 +174,13 @@ Utils::Vector3d center_of_mass(System::System const &system, int p_type) {
173174
double mass = 1.; // placeholder value to avoid division by zero
174175
boost::mpi::reduce(::comm_cart, local_com, com, std::plus<>(), 0);
175176
boost::mpi::reduce(::comm_cart, local_mass, mass, std::plus<>(), 0);
177+
auto invalid_mass = (mass == 0.);
178+
boost::mpi::broadcast(::comm_cart, invalid_mass, 0);
179+
if (invalid_mass) {
180+
throw std::runtime_error(
181+
"Cannot calculate the center of mass: no particle with non-zero mass "
182+
"of the given type(s) was found");
183+
}
176184
return com / mass;
177185
}
178186

@@ -200,7 +208,11 @@ Utils::Vector9d gyration_tensor(System::System const &system,
200208

201209
Utils::Vector9d mat{};
202210
if (::comm_cart.rank() == 0) {
203-
assert(not buf_pos.empty());
211+
if (buf_pos.empty()) {
212+
throw std::runtime_error(
213+
"Cannot calculate the gyration tensor: no particle of the given "
214+
"type(s) was found");
215+
}
204216
auto const center =
205217
std::accumulate(buf_pos.begin(), buf_pos.end(), Utils::Vector3d{}) /
206218
static_cast<double>(buf_pos.size());

src/script_interface/analysis/Analysis.cpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,13 @@ Variant Analysis::do_call_method(std::string const &name,
167167
}
168168
if (name == "center_of_mass") {
169169
auto const p_type = get_value<int>(parameters, "p_type");
170-
context()->parallel_try_catch([&]() { check_particle_type(p_type); });
171-
auto const local = center_of_mass(get_system(), p_type);
172-
return mpi_reduce_sum(context()->get_comm(), local).as_vector();
170+
Variant result;
171+
context()->parallel_try_catch([&]() {
172+
check_particle_type(p_type);
173+
auto const local = center_of_mass(get_system(), p_type);
174+
result = mpi_reduce_sum(context()->get_comm(), local).as_vector();
175+
});
176+
return result;
173177
}
174178
if (name == "angular_momentum") {
175179
auto const p_type = get_value<int>(parameters, "p_type");
@@ -216,17 +220,25 @@ Variant Analysis::do_call_method(std::string const &name,
216220
}
217221
if (name == "gyration_tensor") {
218222
auto const p_types = get_value<std::vector<int>>(parameters, "p_types");
219-
for (auto const p_type : p_types) {
220-
context()->parallel_try_catch([&]() { check_particle_type(p_type); });
221-
}
222-
auto const mat = gyration_tensor(get_system(), p_types);
223-
return std::vector<double>(mat.begin(), mat.end());
223+
Variant result;
224+
context()->parallel_try_catch([&]() {
225+
for (auto const p_type : p_types) {
226+
check_particle_type(p_type);
227+
}
228+
auto const mat = gyration_tensor(get_system(), p_types);
229+
result = std::vector<double>(mat.begin(), mat.end());
230+
});
231+
return result;
224232
}
225233
if (name == "moment_of_inertia_matrix") {
226234
auto const p_type = get_value<int>(parameters, "p_type");
227-
context()->parallel_try_catch([&]() { check_particle_type(p_type); });
228-
auto const local = moment_of_inertia_matrix(get_system(), p_type);
229-
return mpi_reduce_sum(context()->get_comm(), local).as_vector();
235+
Variant result;
236+
context()->parallel_try_catch([&]() {
237+
check_particle_type(p_type);
238+
auto const local = moment_of_inertia_matrix(get_system(), p_type);
239+
result = mpi_reduce_sum(context()->get_comm(), local).as_vector();
240+
});
241+
return result;
230242
}
231243
if (name == "structure_factor") {
232244
auto const order = get_value<int>(parameters, "sf_order");

testsuite/python/analyze_mass_related.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,24 @@ def test_gyration_radius(self):
148148
self.system.analysis.calc_rg(chain_start=0, number_of_chains=1,
149149
chain_length=len(self.system.part))
150150

151+
def test_empty_but_seen_type(self):
152+
"""
153+
A particle type that was seen (so it passes the type-range check)
154+
but currently has no particles must not divide by zero in mass-related
155+
analysis routines.
156+
"""
157+
# create a new type, make it "seen", then make it empty
158+
empty_type = max(self.system.part.all().type) + 17
159+
p = self.system.part.add(pos=[1., 1., 1.], type=empty_type)
160+
p.remove()
161+
162+
with self.assertRaisesRegex(Exception, "no particle with non-zero mass of the given type"):
163+
self.system.analysis.center_of_mass(p_type=empty_type)
164+
with self.assertRaisesRegex(Exception, "no particle of the given type"):
165+
self.system.analysis.gyration_tensor(p_type=empty_type)
166+
with self.assertRaisesRegex(Exception, "no particle with non-zero mass of the given type"):
167+
self.system.analysis.moment_of_inertia_matrix(p_type=empty_type)
168+
151169

152170
if __name__ == "__main__":
153171
ut.main()

0 commit comments

Comments
 (0)