// (C) Copyright John Maddock 2006. | |
// Use, modification and distribution are subject to the | |
// Boost Software License, Version 1.0. (See accompanying file | |
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
#ifndef BOOST_MATH_TOOLS_TEST_DATA_HPP | |
#define BOOST_MATH_TOOLS_TEST_DATA_HPP | |
#ifdef _MSC_VER | |
#pragma once | |
#endif | |
#include <boost/math/tools/config.hpp> | |
#include <boost/assert.hpp> | |
#ifdef BOOST_MSVC | |
# pragma warning(push) | |
# pragma warning(disable: 4127 4701 4512) | |
# pragma warning(disable: 4130) // '==' : logical operation on address of string constant. | |
#endif | |
#include <boost/algorithm/string/trim.hpp> | |
#include <boost/lexical_cast.hpp> | |
#ifdef BOOST_MSVC | |
#pragma warning(pop) | |
#endif | |
#include <boost/type_traits/is_floating_point.hpp> | |
#include <boost/type_traits/is_convertible.hpp> | |
#include <boost/type_traits/integral_constant.hpp> | |
#include <boost/tr1/random.hpp> | |
#include <boost/math/tools/tuple.hpp> | |
#include <boost/math/tools/real_cast.hpp> | |
#include <set> | |
#include <vector> | |
#include <iostream> | |
#ifdef BOOST_MSVC | |
# pragma warning(push) | |
# pragma warning(disable: 4130) // '==' : logical operation on address of string constant. | |
// Used as a warning with BOOST_ASSERT | |
#endif | |
namespace boost{ namespace math{ namespace tools{ | |
enum parameter_type | |
{ | |
random_in_range = 0, | |
periodic_in_range = 1, | |
power_series = 2, | |
dummy_param = 0x80 | |
}; | |
parameter_type operator | (parameter_type a, parameter_type b) | |
{ | |
return static_cast<parameter_type>((int)a|(int)b); | |
} | |
parameter_type& operator |= (parameter_type& a, parameter_type b) | |
{ | |
a = static_cast<parameter_type>(a|b); | |
return a; | |
} | |
// | |
// If type == random_in_range then | |
// z1 and r2 are the endpoints of the half open range and n1 is the number of points. | |
// | |
// If type == periodic_in_range then | |
// z1 and r2 are the endpoints of the half open range and n1 is the number of points. | |
// | |
// If type == power_series then | |
// n1 and n2 are the endpoints of the exponents (closed range) and z1 is the basis. | |
// | |
// If type & dummy_param then this data is ignored and not stored in the output, it | |
// is passed to the generator function however which can do with it as it sees fit. | |
// | |
template <class T> | |
struct parameter_info | |
{ | |
parameter_type type; | |
T z1, z2; | |
int n1, n2; | |
}; | |
template <class T> | |
inline parameter_info<T> make_random_param(T start_range, T end_range, int n_points) | |
{ | |
parameter_info<T> result = { random_in_range, start_range, end_range, n_points, 0 }; | |
return result; | |
} | |
template <class T> | |
inline parameter_info<T> make_periodic_param(T start_range, T end_range, int n_points) | |
{ | |
parameter_info<T> result = { periodic_in_range, start_range, end_range, n_points, 0 }; | |
return result; | |
} | |
template <class T> | |
inline parameter_info<T> make_power_param(T basis, int start_exponent, int end_exponent) | |
{ | |
parameter_info<T> result = { power_series, basis, 0, start_exponent, end_exponent }; | |
return result; | |
} | |
namespace detail{ | |
template <class Seq, class Item, int N> | |
inline void unpack_and_append_tuple(Seq& s, | |
const Item& data, | |
const boost::integral_constant<int, N>&, | |
const boost::false_type&) | |
{ | |
// termimation condition nothing to do here | |
} | |
template <class Seq, class Item, int N> | |
inline void unpack_and_append_tuple(Seq& s, | |
const Item& data, | |
const boost::integral_constant<int, N>&, | |
const boost::true_type&) | |
{ | |
// extract the N'th element, append, and recurse: | |
typedef typename Seq::value_type value_type; | |
value_type val = boost::math::get<N>(data); | |
s.push_back(val); | |
typedef boost::integral_constant<int, N+1> next_value; | |
typedef boost::integral_constant<bool, (boost::math::tuple_size<Item>::value > N+1)> terminate; | |
unpack_and_append_tuple(s, data, next_value(), terminate()); | |
} | |
template <class Seq, class Item> | |
inline void unpack_and_append(Seq& s, const Item& data, const boost::true_type&) | |
{ | |
s.push_back(data); | |
} | |
template <class Seq, class Item> | |
inline void unpack_and_append(Seq& s, const Item& data, const boost::false_type&) | |
{ | |
// Item had better be a tuple-like type or we've had it!!!! | |
typedef boost::integral_constant<int, 0> next_value; | |
typedef boost::integral_constant<bool, (boost::math::tuple_size<Item>::value > 0)> terminate; | |
unpack_and_append_tuple(s, data, next_value(), terminate()); | |
} | |
template <class Seq, class Item> | |
inline void unpack_and_append(Seq& s, const Item& data) | |
{ | |
typedef typename Seq::value_type value_type; | |
unpack_and_append(s, data, ::boost::is_convertible<Item, value_type>()); | |
} | |
} // detail | |
template <class T> | |
class test_data | |
{ | |
public: | |
typedef std::vector<T> row_type; | |
typedef row_type value_type; | |
private: | |
typedef std::set<row_type> container_type; | |
public: | |
typedef typename container_type::reference reference; | |
typedef typename container_type::const_reference const_reference; | |
typedef typename container_type::iterator iterator; | |
typedef typename container_type::const_iterator const_iterator; | |
typedef typename container_type::difference_type difference_type; | |
typedef typename container_type::size_type size_type; | |
// creation: | |
test_data(){} | |
template <class F> | |
test_data(F func, const parameter_info<T>& arg1) | |
{ | |
insert(func, arg1); | |
} | |
// insertion: | |
template <class F> | |
test_data& insert(F func, const parameter_info<T>& arg1) | |
{ | |
// generate data for single argument functor F | |
typedef typename std::set<T>::const_iterator it_type; | |
std::set<T> points; | |
create_test_points(points, arg1); | |
it_type a = points.begin(); | |
it_type b = points.end(); | |
row_type row; | |
while(a != b) | |
{ | |
if((arg1.type & dummy_param) == 0) | |
row.push_back(*a); | |
try{ | |
// domain_error exceptions from func are swallowed | |
// and this data point is ignored: | |
boost::math::tools::detail::unpack_and_append(row, func(*a)); | |
m_data.insert(row); | |
} | |
catch(const std::domain_error&){} | |
row.clear(); | |
++a; | |
} | |
return *this; | |
} | |
template <class F> | |
test_data& insert(F func, const parameter_info<T>& arg1, const parameter_info<T>& arg2) | |
{ | |
// generate data for 2-argument functor F | |
typedef typename std::set<T>::const_iterator it_type; | |
std::set<T> points1, points2; | |
create_test_points(points1, arg1); | |
create_test_points(points2, arg2); | |
it_type a = points1.begin(); | |
it_type b = points1.end(); | |
row_type row; | |
while(a != b) | |
{ | |
it_type c = points2.begin(); | |
it_type d = points2.end(); | |
while(c != d) | |
{ | |
if((arg1.type & dummy_param) == 0) | |
row.push_back(*a); | |
if((arg2.type & dummy_param) == 0) | |
row.push_back(*c); | |
try{ | |
// domain_error exceptions from func are swallowed | |
// and this data point is ignored: | |
detail::unpack_and_append(row, func(*a, *c)); | |
m_data.insert(row); | |
} | |
catch(const std::domain_error&){} | |
row.clear(); | |
++c; | |
} | |
++a; | |
} | |
return *this; | |
} | |
template <class F> | |
test_data& insert(F func, const parameter_info<T>& arg1, const parameter_info<T>& arg2, const parameter_info<T>& arg3) | |
{ | |
// generate data for 3-argument functor F | |
typedef typename std::set<T>::const_iterator it_type; | |
std::set<T> points1, points2, points3; | |
create_test_points(points1, arg1); | |
create_test_points(points2, arg2); | |
create_test_points(points3, arg3); | |
it_type a = points1.begin(); | |
it_type b = points1.end(); | |
row_type row; | |
while(a != b) | |
{ | |
it_type c = points2.begin(); | |
it_type d = points2.end(); | |
while(c != d) | |
{ | |
it_type e = points3.begin(); | |
it_type f = points3.end(); | |
while(e != f) | |
{ | |
if((arg1.type & dummy_param) == 0) | |
row.push_back(*a); | |
if((arg2.type & dummy_param) == 0) | |
row.push_back(*c); | |
if((arg3.type & dummy_param) == 0) | |
row.push_back(*e); | |
try{ | |
// domain_error exceptions from func are swallowed | |
// and this data point is ignored: | |
detail::unpack_and_append(row, func(*a, *c, *e)); | |
m_data.insert(row); | |
} | |
catch(const std::domain_error&){} | |
row.clear(); | |
++e; | |
} | |
++c; | |
} | |
++a; | |
} | |
return *this; | |
} | |
void clear(){ m_data.clear(); } | |
// access: | |
iterator begin() { return m_data.begin(); } | |
iterator end() { return m_data.end(); } | |
const_iterator begin()const { return m_data.begin(); } | |
const_iterator end()const { return m_data.end(); } | |
bool operator==(const test_data& d)const{ return m_data == d.m_data; } | |
bool operator!=(const test_data& d)const{ return m_data != d.m_data; } | |
void swap(test_data& other){ m_data.swap(other.m_data); } | |
size_type size()const{ return m_data.size(); } | |
size_type max_size()const{ return m_data.max_size(); } | |
bool empty()const{ return m_data.empty(); } | |
bool operator < (const test_data& dat)const{ return m_data < dat.m_data; } | |
bool operator <= (const test_data& dat)const{ return m_data <= dat.m_data; } | |
bool operator > (const test_data& dat)const{ return m_data > dat.m_data; } | |
bool operator >= (const test_data& dat)const{ return m_data >= dat.m_data; } | |
private: | |
void create_test_points(std::set<T>& points, const parameter_info<T>& arg1); | |
std::set<row_type> m_data; | |
static float extern_val; | |
static float truncate_to_float(float const * pf); | |
static float truncate_to_float(float c){ return truncate_to_float(&c); } | |
}; | |
// | |
// This code exists to bemuse the compiler's optimizer and force a | |
// truncation to float-precision only: | |
// | |
template <class T> | |
inline float test_data<T>::truncate_to_float(float const * pf) | |
{ | |
BOOST_MATH_STD_USING | |
int expon; | |
float f = floor(ldexp(frexp(*pf, &expon), 22)); | |
f = ldexp(f, expon - 22); | |
return f; | |
//extern_val = *pf; | |
//return *pf; | |
} | |
template <class T> | |
float test_data<T>::extern_val = 0; | |
template <class T> | |
void test_data<T>::create_test_points(std::set<T>& points, const parameter_info<T>& arg1) | |
{ | |
BOOST_MATH_STD_USING | |
// | |
// Generate a set of test points as requested, try and generate points | |
// at only float precision: otherwise when testing float versions of functions | |
// there will be a rounding error in our input values which throws off the results | |
// (Garbage in garbage out etc). | |
// | |
switch(arg1.type & 0x7F) | |
{ | |
case random_in_range: | |
{ | |
BOOST_ASSERT(arg1.z1 < arg1.z2); | |
BOOST_ASSERT(arg1.n1 > 0); | |
typedef float random_type; | |
std::tr1::mt19937 rnd; | |
std::tr1::uniform_real<random_type> ur_a(real_cast<random_type>(arg1.z1), real_cast<random_type>(arg1.z2)); | |
std::tr1::variate_generator<std::tr1::mt19937, std::tr1::uniform_real<random_type> > gen(rnd, ur_a); | |
for(int i = 0; i < arg1.n1; ++i) | |
{ | |
random_type r = gen(); | |
points.insert(truncate_to_float(r)); | |
} | |
} | |
break; | |
case periodic_in_range: | |
{ | |
BOOST_ASSERT(arg1.z1 < arg1.z2); | |
BOOST_ASSERT(arg1.n1 > 0); | |
float interval = real_cast<float>((arg1.z2 - arg1.z1) / arg1.n1); | |
T val = arg1.z1; | |
while(val < arg1.z2) | |
{ | |
points.insert(truncate_to_float(real_cast<float>(val))); | |
val += interval; | |
} | |
} | |
break; | |
case power_series: | |
{ | |
BOOST_ASSERT(arg1.n1 < arg1.n2); | |
typedef float random_type; | |
typedef typename boost::mpl::if_< | |
::boost::is_floating_point<T>, | |
T, long double>::type power_type; | |
std::tr1::mt19937 rnd; | |
std::tr1::uniform_real<random_type> ur_a(1.0, 2.0); | |
std::tr1::variate_generator<std::tr1::mt19937, std::tr1::uniform_real<random_type> > gen(rnd, ur_a); | |
for(int power = arg1.n1; power <= arg1.n2; ++power) | |
{ | |
random_type r = gen(); | |
power_type p = ldexp(static_cast<power_type>(r), power); | |
points.insert(truncate_to_float(real_cast<float>(arg1.z1 + p))); | |
} | |
} | |
break; | |
default: | |
BOOST_ASSERT(0 == "Invalid parameter_info object"); | |
// Assert will fail if get here. | |
// Triggers warning 4130) // '==' : logical operation on address of string constant. | |
} | |
} | |
// | |
// Prompt a user for information on a parameter range: | |
// | |
template <class T> | |
bool get_user_parameter_info(parameter_info<T>& info, const char* param_name) | |
{ | |
#ifdef BOOST_MSVC | |
# pragma warning(push) | |
# pragma warning(disable: 4127) | |
#endif | |
std::string line; | |
do{ | |
std::cout << "What kind of distribution do you require for parameter " << param_name << "?\n" | |
"Choices are:\n" | |
" r Random values in a half open range\n" | |
" p Evenly spaced periodic values in a half open range\n" | |
" e Exponential power series at a particular point: a + 2^b for some range of b\n" | |
"[Default=r]"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "r") | |
{ | |
info.type = random_in_range; | |
break; | |
} | |
else if(line == "p") | |
{ | |
info.type = periodic_in_range; | |
break; | |
} | |
else if(line == "e") | |
{ | |
info.type = power_series; | |
break; | |
} | |
else if(line == "") | |
{ | |
info.type = random_in_range; | |
break; | |
} | |
// | |
// Ooops, not a valid input.... | |
// | |
std::cout << "Sorry don't recognise \"" << line << "\" as a valid input\n" | |
"do you want to try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "n") | |
return false; | |
else if(line == "y") | |
continue; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
}while(true); | |
switch(info.type & ~dummy_param) | |
{ | |
case random_in_range: | |
case periodic_in_range: | |
// get start and end points of range: | |
do{ | |
std::cout << "Data will be in the half open range a <= x < b,\n" | |
"enter value for the start point fo the range [default=0]:"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "") | |
{ | |
info.z1 = 0; | |
break; | |
} | |
try{ | |
info.z1 = boost::lexical_cast<T>(line); | |
break; | |
} | |
catch(const boost::bad_lexical_cast&) | |
{ | |
std::cout << "Sorry, that was not valid input, try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
}while(true); | |
do{ | |
std::cout << "Enter value for the end point fo the range [default=1]:"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "") | |
{ | |
info.z2 = 1; | |
} | |
else | |
{ | |
try | |
{ | |
info.z2 = boost::lexical_cast<T>(line); | |
} | |
catch(const boost::bad_lexical_cast&) | |
{ | |
std::cout << "Sorry, that was not valid input, try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
} | |
if(info.z1 >= info.z2) | |
{ | |
std::cout << "The end point of the range was <= the start point\n" | |
"try a different value for the endpoint [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
break; | |
}while(true); | |
do{ | |
// get the number of points: | |
std::cout << "How many data points do you want?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
try{ | |
info.n1 = boost::lexical_cast<int>(line); | |
info.n2 = 0; | |
if(info.n1 <= 0) | |
{ | |
std::cout << "The number of points should be > 0\n" | |
"try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
break; | |
} | |
catch(const boost::bad_lexical_cast&) | |
{ | |
std::cout << "Sorry, that was not valid input, try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
}while(true); | |
break; | |
case power_series: | |
// get start and end points of range: | |
info.z2 = 0; | |
do{ | |
std::cout << "Data will be in the form a + r*2^b\n" | |
"for random value r,\n" | |
"enter value for the point a [default=0]:"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "") | |
{ | |
info.z1 = 0; | |
break; | |
} | |
try{ | |
info.z1 = boost::lexical_cast<T>(line); | |
break; | |
} | |
catch(const boost::bad_lexical_cast&) | |
{ | |
std::cout << "Sorry, that was not valid input, try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
}while(true); | |
do{ | |
std::cout << "Data will be in the form a + r*2^b\n" | |
"for random value r,\n" | |
"enter value for the starting exponent b:"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
try{ | |
info.n1 = boost::lexical_cast<int>(line); | |
break; | |
} | |
catch(const boost::bad_lexical_cast&) | |
{ | |
std::cout << "Sorry, that was not valid input, try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
}while(true); | |
do{ | |
std::cout << "Data will be in the form a + r*2^b\n" | |
"for random value r,\n" | |
"enter value for the ending exponent b:"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
try{ | |
info.n2 = boost::lexical_cast<int>(line); | |
break; | |
} | |
catch(const boost::bad_lexical_cast&) | |
{ | |
std::cout << "Sorry, that was not valid input, try again [y/n]?"; | |
std::getline(std::cin, line); | |
boost::algorithm::trim(line); | |
if(line == "y") | |
continue; | |
if(line == "n") | |
return false; | |
std::cout << "Sorry don't recognise that either, giving up...\n\n"; | |
return false; | |
} | |
}while(true); | |
break; | |
default: | |
BOOST_ASSERT(0); // should never get here!! | |
} | |
return true; | |
#ifdef BOOST_MSVC | |
# pragma warning(pop) | |
#endif | |
} | |
template <class charT, class traits, class T> | |
inline std::basic_ostream<charT, traits>& write_csv(std::basic_ostream<charT, traits>& os, | |
const test_data<T>& data) | |
{ | |
const charT defarg[] = { ',', ' ', '\0' }; | |
return write_csv(os, data, defarg); | |
} | |
template <class charT, class traits, class T> | |
std::basic_ostream<charT, traits>& write_csv(std::basic_ostream<charT, traits>& os, | |
const test_data<T>& data, | |
const charT* separator) | |
{ | |
typedef typename test_data<T>::const_iterator it_type; | |
typedef typename test_data<T>::value_type value_type; | |
typedef typename value_type::const_iterator value_type_iterator; | |
it_type a, b; | |
a = data.begin(); | |
b = data.end(); | |
while(a != b) | |
{ | |
value_type_iterator x, y; | |
bool sep = false; | |
x = a->begin(); | |
y = a->end(); | |
while(x != y) | |
{ | |
if(sep) | |
os << separator; | |
os << *x; | |
sep = true; | |
++x; | |
} | |
os << std::endl; | |
++a; | |
} | |
return os; | |
} | |
template <class T> | |
std::ostream& write_code(std::ostream& os, | |
const test_data<T>& data, | |
const char* name) | |
{ | |
typedef typename test_data<T>::const_iterator it_type; | |
typedef typename test_data<T>::value_type value_type; | |
typedef typename value_type::const_iterator value_type_iterator; | |
BOOST_ASSERT(os.good()); | |
it_type a, b; | |
a = data.begin(); | |
b = data.end(); | |
if(a == b) | |
return os; | |
os << "#define SC_(x) static_cast<T>(BOOST_JOIN(x, L))\n" | |
" static const boost::array<boost::array<T, " | |
<< a->size() << ">, " << data.size() << "> " << name << " = {{\n"; | |
while(a != b) | |
{ | |
if(a != data.begin()) | |
os << ", \n"; | |
value_type_iterator x, y; | |
x = a->begin(); | |
y = a->end(); | |
os << " { "; | |
while(x != y) | |
{ | |
if(x != a->begin()) | |
os << ", "; | |
os << "SC_(" << *x << ")"; | |
++x; | |
} | |
os << " }"; | |
++a; | |
} | |
os << "\n }};\n#undef SC_\n\n"; | |
return os; | |
} | |
} // namespace tools | |
} // namespace math | |
} // namespace boost | |
#ifdef BOOST_MSVC | |
#pragma warning(pop) | |
#endif | |
#endif // BOOST_MATH_TOOLS_TEST_DATA_HPP | |