Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Observables sanity checks #4255

Merged
merged 7 commits into from
May 25, 2021
Merged
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
12 changes: 11 additions & 1 deletion src/core/observables/BondAngles.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@

#include <utils/Vector.hpp>

#include <cassert>
#include <cmath>
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <vector>

namespace Observables {
Expand All @@ -38,6 +41,10 @@ namespace Observables {
class BondAngles : public PidObservable {
public:
using PidObservable::PidObservable;
explicit BondAngles(std::vector<int> ids) : PidObservable(std::move(ids)) {
if (this->ids().size() < 3)
throw std::runtime_error("At least 3 particles are required");
}

std::vector<double>
evaluate(Utils::Span<std::reference_wrapper<const Particle>> particles,
Expand Down Expand Up @@ -67,7 +74,10 @@ class BondAngles : public PidObservable {
}
return res;
}
std::vector<size_t> shape() const override { return {ids().size() - 2}; }
std::vector<size_t> shape() const override {
assert(ids().size() >= 2);
return {ids().size() - 2};
}
};

} // Namespace Observables
Expand Down
12 changes: 11 additions & 1 deletion src/core/observables/BondDihedrals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
#include <utils/Span.hpp>
#include <utils/Vector.hpp>

#include <cassert>
#include <cmath>
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <vector>

namespace Observables {
Expand All @@ -44,6 +47,10 @@ namespace Observables {
class BondDihedrals : public PidObservable {
public:
using PidObservable::PidObservable;
explicit BondDihedrals(std::vector<int> ids) : PidObservable(std::move(ids)) {
if (this->ids().size() < 4)
throw std::runtime_error("At least 4 particles are required");
}

std::vector<double>
evaluate(Utils::Span<std::reference_wrapper<const Particle>> particles,
Expand All @@ -67,7 +74,10 @@ class BondDihedrals : public PidObservable {
}
return res;
}
std::vector<size_t> shape() const override { return {ids().size() - 3}; }
std::vector<size_t> shape() const override {
assert(ids().size() >= 3);
return {ids().size() - 3};
}
};

} // Namespace Observables
Expand Down
14 changes: 13 additions & 1 deletion src/core/observables/CosPersistenceAngles.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
#include <utils/Span.hpp>
#include <utils/Vector.hpp>

#include <cassert>
#include <cmath>
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <vector>

namespace Observables {
Expand All @@ -39,6 +42,12 @@ namespace Observables {
class CosPersistenceAngles : public PidObservable {
public:
using PidObservable::PidObservable;
explicit CosPersistenceAngles(std::vector<int> ids)
: PidObservable(std::move(ids)) {
if (this->ids().size() < 3)
throw std::runtime_error("At least 3 particles are required");
}

std::vector<double>
evaluate(Utils::Span<std::reference_wrapper<const Particle>> particles,
const ParticleObservables::traits<Particle> &traits) const override {
Expand All @@ -65,7 +74,10 @@ class CosPersistenceAngles : public PidObservable {

return angles;
}
std::vector<size_t> shape() const override { return {ids().size() - 2}; }
std::vector<size_t> shape() const override {
assert(ids().size() >= 2);
return {ids().size() - 2};
}
};

} // Namespace Observables
Expand Down
21 changes: 18 additions & 3 deletions src/core/observables/LBProfileObservable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
#include <utils/Vector.hpp>

#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <stdexcept>

namespace Observables {

Expand All @@ -44,6 +46,18 @@ class LBProfileObservable : public ProfileObservable {
sampling_offset{sampling_offset_x, sampling_offset_y,
sampling_offset_z},
allow_empty_bins(allow_empty_bins) {
if (sampling_delta[0] <= 0.)
throw std::domain_error("sampling_delta_x has to be > 0");
if (sampling_delta[1] <= 0.)
throw std::domain_error("sampling_delta_y has to be > 0");
if (sampling_delta[2] <= 0.)
throw std::domain_error("sampling_delta_z has to be > 0");
if (sampling_offset[0] < 0.)
throw std::domain_error("sampling_offset_x has to be >= 0");
if (sampling_offset[1] < 0.)
throw std::domain_error("sampling_offset_y has to be >= 0");
if (sampling_offset[2] < 0.)
throw std::domain_error("sampling_offset_z has to be >= 0");
calculate_sampling_positions();
}
std::array<double, 3> sampling_delta;
Expand All @@ -53,9 +67,10 @@ class LBProfileObservable : public ProfileObservable {
void calculate_sampling_positions() {
auto const lim = limits();
sampling_positions.clear();
if (sampling_delta[0] == 0 or sampling_delta[1] == 0 or
sampling_delta[2] == 0)
throw std::runtime_error("Parameter delta_x/y/z must not be zero!");
assert(sampling_delta[0] > 0. and sampling_delta[1] > 0. and
sampling_delta[2] > 0.);
assert(sampling_offset[0] >= 0. and sampling_offset[1] >= 0. and
sampling_offset[2] >= 0.);
const auto n_samples_x = static_cast<size_t>(
std::rint((lim[0].second - lim[0].first) / sampling_delta[0]));
const auto n_samples_y = static_cast<size_t>(
Expand Down
13 changes: 12 additions & 1 deletion src/core/observables/ParticleDistances.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
#include <utils/Span.hpp>
#include <utils/Vector.hpp>

#include <cassert>
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <vector>

namespace Observables {
Expand All @@ -38,6 +41,11 @@ namespace Observables {
class ParticleDistances : public PidObservable {
public:
using PidObservable::PidObservable;
explicit ParticleDistances(std::vector<int> ids)
: PidObservable(std::move(ids)) {
if (this->ids().size() < 2)
throw std::runtime_error("At least 2 particles are required");
}

std::vector<double>
evaluate(Utils::Span<std::reference_wrapper<const Particle>> particles,
Expand All @@ -51,7 +59,10 @@ class ParticleDistances : public PidObservable {
}
return res;
}
std::vector<size_t> shape() const override { return {ids().size() - 1}; }
std::vector<size_t> shape() const override {
assert(!ids().empty());
return {ids().size() - 1};
}
};

} // Namespace Observables
Expand Down
7 changes: 7 additions & 0 deletions src/core/observables/ProfileObservable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include <array>
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -54,6 +55,12 @@ class ProfileObservable : virtual public Observable {
throw std::runtime_error("max_y has to be > min_y");
if (max_z <= min_z)
throw std::runtime_error("max_z has to be > min_z");
if (n_x_bins <= 0)
throw std::domain_error("n_x_bins has to be >= 1");
if (n_y_bins <= 0)
throw std::domain_error("n_y_bins has to be >= 1");
if (n_z_bins <= 0)
throw std::domain_error("n_z_bins has to be >= 1");
}

std::vector<size_t> shape() const override {
Expand Down
8 changes: 7 additions & 1 deletion src/core/observables/RDF.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <utils/Span.hpp>

#include <cstddef>
#include <stdexcept>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -54,7 +55,12 @@ class RDF : public Observable {
explicit RDF(std::vector<int> ids1, std::vector<int> ids2, int n_r_bins,
double min_r, double max_r)
: m_ids1(std::move(ids1)), m_ids2(std::move(ids2)), min_r(min_r),
max_r(max_r), n_r_bins(n_r_bins) {}
max_r(max_r), n_r_bins(n_r_bins) {
if (max_r <= min_r)
throw std::runtime_error("max_r has to be > min_r");
if (n_r_bins <= 0)
throw std::domain_error("n_r_bins has to be >= 1");
}
std::vector<double> operator()() const final;

std::vector<int> &ids1() { return m_ids1; }
Expand Down
2 changes: 2 additions & 0 deletions src/core/unit_tests/EspressoSystemStandAlone_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,9 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory,

// check integrated trajectory; the time step is chosen
// small enough so that particles don't travel too far
#ifndef NDEBUG
auto const pos_com = Utils::Vector3d{box_center, box_center, 1.0};
#endif
auto const pids = std::vector<int>{pid1, pid2, pid3};
for (int i = 0; i < 10; ++i) {
std::unordered_map<int, Utils::Vector3d> expected;
Expand Down
20 changes: 20 additions & 0 deletions testsuite/python/observable_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ def test_ParticleDistances(self):
res_obs_chain, distances, decimal=9,
err_msg="Data did not agree for observable ParticleDistances")

# check exceptions
for i in range(2):
with self.assertRaises(RuntimeError):
espressomd.observables.ParticleDistances(ids=np.arange(i))

def test_BondAngles(self):
"""
Check BondAngles, for a particle triple and for a chain.
Expand Down Expand Up @@ -127,6 +132,11 @@ def test_BondAngles(self):
res_obs_chain, angles, decimal=9,
err_msg="Data did not agree for observable BondAngles")

# check exceptions
for i in range(3):
with self.assertRaises(RuntimeError):
espressomd.observables.BondAngles(ids=np.arange(i))

def test_BondDihedrals(self):
"""
Check BondDihedrals, for a particle quadruple and for a chain.
Expand Down Expand Up @@ -201,6 +211,11 @@ def place_particles(bl, offset):
res_obs_chain, [dih1, dih2], decimal=9,
err_msg="Data did not agree for observable BondDihedrals")

# check exceptions
for i in range(4):
with self.assertRaises(RuntimeError):
espressomd.observables.BondDihedrals(ids=np.arange(i))

def test_CosPersistenceAngles(self):
# First test: compare with python implementation
self.system.part.clear()
Expand All @@ -222,6 +237,11 @@ def test_CosPersistenceAngles(self):
expected = np.arange(1, 9) * delta_phi
np.testing.assert_allclose(obs.calculate(), np.cos(expected))

# check exceptions
for i in range(3):
with self.assertRaises(RuntimeError):
espressomd.observables.CosPersistenceAngles(ids=np.arange(i))


if __name__ == "__main__":
ut.main()
10 changes: 10 additions & 0 deletions testsuite/python/observable_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ def test_pid_profile_interface(self):
obs_bin_edges = observable.bin_edges()
np.testing.assert_array_equal(obs_bin_edges[-1, -1, -1], [7, 8, 9])

def test_exceptions(self):
params = self.kwargs.copy()
for axis in 'xyz':
with self.assertRaises(RuntimeError):
espressomd.observables.DensityProfile(
**{**params, f'min_{axis}': 100.})
with self.assertRaises(ValueError):
espressomd.observables.DensityProfile(
**{**params, f'n_{axis}_bins': 0})


if __name__ == '__main__':
ut.main()
25 changes: 16 additions & 9 deletions testsuite/python/observable_profileLB.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,6 @@ def test_velocity_profile(self):
LB_VELOCITY_PROFILE_PARAMS['n_y_bins'] *
LB_VELOCITY_PROFILE_PARAMS['n_z_bins'] * 3)

def test_error_sampling_delta_of_0(self):
lb_velocity_params_local = copy.copy(LB_VELOCITY_PROFILE_PARAMS)
lb_velocity_params_local['sampling_delta_x'] = 0.0
lb_velocity_params_local['sampling_delta_y'] = 0.0
lb_velocity_params_local['sampling_delta_z'] = 0.0
with self.assertRaises(RuntimeError):
_ = espressomd.observables.LBVelocityProfile(
**lb_velocity_params_local)

def test_error_if_no_LB(self):
self.system.actors.clear()
obs = espressomd.observables.LBVelocityProfile(
Expand All @@ -119,6 +110,22 @@ def test_error_if_empty_bin(self):
with self.assertRaises(RuntimeError):
obs.calculate()

def test_exceptions(self):
params = LB_VELOCITY_PROFILE_PARAMS.copy()
for axis in 'xyz':
with self.assertRaisesRegex(RuntimeError, f'max_{axis} has to be > min_{axis}'):
espressomd.observables.LBVelocityProfile(
**{**params, f'min_{axis}': 100.})
with self.assertRaisesRegex(ValueError, f'n_{axis}_bins has to be >= 1'):
espressomd.observables.LBVelocityProfile(
**{**params, f'n_{axis}_bins': 0})
with self.assertRaisesRegex(ValueError, f'sampling_delta_{axis} has to be > 0'):
espressomd.observables.LBVelocityProfile(
**{**params, f'sampling_delta_{axis}': 0})
with self.assertRaisesRegex(ValueError, f'sampling_offset_{axis} has to be >= 0'):
espressomd.observables.LBVelocityProfile(
**{**params, f'sampling_offset_{axis}': -1e-8})

def test_lb_profile_interface(self):
# test setters and getters
params = LB_VELOCITY_PROFILE_PARAMS.copy()
Expand Down
5 changes: 5 additions & 0 deletions testsuite/python/rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ def test_rdf_interface(self):
# check bin centers
obs_bin_centers = observable.bin_centers()
np.testing.assert_array_almost_equal(obs_bin_centers, [1.25, 1.75])
# check exceptions
with self.assertRaises(RuntimeError):
espressomd.observables.RDF(**{**params, 'min_r': 100.})
with self.assertRaises(ValueError):
espressomd.observables.RDF(**{**params, 'n_r_bins': 0})


if __name__ == "__main__":
Expand Down