Commit f1f8de8a authored by Stephen Morris's avatar Stephen Morris
Browse files

[trac641] Random number generator now throws exceptions on error

Previously when compiled without NDEBUG set it did, if a check
failed it called assert().  The tests checks for this using
ASSERT_DEATH.  However, ASSERT_DEATH leaks memory and this was
obscuring the valgrind output.  The changes here cause an exception
to be thrown (instead of a call to abort()) in the case of an error
and the unit tests now EXPECT_THROW instead of ASSERT_DEATH.
parent 6a34b543
...@@ -15,8 +15,12 @@ ...@@ -15,8 +15,12 @@
#ifndef __NSAS_RANDOM_NUMBER_GENERATOR_H #ifndef __NSAS_RANDOM_NUMBER_GENERATOR_H
#define __NSAS_RANDOM_NUMBER_GENERATOR_H #define __NSAS_RANDOM_NUMBER_GENERATOR_H
#include <algorithm>
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include <exceptions/exceptions.h>
#include <boost/random/mersenne_twister.hpp> #include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp> #include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp> #include <boost/random/uniform_real.hpp>
...@@ -25,6 +29,26 @@ ...@@ -25,6 +29,26 @@
namespace isc { namespace isc {
namespace nsas { namespace nsas {
class InvalidLimits : public Exception {
public:
InvalidLimits(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
class SumNotOne : public Exception {
public:
SumNotOne(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
class InvalidProbValue : public Exception {
public:
InvalidProbValue(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/// \brief Uniform random integer generator /// \brief Uniform random integer generator
/// ///
/// Generate uniformly distributed integers in range of [min, max] /// Generate uniformly distributed integers in range of [min, max]
...@@ -35,8 +59,17 @@ public: ...@@ -35,8 +59,17 @@ public:
/// \param min The minimum number in the range /// \param min The minimum number in the range
/// \param max The maximum number in the range /// \param max The maximum number in the range
UniformRandomIntegerGenerator(int min, int max): UniformRandomIntegerGenerator(int min, int max):
min_(min), max_(max), dist_(min, max), generator_(rng_, dist_) min_(std::min(min, max)), max_(std::max(min, max)),
dist_(min_, max_), generator_(rng_, dist_)
{ {
// To preserve the restriction of the underlying uniform_int class (and
// to retain compatibility with earlier versions of the class), we will
// abort if the minimum and maximum given are the wrong way round.
if (min > max) {
isc_throw(InvalidLimits, "minimum limit is greater than maximum "
"when initializing UniformRandomIntegerGenerator");
}
// Init with the current time // Init with the current time
rng_.seed(time(NULL)); rng_.seed(time(NULL));
} }
...@@ -73,8 +106,10 @@ public: ...@@ -73,8 +106,10 @@ public:
size_t min = 0): size_t min = 0):
dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(min) dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(min)
{ {
// The probabilities must be valid // The probabilities must be valid. Checking is quite an expensive
assert(isProbabilitiesValid(probabilities)); // operation, so is only done in a debug build.
assert(areProbabilitiesValid(probabilities));
// Calculate the partial sum of probabilities // Calculate the partial sum of probabilities
std::partial_sum(probabilities.begin(), probabilities.end(), std::partial_sum(probabilities.begin(), probabilities.end(),
std::back_inserter(cumulative_)); std::back_inserter(cumulative_));
...@@ -96,8 +131,8 @@ public: ...@@ -96,8 +131,8 @@ public:
/// \param min The minimum integer that generated /// \param min The minimum integer that generated
void reset(const std::vector<double>& probabilities, size_t min = 0) void reset(const std::vector<double>& probabilities, size_t min = 0)
{ {
// The probabilities must be valid // The probabilities must be valid.
assert(isProbabilitiesValid(probabilities)); assert(areProbabilitiesValid(probabilities));
// Reset the cumulative sum // Reset the cumulative sum
cumulative_.clear(); cumulative_.clear();
...@@ -120,16 +155,24 @@ public: ...@@ -120,16 +155,24 @@ public:
private: private:
/// \brief Check the validation of probabilities vector /// \brief Check the validation of probabilities vector
/// ///
/// The probability must be in range of [0, 1.0] and the sum must be equal to 1.0 /// The probability must be in range of [0, 1.0] and the sum must be equal
/// Empty probabilities is also valid. /// to 1.0. Empty probabilities are also valid.
bool isProbabilitiesValid(const std::vector<double>& probabilities) const ///
/// Checking the probabilities is quite an expensive operation, so it is
/// only done during a debug build (via a call through assert()). However,
/// instead of letting assert() call abort(), if this method encounters an
/// error, an exception is thrown. This makes unit testing somewhat easier.
///
/// \param probabilities Vector of probabilities.
bool areProbabilitiesValid(const std::vector<double>& probabilities) const
{ {
typedef std::vector<double>::const_iterator Iterator; typedef std::vector<double>::const_iterator Iterator;
double sum = probabilities.empty() ? 1.0 : 0.0; double sum = probabilities.empty() ? 1.0 : 0.0;
for(Iterator it = probabilities.begin(); it != probabilities.end(); ++it){ for(Iterator it = probabilities.begin(); it != probabilities.end(); ++it){
//The probability must be in [0, 1.0] //The probability must be in [0, 1.0]
if(*it < 0.0 || *it > 1.0) { if(*it < 0.0 || *it > 1.0) {
return false; isc_throw(InvalidProbValue,
"probability must be in the range 0..1");
} }
sum += *it; sum += *it;
...@@ -137,12 +180,16 @@ private: ...@@ -137,12 +180,16 @@ private:
double epsilon = 0.0001; double epsilon = 0.0001;
// The sum must be equal to 1 // The sum must be equal to 1
return std::fabs(sum - 1.0) < epsilon; if (std::fabs(sum - 1.0) >= epsilon) {
isc_throw(SumNotOne, "Sum of probabilities is not equal to 1");
}
return true;
} }
std::vector<double> cumulative_; ///< The partial sum of the probabilities std::vector<double> cumulative_; ///< Partial sum of the probabilities
boost::mt19937 rng_; ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator boost::mt19937 rng_; ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator
boost::uniform_real<> dist_; ///< Uniformly distributed real numbers boost::uniform_real<> dist_; ///< Uniformly distributed real numbers
// Shortcut typedef // Shortcut typedef
// This typedef is placed directly before its use, as the sunstudio // This typedef is placed directly before its use, as the sunstudio
......
...@@ -59,11 +59,11 @@ private: ...@@ -59,11 +59,11 @@ private:
// non-debug environment. // non-debug environment.
// Note: the death test is not supported by all platforms. We need to // Note: the death test is not supported by all platforms. We need to
// compile tests using it selectively. // compile tests using it selectively.
#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST) #if !defined(NDEBUG)
// Test of the constructor // Test of the constructor
TEST_F(UniformRandomIntegerGeneratorTest, Constructor) { TEST_F(UniformRandomIntegerGeneratorTest, Constructor) {
// The range must be min<=max // The range must be min<=max
ASSERT_DEATH(UniformRandomIntegerGenerator(3, 2), ""); ASSERT_THROW(UniformRandomIntegerGenerator(3, 2), InvalidLimits);
} }
#endif #endif
...@@ -109,30 +109,32 @@ TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) { ...@@ -109,30 +109,32 @@ TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) {
/// the tests will be failed since assert() is non-op in non-debug version. /// the tests will be failed since assert() is non-op in non-debug version.
/// The "#ifndef NDEBUG" is added to make the tests be performed only in /// The "#ifndef NDEBUG" is added to make the tests be performed only in
/// non-debug environment. /// non-debug environment.
#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST) #if !defined(NDEBUG)
//The probability must be >= 0 //The probability must be >= 0
probabilities.push_back(-0.1); probabilities.push_back(-0.1);
probabilities.push_back(1.1); probabilities.push_back(1.1);
ASSERT_DEATH(WeightedRandomIntegerGenerator gen2(probabilities), ""); ASSERT_THROW(WeightedRandomIntegerGenerator gen2(probabilities),
InvalidProbValue);
//The probability must be <= 1.0 //The probability must be <= 1.0
probabilities.clear(); probabilities.clear();
probabilities.push_back(0.1); probabilities.push_back(0.1);
probabilities.push_back(1.1); probabilities.push_back(1.1);
ASSERT_DEATH(WeightedRandomIntegerGenerator gen3(probabilities), ""); ASSERT_THROW(WeightedRandomIntegerGenerator gen3(probabilities),
InvalidProbValue);
//The sum must be equal to 1.0 //The sum must be equal to 1.0
probabilities.clear(); probabilities.clear();
probabilities.push_back(0.2); probabilities.push_back(0.2);
probabilities.push_back(0.9); probabilities.push_back(0.9);
ASSERT_DEATH(WeightedRandomIntegerGenerator gen4(probabilities), ""); ASSERT_THROW(WeightedRandomIntegerGenerator gen4(probabilities), SumNotOne);
//The sum must be equal to 1.0 //The sum must be equal to 1.0
probabilities.clear(); probabilities.clear();
probabilities.push_back(0.3); probabilities.push_back(0.3);
probabilities.push_back(0.2); probabilities.push_back(0.2);
probabilities.push_back(0.1); probabilities.push_back(0.1);
ASSERT_DEATH(WeightedRandomIntegerGenerator gen5(probabilities), ""); ASSERT_THROW(WeightedRandomIntegerGenerator gen5(probabilities), SumNotOne);
#endif #endif
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment