mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 13:10:19 -04:00 
			
		
		
		
	
		
			
	
	
		
			509 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			509 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|  | [section:special_tut Tutorial: How to Write a New Special Function] | ||
|  | 
 | ||
|  | [section:special_tut_impl Implementation] | ||
|  | 
 | ||
|  | In this section, we'll provide a "recipe" for adding a new special function to this library to make life easier for | ||
|  | future authors wishing to contribute.  We'll assume the function returns a single floating-point result, and takes | ||
|  | two floating-point arguments.  For the sake of exposition we'll give the function the name [~my_special]. | ||
|  | 
 | ||
|  | Normally, the implementation of such a function is split into two layers - a public user layer, and an internal | ||
|  | implementation layer that does the actual work. | ||
|  | The implementation layer is declared inside a `detail` namespace and has a simple signature: | ||
|  | 
 | ||
|  |    namespace boost { namespace math { namespace detail { | ||
|  | 
 | ||
|  |    template <class T, class Policy> | ||
|  |    T my_special_imp(const T& a, const T&b, const Policy& pol) | ||
|  |    { | ||
|  |       /* Implementation goes here */ | ||
|  |    } | ||
|  | 
 | ||
|  |    }}} // namespaces | ||
|  | 
 | ||
|  | We'll come back to what can go inside the implementation later, but first lets look at the user layer. | ||
|  | This consists of two overloads of the function, with and without a __Policy argument: | ||
|  | 
 | ||
|  |    namespace boost{ namespace math{ | ||
|  | 
 | ||
|  |    template <class T, class U> | ||
|  |    typename tools::promote_args<T, U>::type my_special(const T& a, const U& b); | ||
|  | 
 | ||
|  |    template <class T, class U, class Policy> | ||
|  |    typename tools::promote_args<T, U>::type my_special(const T& a, const U& b, const Policy& pol); | ||
|  | 
 | ||
|  |    }} // namespaces | ||
|  | 
 | ||
|  | Note how each argument has a different template type - this allows for mixed type arguments - the return | ||
|  | type is computed from a traits class and is the "common type" of all the arguments after any integer | ||
|  | arguments have been promoted to type `double`. | ||
|  | 
 | ||
|  | The implementation of the non-policy overload is trivial: | ||
|  | 
 | ||
|  |    namespace boost{ namespace math{ | ||
|  | 
 | ||
|  |    template <class T, class U> | ||
|  |    inline typename tools::promote_args<T, U>::type my_special(const T& a, const U& b) | ||
|  |    { | ||
|  |       // Simply forward with a default policy: | ||
|  |       return my_special(a, b, policies::policy<>(); | ||
|  |    } | ||
|  | 
 | ||
|  |    }} // namespaces | ||
|  | 
 | ||
|  | The implementation of the other overload is somewhat more complex, as there's some meta-programming to do, | ||
|  | but from a runtime perspective is still a one-line forwarding function.  Here it is with comments explaining | ||
|  | what each line does: | ||
|  | 
 | ||
|  |    namespace boost{ namespace math{ | ||
|  | 
 | ||
|  |    template <class T, class U, class Policy> | ||
|  |    inline typename tools::promote_args<T, U>::type my_special(const T& a, const U& b, const Policy& pol) | ||
|  |    { | ||
|  |       // | ||
|  |       // We've found some standard library functions to misbehave if any FPU exception flags | ||
|  |       // are set prior to their call, this code will clear those flags, then reset them | ||
|  |       // on exit: | ||
|  |       // | ||
|  |       BOOST_FPU_EXCEPTION_GUARD | ||
|  |       // | ||
|  |       // The type of the result - the common type of T and U after | ||
|  |       // any integer types have been promoted to double: | ||
|  |       // | ||
|  |       typedef typename tools::promote_args<T, U>::type result_type; | ||
|  |       // | ||
|  |       // The type used for the calculation.  This may be a wider type than | ||
|  |       // the result in order to ensure full precision: | ||
|  |       // | ||
|  |       typedef typename policies::evaluation<result_type, Policy>::type value_type; | ||
|  |       // | ||
|  |       // The type of the policy to forward to the actual implementation. | ||
|  |       // We disable promotion of float and double as that's [possibly] | ||
|  |       // happened already in the line above.  Also reset to the default | ||
|  |       // any policies we don't use (reduces code bloat if we're called | ||
|  |       // multiple times with differing policies we don't actually use). | ||
|  |       // Also normalise the type, again to reduce code bloat in case we're | ||
|  |       // called multiple times with functionally identical policies that happen | ||
|  |       // to be different types. | ||
|  |       // | ||
|  |       typedef typename policies::normalise< | ||
|  |          Policy, | ||
|  |          policies::promote_float<false>, | ||
|  |          policies::promote_double<false>, | ||
|  |          policies::discrete_quantile<>, | ||
|  |          policies::assert_undefined<> >::type forwarding_policy; | ||
|  |       // | ||
|  |       // Whew.  Now we can make the actual call to the implementation. | ||
|  |       // Arguments are explicitly cast to the evaluation type, and the result | ||
|  |       // passed through checked_narrowing_cast which handles things like overflow | ||
|  |       // according to the policy passed: | ||
|  |       // | ||
|  |       return policies::checked_narrowing_cast<result_type, forwarding_policy>( | ||
|  |             detail::my_special_imp( | ||
|  |                   static_cast<value_type>(a), | ||
|  |                   static_cast<value_type>(x), | ||
|  |                   forwarding_policy()), | ||
|  |             "boost::math::my_special<%1%>(%1%, %1%)"); | ||
|  |    } | ||
|  | 
 | ||
|  |    }} // namespaces | ||
|  | 
 | ||
|  | We're now almost there, we just need to flesh out the details of the implementation layer: | ||
|  | 
 | ||
|  |    namespace boost { namespace math { namespace detail { | ||
|  | 
 | ||
|  |    template <class T, class Policy> | ||
|  |    T my_special_imp(const T& a, const T&b, const Policy& pol) | ||
|  |    { | ||
|  |       /* Implementation goes here */ | ||
|  |    } | ||
|  | 
 | ||
|  |    }}} // namespaces | ||
|  | 
 | ||
|  | The following guidelines indicate what (other than basic arithmetic) can go in the implementation: | ||
|  | 
 | ||
|  | * Error conditions (for example bad arguments) should be handled by calling one of the | ||
|  | [link math_toolkit.error_handling.finding_more_information policy based error handlers]. | ||
|  | * Calls to standard library functions should be made unqualified (this allows argument | ||
|  | dependent lookup to find standard library functions for user-defined floating point | ||
|  | types such as those from __multiprecision).  In addition, the macro `BOOST_MATH_STD_USING` | ||
|  | should appear at the start of the function (note no semi-colon afterwards!) so that | ||
|  | all the math functions in `namespace std` are visible in the current scope. | ||
|  | * Calls to other special functions should be made as fully qualified calls, and include the | ||
|  | policy parameter as the last argument, for example `boost::math::tgamma(a, pol)`. | ||
|  | * Where possible, evaluation of series, continued fractions, polynomials, or root | ||
|  | finding should use one of the [link math_toolkit.internals_overview  boiler-plate functions].  In any case, after | ||
|  | any iterative method, you should verify that the number of iterations did not exceed the | ||
|  | maximum specified in the __Policy type, and if it did terminate as a result of exceeding the | ||
|  | maximum, then the appropriate error handler should be called (see existing code for examples). | ||
|  | * Numeric constants such as [pi] etc should be obtained via a call to the [link math_toolkit.constants appropriate function], | ||
|  | for example: `constants::pi<T>()`. | ||
|  | * Where tables of coefficients are used (for example for rational approximations), care should be taken | ||
|  | to ensure these are initialized at program startup to ensure thread safety when using user-defined number types. | ||
|  | See for example the use of `erf_initializer` in [@../../include/boost/math/special_functions/erf.hpp  erf.hpp]. | ||
|  | 
 | ||
|  | Here are some other useful internal functions: | ||
|  | 
 | ||
|  | [table | ||
|  | [[function][Meaning]] | ||
|  | [[`policies::digits<T, Policy>()`][Returns number of binary digits in T (possible overridden by the policy).]] | ||
|  | [[`policies::get_max_series_iterations<Policy>()`][Maximum number of iterations for series evaluation.]] | ||
|  | [[`policies::get_max_root_iterations<Policy>()`][Maximum number of iterations for root finding.]] | ||
|  | [[`polices::get_epsilon<T, Policy>()`][Epsilon for type T, possibly overridden by the Policy.]] | ||
|  | [[`tools::digits<T>()`][Returns the number of binary digits in T.]] | ||
|  | [[`tools::max_value<T>()`][Equivalent to `std::numeric_limits<T>::max()`]] | ||
|  | [[`tools::min_value<T>()`][Equivalent to `std::numeric_limits<T>::min()`]] | ||
|  | [[`tools::log_max_value<T>()`][Equivalent to the natural logarithm of `std::numeric_limits<T>::max()`]] | ||
|  | [[`tools::log_min_value<T>()`][Equivalent to the natural logarithm of `std::numeric_limits<T>::min()`]] | ||
|  | [[`tools::epsilon<T>()`][Equivalent to `std::numeric_limits<T>::epsilon()`.]] | ||
|  | [[`tools::root_epsilon<T>()`][Equivalent to the square root of `std::numeric_limits<T>::epsilon()`.]] | ||
|  | [[`tools::forth_root_epsilon<T>()`][Equivalent to the forth root of `std::numeric_limits<T>::epsilon()`.]] | ||
|  | ] | ||
|  | 
 | ||
|  | [endsect] | ||
|  | 
 | ||
|  | [section:special_tut_test Testing] | ||
|  | 
 | ||
|  | We work under the assumption that untested code doesn't work, so some tests for your new special function are in order, | ||
|  | we'll divide these up in to 3 main categories: | ||
|  | 
 | ||
|  | [h4 Spot Tests] | ||
|  | 
 | ||
|  | Spot tests consist of checking that the expected exception is generated when the inputs are in error (or | ||
|  | otherwise generate undefined values), and checking any special values.  We can check for expected exceptions | ||
|  | with `BOOST_CHECK_THROW`, so for example if it's a domain error for the last parameter to be outside the range | ||
|  | `[0,1]` then we might have: | ||
|  | 
 | ||
|  |    BOOST_CHECK_THROW(my_special(0, -0.1), std::domain_error); | ||
|  |    BOOST_CHECK_THROW(my_special(0, 1.1), std::domain_error); | ||
|  | 
 | ||
|  | When the function has known exact values (typically integer values) we can use `BOOST_CHECK_EQUAL`: | ||
|  | 
 | ||
|  |    BOOST_CHECK_EQUAL(my_special(1.0, 0.0), 0); | ||
|  |    BOOST_CHECK_EQUAL(my_special(1.0, 1.0), 1); | ||
|  | 
 | ||
|  | When the function has known values which are not exact (from a floating point perspective) then we can use | ||
|  | `BOOST_CHECK_CLOSE_FRACTION`: | ||
|  | 
 | ||
|  |    // Assumes 4 epsilon is as close as we can get to a true value of 2Pi: | ||
|  |    BOOST_CHECK_CLOSE_FRACTION(my_special(0.5, 0.5), 2 * constants::pi<double>(), std::numeric_limits<double>::epsilon() * 4); | ||
|  | 
 | ||
|  | [h4 Independent Test Values] | ||
|  | 
 | ||
|  | If the function is implemented by some other known good source (for example Mathematica or it's online versions | ||
|  | [@http://functions.wolfram.com functions.wolfram.com] or [@http://www.wolframalpha.com www.wolframalpha.com] | ||
|  | then it's a good idea to sanity check our implementation by having at least one independendly generated value | ||
|  | for each code branch our implementation may take.  To slot these in nicely with our testing framework it's best to | ||
|  | tabulate these like this: | ||
|  | 
 | ||
|  |     // function values calculated on http://functions.wolfram.com/ | ||
|  |     static const boost::array<boost::array<T, 3>, 10> my_special_data = {{ | ||
|  |         {{ SC_(0), SC_(0), SC_(1) }}, | ||
|  |         {{ SC_(0), SC_(1), SC_(1.26606587775200833559824462521471753760767031135496220680814) }}, | ||
|  |         /* More values here... */ | ||
|  |     }}; | ||
|  | 
 | ||
|  | We'll see how to use this table and the meaning of the `SC_` macro later.  One important point | ||
|  | is to make sure that the input values have exact binary representations: so choose values such as | ||
|  | 1.5, 1.25, 1.125 etc.  This ensures that if `my_special` is unusually sensitive in one area, that | ||
|  | we don't get apparently large errors just because the inputs are 0.5 ulp in error. | ||
|  | 
 | ||
|  | [h4 Random Test Values] | ||
|  | 
 | ||
|  | We can generate a large number of test values to check both for future regressions, and for | ||
|  | accumulated rounding or cancellation error in our implementation.  Ideally we would use an | ||
|  | independent implementation for this (for example my_special may be defined in directly terms | ||
|  | of other special functions but not implemented that way for performance or accuracy reasons). | ||
|  | Alternatively we may use our own implementation directly, but with any special cases (asymptotic | ||
|  | expansions etc) disabled.  We have a set of [link math_toolkit.internals.test_data tools] | ||
|  | to generate test data directly, here's a typical example: | ||
|  | 
 | ||
|  | [import ../../example/special_data.cpp] | ||
|  | [special_data_example] | ||
|  | 
 | ||
|  | Typically several sets of data will be generated this way, including random values in some "normal" | ||
|  | range, extreme values (very large or very small), and values close to any "interesting" behaviour | ||
|  | of the function (singularities etc). | ||
|  | 
 | ||
|  | [h4 The Test File Header] | ||
|  | 
 | ||
|  | We split the actual test file into 2 distinct parts: a header that contains the testing code | ||
|  | as a series of function templates, and the actual .cpp test driver that decides which types | ||
|  | are tested, and sets the "expected" error rates for those types.  It's done this way because: | ||
|  | 
 | ||
|  | * We want to test with both built in floating point types, and with multiprecision types. | ||
|  | However, both compile and runtimes with the latter can be too long for the folks who run | ||
|  | the tests to realistically cope with, so it makes sense to split the test into (at least) | ||
|  | 2 parts. | ||
|  | * The definition of the SC_ macro used in our tables of data may differ depending on what type | ||
|  | we're testing (see below).  Again this is largely a matter of managing compile times as large tables | ||
|  | of user-defined-types can take a crazy amount of time to compile with some compilers. | ||
|  | 
 | ||
|  | The test header contains 2 functions: | ||
|  | 
 | ||
|  |    template <class Real, class T> | ||
|  |    void do_test(const T& data, const char* type_name, const char* test_name); | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    void test(T, const char* type_name); | ||
|  | 
 | ||
|  | Before implementing those, we'll include the headers we'll need, and provide a default | ||
|  | definition for the SC_ macro: | ||
|  | 
 | ||
|  |    // A couple of Boost.Test headers in case we need any BOOST_CHECK_* macros: | ||
|  |    #include <boost/test/unit_test.hpp> | ||
|  |    #include <boost/test/floating_point_comparison.hpp> | ||
|  |    // Our function to test: | ||
|  |    #include <boost/math/special_functions/my_special.hpp> | ||
|  |    // We need boost::array for our test data, plus a few headers from | ||
|  |    // libs/math/test that contain our testing machinary: | ||
|  |    #include <boost/array.hpp> | ||
|  |    #include "functor.hpp" | ||
|  |    #include "handle_test_result.hpp" | ||
|  |    #include "table_type.hpp" | ||
|  | 
 | ||
|  |    #ifndef SC_ | ||
|  |    #define SC_(x) static_cast<typename table_type<T>::type>(BOOST_JOIN(x, L)) | ||
|  |    #endif | ||
|  | 
 | ||
|  | The easiest function to implement is the "test" function which is what we'll be calling | ||
|  | from the test-driver program.  It simply includes the files containing the tabular | ||
|  | test data and calls `do_test` function for each table, along with a description of what's | ||
|  | being tested: | ||
|  | 
 | ||
|  |    template <class T> | ||
|  |    void test(T, const char* type_name) | ||
|  |    { | ||
|  |       // | ||
|  |       // The actual test data is rather verbose, so it's in a separate file | ||
|  |       // | ||
|  |       // The contents are as follows, each row of data contains | ||
|  |       // three items, input value a, input value b and my_special(a, b): | ||
|  |       // | ||
|  |    #  include "my_special_1.ipp" | ||
|  | 
 | ||
|  |       do_test<T>(my_special_1, name, "MySpecial Function: Mathematica Values"); | ||
|  | 
 | ||
|  |    #  include "my_special_2.ipp" | ||
|  | 
 | ||
|  |       do_test<T>(my_special_2, name, "MySpecial Function: Random Values"); | ||
|  | 
 | ||
|  |    #  include "my_special_3.ipp" | ||
|  | 
 | ||
|  |       do_test<T>(my_special_3, name, "MySpecial Function: Very Small Values"); | ||
|  |    } | ||
|  | 
 | ||
|  | The function `do_test` takes each table of data and calculates values for each row | ||
|  | of data, along with statistics for max and mean error etc, most of this is handled | ||
|  | by some boilerplate code: | ||
|  | 
 | ||
|  |    template <class Real, class T> | ||
|  |    void do_test(const T& data, const char* type_name, const char* test_name) | ||
|  |    { | ||
|  |       // Get the type of each row and each element in the rows: | ||
|  |       typedef typename T::value_type row_type; | ||
|  |       typedef Real                   value_type; | ||
|  | 
 | ||
|  |       // Get a pointer to our function, we have to use a workaround here | ||
|  |       // as some compilers require the template types to be explicitly | ||
|  |       // specified, while others don't much like it if it is! | ||
|  |       typedef value_type (*pg)(value_type, value_type); | ||
|  |    #if defined(BOOST_MATH_NO_DEDUCED_FUNCTION_POINTERS) | ||
|  |       pg funcp = boost::math::my_special<value_type, value_type>; | ||
|  |    #else | ||
|  |       pg funcp = boost::math::my_special; | ||
|  |    #endif | ||
|  | 
 | ||
|  |       // Somewhere to hold our results: | ||
|  |       boost::math::tools::test_result<value_type> result; | ||
|  |       // And some pretty printing: | ||
|  |       std::cout << "Testing " << test_name << " with type " << type_name | ||
|  |          << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"; | ||
|  | 
 | ||
|  |       // | ||
|  |       // Test my_special against data: | ||
|  |       // | ||
|  |       result = boost::math::tools::test_hetero<Real>( | ||
|  |          /* First argument is the table */ | ||
|  |          data, | ||
|  |          /* Next comes our function pointer, plus the indexes of it's arguments in the table */ | ||
|  |          bind_func<Real>(funcp, 0, 1), | ||
|  |          /* Then the index of the result in the table - potentially we can test several | ||
|  |          related functions this way, each having the same input arguments, and different | ||
|  |          output values in different indexes in the table */ | ||
|  |          extract_result<Real>(2)); | ||
|  |       // | ||
|  |       // Finish off with some boilerplate to check the results were within the expected errors, | ||
|  |       // and pretty print the results: | ||
|  |       // | ||
|  |       handle_test_result(result, data[result.worst()], result.worst(), type_name, "boost::math::my_special", test_name); | ||
|  |    } | ||
|  | 
 | ||
|  | Now we just need to write the test driver program, at it's most basic it looks something like this: | ||
|  | 
 | ||
|  |    #include <boost/math/special_functions/math_fwd.hpp> | ||
|  |    #include <boost/math/tools/test.hpp> | ||
|  |    #include <boost/math/tools/stats.hpp> | ||
|  |    #include <boost/type_traits.hpp> | ||
|  |    #include <boost/array.hpp> | ||
|  |    #include "functor.hpp" | ||
|  | 
 | ||
|  |    #include "handle_test_result.hpp" | ||
|  |    #include "test_my_special.hpp" | ||
|  | 
 | ||
|  |    BOOST_AUTO_TEST_CASE( test_main ) | ||
|  |    { | ||
|  |       // | ||
|  |       // Test each floating point type, plus real_concept. | ||
|  |       // We specify the name of each type by hand as typeid(T).name() | ||
|  |       // often gives an unreadable mangled name. | ||
|  |       // | ||
|  |       test(0.1F, "float"); | ||
|  |       test(0.1, "double"); | ||
|  |       // | ||
|  |       // Testing of long double and real_concept is protected | ||
|  |       // by some logic to disable these for unsupported | ||
|  |       // or problem compilers. | ||
|  |       // | ||
|  |    #ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS | ||
|  |       test(0.1L, "long double"); | ||
|  |    #ifndef BOOST_MATH_NO_REAL_CONCEPT_TESTS | ||
|  |    #if !BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) | ||
|  |       test(boost::math::concepts::real_concept(0.1), "real_concept"); | ||
|  |    #endif | ||
|  |    #endif | ||
|  |    #else | ||
|  |       std::cout << "<note>The long double tests have been disabled on this platform " | ||
|  |          "either because the long double overloads of the usual math functions are " | ||
|  |          "not available at all, or because they are too inaccurate for these tests " | ||
|  |          "to pass.</note>" << std::cout; | ||
|  |    #endif | ||
|  |    } | ||
|  | 
 | ||
|  | That's almost all there is too it - except that if the above program is run it's very likely that | ||
|  | all the tests will fail as the default maximum allowable error is 1 epsilon.  So we'll | ||
|  | define a function (don't forget to call it from the start of the `test_main` above) to | ||
|  | up the limits to something sensible, based both on the function we're calling and on | ||
|  | the particular tests plus the platform and compiler: | ||
|  | 
 | ||
|  |    void expected_results() | ||
|  |    { | ||
|  |       // | ||
|  |       // Define the max and mean errors expected for | ||
|  |       // various compilers and platforms. | ||
|  |       // | ||
|  |       const char* largest_type; | ||
|  |    #ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS | ||
|  |       if(boost::math::policies::digits<double, boost::math::policies::policy<> >() == boost::math::policies::digits<long double, boost::math::policies::policy<> >()) | ||
|  |       { | ||
|  |          largest_type = "(long\\s+)?double|real_concept"; | ||
|  |       } | ||
|  |       else | ||
|  |       { | ||
|  |          largest_type = "long double|real_concept"; | ||
|  |       } | ||
|  |    #else | ||
|  |       largest_type = "(long\\s+)?double"; | ||
|  |    #endif | ||
|  |       // | ||
|  |       // We call add_expected_result for each error rate we wish to adjust, these tell | ||
|  |       // handle_test_result what level of error is acceptable.  We can have as many calls | ||
|  |       // to add_expected_result as we need, each one establishes a rule for acceptable error | ||
|  |       // with rules set first given preference. | ||
|  |       // | ||
|  |       add_expected_result( | ||
|  |          /* First argument is a regular expression to match against the name of the compiler | ||
|  |             set in BOOST_COMPILER */ | ||
|  |          ".*", | ||
|  |          /* Second argument is a regular expression to match against the name of the | ||
|  |             C++ standard library as set in BOOST_STDLIB */ | ||
|  |          ".*", | ||
|  |          /* Third argument is a regular expression to match against the name of the | ||
|  |             platform as set in BOOST_PLATFORM */ | ||
|  |          ".*", | ||
|  |          /* Forth argument is the name of the type being tested, normally we will | ||
|  |             only need to up the acceptable error rate for the widest floating | ||
|  |             point type being tested */ | ||
|  |          largest_real, | ||
|  |          /* Fifth argument is a regular expression to match against | ||
|  |             the name of the group of data being tested */ | ||
|  |          "MySpecial Function:.*Small.*", | ||
|  |          /* Sixth argument is a regular expression to match against the name | ||
|  |             of the function being tested */ | ||
|  |          "boost::math::my_special", | ||
|  |          /* Seventh argument is the maximum allowable error expressed in units | ||
|  |             of machine epsilon passed as a long integer value */ | ||
|  |          50, | ||
|  |          /* Eighth argument is the maximum allowable mean error expressed in units | ||
|  |             of machine epsilon passed as a long integer value */ | ||
|  |          20); | ||
|  |    } | ||
|  | 
 | ||
|  | [h4 Testing Multiprecision Types] | ||
|  | 
 | ||
|  | Testing of multiprecision types is handled by the test drivers in libs/multiprecision/test/math, | ||
|  | please refer to these for examples.  Note that these tests are run only occationally as they take | ||
|  | a lot of CPU cycles to build and run. | ||
|  | 
 | ||
|  | [h4 Improving Compile Times] | ||
|  | 
 | ||
|  | As noted above, these test programs can take a while to build as we're instantiating a lot of templates | ||
|  | for several different types, and our test runners are already stretched to the limit, and probably | ||
|  | using outdated "spare" hardware.  There are two things we can do to speed things up: | ||
|  | 
 | ||
|  | * Use a precompiled header. | ||
|  | * Use separate compilation of our special function templates. | ||
|  | 
 | ||
|  | We can make these changes by changing the list of includes from: | ||
|  | 
 | ||
|  |    #include <boost/math/special_functions/math_fwd.hpp> | ||
|  |    #include <boost/math/tools/test.hpp> | ||
|  |    #include <boost/math/tools/stats.hpp> | ||
|  |    #include <boost/type_traits.hpp> | ||
|  |    #include <boost/array.hpp> | ||
|  |    #include "functor.hpp" | ||
|  | 
 | ||
|  |    #include "handle_test_result.hpp" | ||
|  | 
 | ||
|  | To just: | ||
|  | 
 | ||
|  |    #include <pch_light.hpp> | ||
|  | 
 | ||
|  | And changing | ||
|  | 
 | ||
|  |    #include <boost/math/special_functions/my_special.hpp> | ||
|  | 
 | ||
|  | To: | ||
|  | 
 | ||
|  |    #include <boost/math/special_functions/math_fwd.hpp> | ||
|  | 
 | ||
|  | The Jamfile target that builds the test program will need the targets | ||
|  | 
 | ||
|  |    test_instances//test_instances pch_light | ||
|  | 
 | ||
|  | adding to it's list of source dependencies (see the Jamfile for examples). | ||
|  | 
 | ||
|  | Finally the project in libs/math/test/test_instances will need modifying | ||
|  | to instantiate function `my_special`. | ||
|  | 
 | ||
|  | These changes should be made last, when `my_special` is stable and the code is in Trunk. | ||
|  | 
 | ||
|  | [h4 Concept Checks] | ||
|  | 
 | ||
|  | Our concept checks verify that your function's implementation makes no assumptions that aren't | ||
|  | required by our [link math_toolkit.real_concepts Real number conceptual requirements].  They also | ||
|  | check for various common bugs and programming traps that we've fallen into over time.  To | ||
|  | add your function to these tests, edit libs/math/test/compile_test/instantiate.hpp to add | ||
|  | calls to your function: there are 7 calls to each function, each with a different purpose. | ||
|  | Search for something like "ibeta" or "gamm_p" and follow their examples. | ||
|  | 
 | ||
|  | [endsect] | ||
|  | 
 | ||
|  | [endsect] | ||
|  | 
 | ||
|  | [/ | ||
|  |   Copyright 2013 John Maddock. | ||
|  |   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). | ||
|  | ] |