mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 04:50:34 -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).
 | |
| ]
 |