Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/core/cluster_analysis/Cluster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ Cluster::fractal_dimension([[maybe_unused]] double dr) {
log_pcounts.push_back(log(static_cast<double>(subcluster_ids.size())));
log_diameters.push_back(log(current_rg * 2.0));
}
if (log_diameters.size() < 2) {
throw std::runtime_error(
"The fractal dimension of a cluster with fewer than two distinct "
"radial shells (e.g. a 2-particle cluster) is undefined. Provide a "
"larger cluster or a smaller dr.");
}
double c0, c1, cov00, cov01, cov11, sumsq;
gsl_fit_linear(log_diameters.data(), 1, log_pcounts.data(), 1,
log_pcounts.size(), &c0, &c1, &cov00, &cov01, &cov11, &sumsq);
Expand Down
21 changes: 21 additions & 0 deletions testsuite/python/cluster_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,27 @@ def test_single_cluster_analysis_lees_edwards(self):
with self.assertRaises(RuntimeError):
cluster.radius_of_gyration()

@ut.skipUnless(espressomd.has_features("GSL"),
"Fractal dimension calculation requires GSL")
def test_fractal_dimension_small_cluster(self):
# A 2-particle cluster yields fewer than two radial shells, so the
# fractal dimension is mathematically undefined. The previous behaviour
# silently returned NaN (GSL linear fit with n<2 points); the call must
# raise a clear RuntimeError instead.
self.set_two_clusters()
dc = espressomd.pair_criteria.DistanceCriterion(cut_off=0.12)
self.cs.set_params(pair_criterion=dc)
self.cs.run_for_all_pairs()

clusters = [c for _, c in self.cs.clusters]
small_cluster, = [c for c in clusters if c.size() == 2]

# dr=0. gives a single fit point, dr large enough gives zero fit points;
# both are degenerate and must raise rather than return NaN.
for dr in (0., 10.):
with self.assertRaises(RuntimeError):
small_cluster.fractal_dimension(dr=dr)

def test_single_cluster_analysis(self):
# Place particles on a line (crossing periodic boundaries)
for x in np.arange(-0.2, 0.21, 0.01):
Expand Down
Loading