mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-30 20:40:28 -04:00 
			
		
		
		
	
		
			
	
	
		
			447 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			447 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|  | [section:test_data Graphing, Profiling, and Generating Test Data for Special Functions] | ||
|  | 
 | ||
|  | The class `test_data` and associated helper functions are designed so that in just | ||
|  | a few lines of code you should be able to: | ||
|  | 
 | ||
|  | * Profile a continued fraction, or infinite series for convergence and accuracy. | ||
|  | * Generate csv data from a special function that can be imported into your favorite | ||
|  | graphing program (or spreadsheet) for further analysis. | ||
|  | * Generate high precision test data. | ||
|  | 
 | ||
|  | [h4 Synopsis] | ||
|  | 
 | ||
|  |    #include <boost/math/tools/test_data.hpp> | ||
|  | 
 | ||
|  | [important | ||
|  | This is a non-core Boost.Math header that is predominantly used for internal | ||
|  | maintenance of the library: as a result the library is located under  | ||
|  | `libs/math/include_private` and you will need to add that directory to | ||
|  | your include path in order to use this feature. | ||
|  | ] | ||
|  | 
 | ||
|  |    namespace boost{ namespace math{ namespace tools{ | ||
|  | 
 | ||
|  |    enum parameter_type | ||
|  |    { | ||
|  |       random_in_range = 0, | ||
|  |       periodic_in_range = 1, | ||
|  |       power_series = 2, | ||
|  |       dummy_param = 0x80, | ||
|  |    }; | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    struct parameter_info; | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    parameter_info<T> make_random_param(T start_range, T end_range, int n_points); | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    parameter_info<T> make_periodic_param(T start_range, T end_range, int n_points); | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    parameter_info<T> make_power_param(T basis, int start_exponent, int end_exponent); | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    bool get_user_parameter_info(parameter_info<T>& info, const char* param_name); | ||
|  | 
 | ||
|  |    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); | ||
|  | 
 | ||
|  |       // insertion: | ||
|  |       template <class F> | ||
|  |       test_data& insert(F func, const parameter_info<T>& arg1); | ||
|  | 
 | ||
|  |       template <class F> | ||
|  |       test_data& insert(F func, const parameter_info<T>& arg1,  | ||
|  |                         const parameter_info<T>& arg2); | ||
|  | 
 | ||
|  |       template <class F> | ||
|  |       test_data& insert(F func, const parameter_info<T>& arg1,  | ||
|  |                         const parameter_info<T>& arg2,  | ||
|  |                         const parameter_info<T>& arg3); | ||
|  | 
 | ||
|  |       void clear(); | ||
|  | 
 | ||
|  |       // access: | ||
|  |       iterator begin(); | ||
|  |       iterator end(); | ||
|  |       const_iterator begin()const; | ||
|  |       const_iterator end()const; | ||
|  |       bool operator==(const test_data& d)const; | ||
|  |       bool operator!=(const test_data& d)const; | ||
|  |       void swap(test_data& other); | ||
|  |       size_type size()const; | ||
|  |       size_type max_size()const; | ||
|  |       bool empty()const; | ||
|  | 
 | ||
|  |       bool operator < (const test_data& dat)const; | ||
|  |       bool operator <= (const test_data& dat)const; | ||
|  |       bool operator > (const test_data& dat)const; | ||
|  |       bool operator >= (const test_data& dat)const; | ||
|  |    }; | ||
|  | 
 | ||
|  |    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); | ||
|  | 
 | ||
|  |    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); | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    std::ostream& write_code(std::ostream& os, | ||
|  |                             const test_data<T>& data,  | ||
|  |                             const char* name); | ||
|  |                              | ||
|  |    }}} // namespaces | ||
|  |     | ||
|  | [h4 Description] | ||
|  | 
 | ||
|  | This tool is best illustrated with the following series of examples. | ||
|  | 
 | ||
|  | The functionality of test_data is split into the following parts: | ||
|  | 
 | ||
|  | * A functor that implements the function for which data is being generated: | ||
|  | this is the bit you have to write. | ||
|  | * One of more parameters that are to be passed to the functor, these are | ||
|  | described in fairly abstract terms: give me N points distributed like /this/ etc. | ||
|  | * The class test_data, that takes the functor and descriptions of the parameters | ||
|  | and computes how ever many output points have been requested, these are stored | ||
|  | in a sorted container. | ||
|  | * Routines to iterate over the test_data container and output the data in either | ||
|  | csv format, or as C++ source code (as a table using Boost.Array). | ||
|  | 
 | ||
|  | [h5 Example 1: Output Data for Graph Plotting] | ||
|  | 
 | ||
|  | For example, lets say we want to graph the lgamma function between -3 and 100, | ||
|  | one could do this like so: | ||
|  | 
 | ||
|  |    #include <boost/math/tools/test_data.hpp> | ||
|  |    #include <boost/math/special_functions/gamma.hpp> | ||
|  |     | ||
|  |    int main() | ||
|  |    { | ||
|  |       using namespace boost::math::tools; | ||
|  |        | ||
|  |       // create an object to hold the data: | ||
|  |       test_data<double> data; | ||
|  |        | ||
|  |       // insert 500 points at uniform intervals between just after -3 and 100: | ||
|  |       double (*pf)(double) = boost::math::lgamma; | ||
|  |       data.insert(pf, make_periodic_param(-3.0 + 0.00001, 100.0, 500)); | ||
|  |        | ||
|  |       // print out in csv format: | ||
|  |       write_csv(std::cout, data, ", "); | ||
|  |       return 0; | ||
|  |    } | ||
|  |     | ||
|  | Which, when plotted, results in: | ||
|  | 
 | ||
|  | [graph lgamma] | ||
|  | 
 | ||
|  | [h5 Example 2: Creating Test Data] | ||
|  | 
 | ||
|  | As a second example, let's suppose we want to create highly accurate test | ||
|  | data for a special function.  Since many special functions have two or | ||
|  | more independent parameters, it's very hard to effectively cover all of | ||
|  | the possible parameter space without generating gigabytes of data at | ||
|  | great computational expense.  A second best approach is to provide the tools | ||
|  | by which a user (or the library maintainer) can quickly generate more data | ||
|  | on demand to probe the function over a particular domain of interest. | ||
|  | 
 | ||
|  | In this example we'll generate test data for the beta function using | ||
|  | [@http://shoup.net/ntl/doc/RR.txt NTL::RR] at 1000 bit precision. | ||
|  | Rather than call our generic | ||
|  | version of the beta function, we'll implement a deliberately naive version | ||
|  | of the beta function using lgamma, and rely on the high precision of the  | ||
|  | data type used to get results accurate to at least 128-bit precision. In this | ||
|  | way our test data is independent of whatever clever tricks we may wish to | ||
|  | use inside the our beta function. | ||
|  | 
 | ||
|  | To start with then, here's the function object that creates the test data: | ||
|  | 
 | ||
|  |    #include <boost/math/tools/ntl.hpp> | ||
|  |    #include <boost/math/special_functions/gamma.hpp> | ||
|  |    #include <boost/math/tools/test_data.hpp> | ||
|  |    #include <fstream> | ||
|  | 
 | ||
|  |    #include <boost/math/tools/test_data.hpp> | ||
|  | 
 | ||
|  |    using namespace boost::math::tools; | ||
|  | 
 | ||
|  |    struct beta_data_generator | ||
|  |    { | ||
|  |       NTL::RR operator()(NTL::RR a, NTL::RR b) | ||
|  |       { | ||
|  |          // | ||
|  |          // If we throw a domain error then test_data will | ||
|  |          // ignore this input point. We'll use this to filter | ||
|  |          // out all cases where a < b since the beta function | ||
|  |          // is symmetrical in a and b: | ||
|  |          // | ||
|  |          if(a < b) | ||
|  |             throw std::domain_error(""); | ||
|  |              | ||
|  |          // very naively calculate spots with lgamma: | ||
|  |          NTL::RR g1, g2, g3; | ||
|  |          int s1, s2, s3; | ||
|  |          g1 = boost::math::lgamma(a, &s1); | ||
|  |          g2 = boost::math::lgamma(b, &s2); | ||
|  |          g3 = boost::math::lgamma(a+b, &s3); | ||
|  |          g1 += g2 - g3; | ||
|  |          g1 = exp(g1); | ||
|  |          g1 *= s1 * s2 * s3; | ||
|  |          return g1; | ||
|  |       } | ||
|  |    }; | ||
|  | 
 | ||
|  | To create the data, we'll need to input the domains for a and b | ||
|  | for which the function will be tested: the function `get_user_parameter_info` | ||
|  | is designed for just that purpose.  The start of main will look something like: | ||
|  | 
 | ||
|  |    // Set the precision on RR: | ||
|  |    NTL::RR::SetPrecision(1000); // bits. | ||
|  |    NTL::RR::SetOutputPrecision(40); // decimal digits. | ||
|  | 
 | ||
|  |    parameter_info<NTL::RR> arg1, arg2; | ||
|  |    test_data<NTL::RR> data; | ||
|  | 
 | ||
|  |    std::cout << "Welcome.\n" | ||
|  |       "This program will generate spot tests for the beta function:\n" | ||
|  |       "  beta(a, b)\n\n"; | ||
|  | 
 | ||
|  |    bool cont; | ||
|  |    std::string line; | ||
|  | 
 | ||
|  |    do{ | ||
|  |       // prompt the user for the domain of a and b to test: | ||
|  |       get_user_parameter_info(arg1, "a"); | ||
|  |       get_user_parameter_info(arg2, "b"); | ||
|  |        | ||
|  |       // create the data: | ||
|  |       data.insert(beta_data_generator(), arg1, arg2); | ||
|  |        | ||
|  |       // see if the user want's any more domains tested: | ||
|  |       std::cout << "Any more data [y/n]?"; | ||
|  |       std::getline(std::cin, line); | ||
|  |       boost::algorithm::trim(line); | ||
|  |       cont = (line == "y"); | ||
|  |    }while(cont); | ||
|  | 
 | ||
|  | [caution At this point one potential stumbling block should be mentioned: | ||
|  | test_data<>::insert will create a matrix of test data when there are two | ||
|  | or more parameters, so if we have two parameters and we're asked for | ||
|  | a thousand points on each, that's a ['million test points in total]. | ||
|  | Don't say you weren't warned!] | ||
|  | 
 | ||
|  | There's just one final step now, and that's to write the test data to file: | ||
|  | 
 | ||
|  |    std::cout << "Enter name of test data file [default=beta_data.ipp]"; | ||
|  |    std::getline(std::cin, line); | ||
|  |    boost::algorithm::trim(line); | ||
|  |    if(line == "") | ||
|  |       line = "beta_data.ipp"; | ||
|  |    std::ofstream ofs(line.c_str()); | ||
|  |    write_code(ofs, data, "beta_data"); | ||
|  | 
 | ||
|  | The format of the test data looks something like: | ||
|  | 
 | ||
|  |    #define SC_(x) static_cast<T>(BOOST_JOIN(x, L)) | ||
|  |       static const boost::array<boost::array<T, 3>, 1830> | ||
|  |       beta_med_data = { | ||
|  |          SC_(0.4883005917072296142578125), | ||
|  |          SC_(0.4883005917072296142578125), | ||
|  |          SC_(3.245912809500479157065104747353807392371),  | ||
|  |          SC_(3.5808107852935791015625), | ||
|  |          SC_(0.4883005917072296142578125), | ||
|  |          SC_(1.007653173802923954909901438393379243537),  | ||
|  |          /* ... lots of rows skipped */ | ||
|  |    }; | ||
|  | 
 | ||
|  | The first two values in each row are the input parameters that were passed | ||
|  | to our functor and the last value is the return value from the functor. | ||
|  | Had our functor returned a __tuple rather than a value, then we would have had | ||
|  | one entry for each element in the tuple in addition to the input parameters. | ||
|  | 
 | ||
|  | The first #define serves two purposes: | ||
|  | 
 | ||
|  | * It reduces the file sizes considerably: all those `static_cast`'s add up to a lot | ||
|  | of bytes otherwise (they are needed to suppress compiler warnings when `T` is  | ||
|  | narrower than a `long double`). | ||
|  | * It provides a useful customisation point: for example if we were testing | ||
|  | a user-defined type that has more precision than a `long double` we could change | ||
|  | it to: | ||
|  | 
 | ||
|  | [^#define SC_(x) lexical_cast<T>(BOOST_STRINGIZE(x))] | ||
|  |     | ||
|  | in order to ensure that no truncation of the values occurs prior to conversion | ||
|  | to `T`.  Note that this isn't used by default as it's rather hard on the compiler | ||
|  | when the table is large. | ||
|  | 
 | ||
|  | [h5 Example 3: Profiling a Continued Fraction for Convergence and Accuracy] | ||
|  | 
 | ||
|  | Alternatively, lets say we want to profile a continued fraction for  | ||
|  | convergence and error.  As an example, we'll use the continued fraction | ||
|  | for the upper incomplete gamma function, the following function object | ||
|  | returns the next a[sub N ] and b[sub N ] of the continued fraction | ||
|  | each time it's invoked: | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    struct upper_incomplete_gamma_fract | ||
|  |    { | ||
|  |    private: | ||
|  |       T z, a; | ||
|  |       int k; | ||
|  |    public: | ||
|  |       typedef std::pair<T,T> result_type; | ||
|  | 
 | ||
|  |       upper_incomplete_gamma_fract(T a1, T z1) | ||
|  |          : z(z1-a1+1), a(a1), k(0) | ||
|  |       { | ||
|  |       } | ||
|  | 
 | ||
|  |       result_type operator()() | ||
|  |       { | ||
|  |          ++k; | ||
|  |          z += 2; | ||
|  |          return result_type(k * (a - k), z); | ||
|  |       } | ||
|  |    }; | ||
|  | 
 | ||
|  | We want to measure both the relative error, and the rate of convergence | ||
|  | of this fraction, so we'll write a functor that returns both as a __tuple: | ||
|  | class test_data will unpack the tuple for us, and create one column of data | ||
|  | for each element in the tuple (in addition to the input parameters): | ||
|  | 
 | ||
|  |    #include <boost/math/tools/test_data.hpp> | ||
|  |    #include <boost/math/tools/test.hpp> | ||
|  |    #include <boost/math/special_functions/gamma.hpp> | ||
|  |    #include <boost/math/tools/ntl.hpp> | ||
|  |    #include <boost/math/tools/tuple.hpp> | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    struct profile_gamma_fraction | ||
|  |    { | ||
|  |       typedef ``__tuple``<T, T> result_type; | ||
|  | 
 | ||
|  |       result_type operator()(T val) | ||
|  |       { | ||
|  |          using namespace boost::math::tools; | ||
|  |          // estimate the true value, using arbitary precision | ||
|  |          // arithmetic and NTL::RR: | ||
|  |          NTL::RR rval(val); | ||
|  |          upper_incomplete_gamma_fract<NTL::RR> f1(rval, rval); | ||
|  |          NTL::RR true_val = continued_fraction_a(f1, 1000); | ||
|  |          // | ||
|  |          // Now get the aproximation at double precision, along with the number of | ||
|  |          // iterations required: | ||
|  |          boost::uintmax_t iters = std::numeric_limits<boost::uintmax_t>::max(); | ||
|  |          upper_incomplete_gamma_fract<T> f2(val, val); | ||
|  |          T found_val = continued_fraction_a(f2, std::numeric_limits<T>::digits, iters); | ||
|  |          // | ||
|  |          // Work out the relative error, as measured in units of epsilon: | ||
|  |          T err = real_cast<T>(relative_error(true_val, NTL::RR(found_val)) / std::numeric_limits<T>::epsilon()); | ||
|  |          // | ||
|  |          // now just return the results as a tuple: | ||
|  |          return boost::math::make_tuple(err, iters); | ||
|  |       } | ||
|  |    }; | ||
|  | 
 | ||
|  | Feeding that functor into test_data allows rapid output of csv data, | ||
|  | for whatever type `T` we may be interested in: | ||
|  |     | ||
|  |    int main() | ||
|  |    { | ||
|  |       using namespace boost::math::tools; | ||
|  |       // create an object to hold the data: | ||
|  |       test_data<double> data; | ||
|  |       // insert 500 points at uniform intervals between just after 0 and 100: | ||
|  |       data.insert(profile_gamma_fraction<double>(), make_periodic_param(0.01, 100.0, 100)); | ||
|  |       // print out in csv format: | ||
|  |       write_csv(std::cout, data, ", "); | ||
|  |       return 0; | ||
|  |    } | ||
|  | 
 | ||
|  | This time there's no need to plot a graph, the first few rows are: | ||
|  | 
 | ||
|  |    a and z,  Error/epsilon,  Iterations required | ||
|  |     | ||
|  |    0.01,     9723.14,        4726 | ||
|  |    1.0099,   9.54818,        87 | ||
|  |    2.0098,   3.84777,        40 | ||
|  |    3.0097,   0.728358,       25 | ||
|  |    4.0096,   2.39712,        21 | ||
|  |    5.0095,   0.233263,       16 | ||
|  | 
 | ||
|  | So it's pretty clear that this fraction shouldn't be used for small values | ||
|  | of a and z. | ||
|  | 
 | ||
|  | [h4 reference] | ||
|  | 
 | ||
|  | Most of this tool has been described already in the examples above, we'll  | ||
|  | just add the following notes on the non-member functions: | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    parameter_info<T> make_random_param(T start_range, T end_range, int n_points); | ||
|  |     | ||
|  | Tells class test_data to test /n_points/ random values in the range  | ||
|  | [start_range,end_range]. | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    parameter_info<T> make_periodic_param(T start_range, T end_range, int n_points); | ||
|  |     | ||
|  | Tells class test_data to test /n_points/ evenly spaced values in the range  | ||
|  | [start_range,end_range]. | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    parameter_info<T> make_power_param(T basis, int start_exponent, int end_exponent); | ||
|  | 
 | ||
|  | Tells class test_data to test points of the form ['basis + R * 2[super expon]] for each | ||
|  | /expon/ in the range [start_exponent, end_exponent], and /R/ a random number in \[0.5, 1\]. | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    bool get_user_parameter_info(parameter_info<T>& info, const char* param_name); | ||
|  |     | ||
|  | Prompts the user for the parameter range and form to use. | ||
|  | 
 | ||
|  | Finally, if we don't want the parameter to be included in the output, we can tell | ||
|  | test_data by setting it a "dummy parameter": | ||
|  | 
 | ||
|  |    parameter_info<double> p = make_random_param(2.0, 5.0, 10); | ||
|  |    p.type |= dummy_param; | ||
|  |     | ||
|  | This is useful when the functor used transforms the parameter in some way | ||
|  | before passing it to the function under test, usually the functor will then | ||
|  | return both the transformed input and the result in a tuple, so there's no | ||
|  | need for the original pseudo-parameter to be included in program output. | ||
|  | 
 | ||
|  | [endsect][/section:test_data Graphing, Profiling, and Generating Test Data for Special Functions] | ||
|  | 
 | ||
|  | [/  | ||
|  |   Copyright 2006 John Maddock and Paul A. Bristow. | ||
|  |   Distributed under 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). | ||
|  | ] |