mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2026-06-16 12:48:42 -04:00
Squashed 'boost/' content from commit b4feb19f2
git-subtree-dir: boost git-subtree-split: b4feb19f287ee92d87a9624b5d36b7cf46aeadeb
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
[section:cf Continued Fraction Evaluation]
|
||||
|
||||
[h4 Synopsis]
|
||||
|
||||
``
|
||||
#include <boost/math/tools/fraction.hpp>
|
||||
``
|
||||
|
||||
namespace boost{ namespace math{ namespace tools{
|
||||
|
||||
template <class Gen, class U>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_b(Gen& g, const U& tolerance, boost::uintmax_t& max_terms)
|
||||
|
||||
template <class Gen, class U>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_b(Gen& g, const U& tolerance)
|
||||
|
||||
template <class Gen, class U>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_a(Gen& g, const U& tolerance, boost::uintmax_t& max_terms)
|
||||
|
||||
template <class Gen, class U>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_a(Gen& g, const U& tolerance)
|
||||
|
||||
//
|
||||
// These interfaces are present for legacy reasons, and are now deprecated:
|
||||
//
|
||||
template <class Gen>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_b(Gen& g, int bits);
|
||||
|
||||
template <class Gen>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_b(Gen& g, int bits, boost::uintmax_t& max_terms);
|
||||
|
||||
template <class Gen>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_a(Gen& g, int bits);
|
||||
|
||||
template <class Gen>
|
||||
typename detail::fraction_traits<Gen>::result_type
|
||||
continued_fraction_a(Gen& g, int bits, boost::uintmax_t& max_terms);
|
||||
|
||||
}}} // namespaces
|
||||
|
||||
[h4 Description]
|
||||
|
||||
[@http://en.wikipedia.org/wiki/Continued_fraction Continued fractions are a common method of approximation. ]
|
||||
These functions all evaluate the continued fraction described by the /generator/
|
||||
type argument. The functions with an "_a" suffix evaluate the fraction:
|
||||
|
||||
[equation fraction2]
|
||||
|
||||
and those with a "_b" suffix evaluate the fraction:
|
||||
|
||||
[equation fraction1]
|
||||
|
||||
This latter form is somewhat more natural in that it corresponds with the usual
|
||||
definition of a continued fraction, but note that the first /a/ value returned by
|
||||
the generator is discarded. Further, often the first /a/ and /b/ values in a
|
||||
continued fraction have different defining equations to the remaining terms, which
|
||||
may make the "_a" suffixed form more appropriate.
|
||||
|
||||
The generator type should be a function object which supports the following
|
||||
operations:
|
||||
|
||||
[table
|
||||
[[Expression] [Description]]
|
||||
[[Gen::result_type] [The type that is the result of invoking operator().
|
||||
This can be either an arithmetic type, or a std::pair<> of arithmetic types.]]
|
||||
[[g()] [Returns an object of type Gen::result_type.
|
||||
|
||||
Each time this operator is called then the next pair of /a/ and /b/
|
||||
values is returned. Or, if result_type is an arithmetic type,
|
||||
then the next /b/ value is returned and all the /a/ values
|
||||
are assumed to 1.]]
|
||||
]
|
||||
|
||||
In all the continued fraction evaluation functions the /tolerance/ parameter is the
|
||||
precision desired in the result, evaluation of the fraction will
|
||||
continue until the last term evaluated leaves the relative error in the result
|
||||
less than /tolerance/. The deprecated interfaces take a number of digits precision
|
||||
here, internally they just convert this to a tolerance and forward call.
|
||||
|
||||
If the optional /max_terms/ parameter is specified then no more than /max_terms/
|
||||
calls to the generator will be made, and on output,
|
||||
/max_terms/ will be set to actual number of
|
||||
calls made. This facility is particularly useful when profiling a continued
|
||||
fraction for convergence.
|
||||
|
||||
[h4 Implementation]
|
||||
|
||||
Internally these algorithms all use the modified Lentz algorithm: refer to
|
||||
Numeric Recipes in C++, W. H. Press et all, chapter 5,
|
||||
(especially 5.2 Evaluation of continued fractions, p 175 - 179)
|
||||
for more information, also
|
||||
Lentz, W.J. 1976, Applied Optics, vol. 15, pp. 668-671.
|
||||
|
||||
[h4 Examples]
|
||||
|
||||
The [@http://en.wikipedia.org/wiki/Golden_ratio golden ratio phi = 1.618033989...]
|
||||
can be computed from the simplest continued fraction of all:
|
||||
|
||||
[equation fraction3]
|
||||
|
||||
We begin by defining a generator function:
|
||||
|
||||
template <class T>
|
||||
struct golden_ratio_fraction
|
||||
{
|
||||
typedef T result_type;
|
||||
|
||||
result_type operator()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
The golden ratio can then be computed to double precision using:
|
||||
|
||||
continued_fraction_a(
|
||||
golden_ratio_fraction<double>(),
|
||||
std::numeric_limits<double>::epsilon());
|
||||
|
||||
It's more usual though to have to define both the /a/'s and the /b/'s
|
||||
when evaluating special functions by continued fractions, for example
|
||||
the tan function is defined by:
|
||||
|
||||
[equation fraction4]
|
||||
|
||||
So its generator object would look like:
|
||||
|
||||
template <class T>
|
||||
struct tan_fraction
|
||||
{
|
||||
private:
|
||||
T a, b;
|
||||
public:
|
||||
tan_fraction(T v)
|
||||
: a(-v*v), b(-1)
|
||||
{}
|
||||
|
||||
typedef std::pair<T,T> result_type;
|
||||
|
||||
std::pair<T,T> operator()()
|
||||
{
|
||||
b += 2;
|
||||
return std::make_pair(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
Notice that if the continuant is subtracted from the /b/ terms,
|
||||
as is the case here, then all the /a/ terms returned by the generator
|
||||
will be negative. The tangent function can now be evaluated using:
|
||||
|
||||
template <class T>
|
||||
T tan(T a)
|
||||
{
|
||||
tan_fraction<T> fract(a);
|
||||
return a / continued_fraction_b(fract, std::numeric_limits<T>::epsilon());
|
||||
}
|
||||
|
||||
Notice that this time we're using the "_b" suffixed version to evaluate
|
||||
the fraction: we're removing the leading /a/ term during fraction evaluation
|
||||
as it's different from all the others.
|
||||
|
||||
[endsect][/section:cf Continued Fraction Evaluation]
|
||||
|
||||
[/
|
||||
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).
|
||||
]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[section:internals_overview Overview]
|
||||
|
||||
This section contains internal utilities used by the library's implementation
|
||||
along with tools used in development and testing. These tools have
|
||||
limitied documentation, but now have quite stable interfaces
|
||||
and may also be useful outside Boost.Math.
|
||||
|
||||
There is no doubt that these components can be improved, but they are also
|
||||
largely incidental to the main purpose of this library.
|
||||
|
||||
These tools are designed to "just get the job done", and receive minimal
|
||||
documentation here, in the hopes that they will help stimulate further
|
||||
submissions to this library.
|
||||
|
||||
[endsect] [/section:internals_overview Overview]
|
||||
|
||||
[/
|
||||
Copyright 2006, 2010, 2013, 2014 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).
|
||||
]
|
||||
@@ -0,0 +1,167 @@
|
||||
[section:minimax Minimax Approximations and the Remez Algorithm]
|
||||
|
||||
The directory libs/math/minimax contains a command line driven
|
||||
program for the generation of minimax approximations using the Remez
|
||||
algorithm. Both polynomial and rational approximations are supported,
|
||||
although the latter are tricky to converge: it is not uncommon for
|
||||
convergence of rational forms to fail. No such limitations are present
|
||||
for polynomial approximations which should always converge smoothly.
|
||||
|
||||
It's worth stressing that developing rational approximations to functions
|
||||
is often not an easy task, and one to which many books have been devoted.
|
||||
To use this tool, you will need to have a reasonable grasp of what the Remez
|
||||
algorithm is, and the general form of the approximation you want to achieve.
|
||||
|
||||
Unless you already familar with the Remez method,
|
||||
you should first read the [link math_toolkit.remez
|
||||
brief background article explaining the principles behind the
|
||||
Remez algorithm].
|
||||
|
||||
The program consists of two parts:
|
||||
|
||||
[variablelist
|
||||
[[main.cpp][Contains the command line parser, and all the calls to the Remez code.]]
|
||||
[[f.cpp][Contains the function to approximate.]]
|
||||
]
|
||||
|
||||
Therefore to use this tool, you must modify f.cpp to return the function to
|
||||
approximate. The tools supports multiple function approximations within
|
||||
the same compiled program: each as a separate variant:
|
||||
|
||||
NTL::RR f(const NTL::RR& x, int variant);
|
||||
|
||||
Returns the value of the function /variant/ at point /x/. So if you
|
||||
wish you can just add the function to approximate as a new variant
|
||||
after the existing examples.
|
||||
|
||||
In addition to those two files, the program needs to be linked to
|
||||
a [link math_toolkit.high_precision.use_ntl patched NTL library to compile].
|
||||
|
||||
Note that the function /f/ must return the rational part of the
|
||||
approximation: for example if you are approximating a function
|
||||
/f(x)/ then it is quite common to use:
|
||||
|
||||
f(x) = g(x)(Y + R(x))
|
||||
|
||||
where /g(x)/ is the dominant part of /f(x)/, /Y/ is some constant, and
|
||||
/R(x)/ is the rational approximation part, usually optimised for a low
|
||||
absolute error compared to |Y|.
|
||||
|
||||
In this case you would define /f/ to return ['f(x)/g(x)] and then set the
|
||||
y-offset of the approximation to /Y/ (see command line options below).
|
||||
|
||||
Many other forms are possible, but in all cases the objective is to
|
||||
split /f(x)/ into a dominant part that you can evaluate easily using
|
||||
standard math functions, and a smooth and slowly changing rational approximation
|
||||
part. Refer to your favourite textbook for more examples.
|
||||
|
||||
Command line options for the program are as follows:
|
||||
|
||||
[variablelist
|
||||
[[variant N][Sets the current function variant to N. This allows multiple functions
|
||||
that are to be approximated to be compiled into the same executable.
|
||||
Defaults to 0.]]
|
||||
[[range a b][Sets the domain for the approximation to the range \[a,b\], defaults
|
||||
to \[0,1\].]]
|
||||
[[relative][Sets the Remez code to optimise for relative error. This is the default
|
||||
at program startup. Note that relative error can only be used
|
||||
if f(x) has no roots over the range being optimised.]]
|
||||
[[absolute][Sets the Remez code to optimise for absolute error.]]
|
||||
[[pin \[true|false\]]["Pins" the code so that the rational approximation
|
||||
passes through the origin. Obviously only set this to
|
||||
/true/ if R(0) must be zero. This is typically used when
|
||||
trying to preserve a root at \[0,0\] while also optimising
|
||||
for relative error.]]
|
||||
[[order N D][Sets the order of the approximation to /N/ in the numerator and /D/
|
||||
in the denominator. If /D/ is zero then the result will be a polynomial
|
||||
approximation. There will be N+D+2 coefficients in total, the first
|
||||
coefficient of the numerator is zero if /pin/ was set to true, and the
|
||||
first coefficient of the denominator is always one.]]
|
||||
[[working-precision N][Sets the working precision of NTL::RR to /N/ binary digits. Defaults to 250.]]
|
||||
[[target-precision N][Sets the precision of printed output to /N/ binary digits:
|
||||
set to the same number of digits as the type that will be used to
|
||||
evaluate the approximation. Defaults to 53 (for double precision).]]
|
||||
[[skew val]["Skews" the initial interpolated control points towards one
|
||||
end or the other of the range. Positive values skew the
|
||||
initial control points towards the left hand side of the
|
||||
range, and negative values towards the right hand side.
|
||||
If an approximation won't converge (a common situation)
|
||||
try adjusting the skew parameter until the first step yields
|
||||
the smallest possible error. /val/ should be in the range
|
||||
\[-100,+100\], the default is zero.]]
|
||||
[[brake val][Sets a brake on each step so that the change in the
|
||||
control points is braked by /val%/. Defaults to 50,
|
||||
try a higher value if an approximation won't converge,
|
||||
or a lower value to get speedier convergence.]]
|
||||
[[x-offset val][Sets the x-offset to /val/: the approximation will
|
||||
be generated for `f(S * (x + X)) + Y` where /X/ is the
|
||||
x-offset, /S/ is the x-scale
|
||||
and /Y/ is the y-offset. Defaults to zero. To avoid
|
||||
rounding errors, take care to specify a value that can
|
||||
be exactly represented as a floating point number.]]
|
||||
[[x-scale val][Sets the x-scale to /val/: the approximation will
|
||||
be generated for `f(S * (x + X)) + Y` where /S/ is the
|
||||
x-scale, /X/ is the x-offset
|
||||
and /Y/ is the y-offset. Defaults to one. To avoid
|
||||
rounding errors, take care to specify a value that can
|
||||
be exactly represented as a floating point number.]]
|
||||
[[y-offset val][Sets the y-offset to /val/: the approximation will
|
||||
be generated for `f(S * (x + X)) + Y` where /X/
|
||||
is the x-offset, /S/ is the x-scale
|
||||
and /Y/ is the y-offset. Defaults to zero. To avoid
|
||||
rounding errors, take care to specify a value that can
|
||||
be exactly represented as a floating point number.]]
|
||||
[[y-offset auto][Sets the y-offset to the average value of f(x)
|
||||
evaluated at the two endpoints of the range plus the midpoint
|
||||
of the range. The calculated value is deliberately truncated
|
||||
to /float/ precision (and should be stored as a /float/
|
||||
in your code). The approximation will
|
||||
be generated for `f(x + X) + Y` where /X/ is the x-offset
|
||||
and /Y/ is the y-offset. Defaults to zero.]]
|
||||
[[graph N][Prints N evaluations of f(x) at evenly spaced points over the
|
||||
range being optimised. If unspecified then /N/ defaults
|
||||
to 3. Use to check that f(x) is indeed smooth over the range
|
||||
of interest.]]
|
||||
[[step N][Performs /N/ steps, or one step if /N/ is unspecified.
|
||||
After each step prints: the peek error at the extrema of
|
||||
the error function of the approximation,
|
||||
the theoretical error term solved for on the last step,
|
||||
and the maximum relative change in the location of the
|
||||
Chebyshev control points. The approximation is converged on the
|
||||
minimax solution when the two error terms are (approximately)
|
||||
equal, and the change in the control points has decreased to
|
||||
a suitably small value.]]
|
||||
[[test \[float|double|long\]][Tests the current approximation at float,
|
||||
double, or long double precision. Useful to check for rounding
|
||||
errors in evaluating the approximation at fixed precision.
|
||||
Tests are conducted at the extrema of the error function of the
|
||||
approximation, and at the zeros of the error function.]]
|
||||
[[test \[float|double|long\] N] [Tests the current approximation at float,
|
||||
double, or long double precision. Useful to check for rounding
|
||||
errors in evaluating the approximation at fixed precision.
|
||||
Tests are conducted at N evenly spaced points over the range
|
||||
of the approximation. If none of \[float|double|long\] are specified
|
||||
then tests using NTL::RR, this can be used to obtain the error
|
||||
function of the approximation.]]
|
||||
[[rescale a b][Takes the current Chebeshev control points, and rescales them
|
||||
over a new interval \[a,b\]. Sometimes this can be used to obtain
|
||||
starting control points for an approximation that can not otherwise be
|
||||
converged.]]
|
||||
[[rotate][Moves one term from the numerator to the denominator, but keeps the
|
||||
Chebyshev control points the same. Sometimes this can be used to obtain
|
||||
starting control points for an approximation that can not otherwise be
|
||||
converged.]]
|
||||
[[info][Prints out the current approximation: the location of the zeros of the
|
||||
error function, the location of the Chebyshev control points, the
|
||||
x and y offsets, and of course the coefficients of the polynomials.]]
|
||||
]
|
||||
|
||||
|
||||
[endsect][/section:minimax Minimax Approximations and the Remez Algorithm]
|
||||
|
||||
[/
|
||||
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).
|
||||
]
|
||||
@@ -0,0 +1,179 @@
|
||||
[section:polynomials Polynomials]
|
||||
|
||||
[h4 Synopsis]
|
||||
|
||||
``
|
||||
#include <boost/math/tools/polynomial.hpp>
|
||||
``
|
||||
|
||||
namespace boost { namespace math {
|
||||
namespace tools {
|
||||
|
||||
template <class T>
|
||||
class polynomial
|
||||
{
|
||||
public:
|
||||
// typedefs:
|
||||
typedef typename std::vector<T>::value_type value_type;
|
||||
typedef typename std::vector<T>::size_type size_type;
|
||||
|
||||
// construct:
|
||||
polynomial(){}
|
||||
template <class U>
|
||||
polynomial(const U* data, unsigned order);
|
||||
template <class I>
|
||||
polynomial(I first, I last);
|
||||
template <class U>
|
||||
explicit polynomial(const U& point);
|
||||
polynomial(std::initializer_list<T> l); // C++11
|
||||
|
||||
// access:
|
||||
size_type size()const;
|
||||
size_type degree()const;
|
||||
value_type& operator[](size_type i);
|
||||
const value_type& operator[](size_type i)const;
|
||||
std::vector<T> const& data() const;
|
||||
std::vector<T>& data();
|
||||
explicit operator bool() const;
|
||||
|
||||
// modify:
|
||||
void set_zero();
|
||||
|
||||
// operators:
|
||||
template <class U>
|
||||
polynomial& operator +=(const U& value);
|
||||
template <class U>
|
||||
polynomial& operator -=(const U& value);
|
||||
template <class U>
|
||||
polynomial& operator *=(const U& value);
|
||||
template <class U>
|
||||
polynomial& operator /=(const U& value);
|
||||
template <class U>
|
||||
polynomial& operator %=(const U& value);
|
||||
template <class U>
|
||||
polynomial& operator +=(const polynomial<U>& value);
|
||||
template <class U>
|
||||
polynomial& operator -=(const polynomial<U>& value);
|
||||
template <class U>
|
||||
polynomial& operator *=(const polynomial<U>& value);
|
||||
template <class U>
|
||||
polynomial& operator /=(const polynomial<U>& value);
|
||||
template <class U>
|
||||
polynomial& operator %=(const polynomial<U>& value);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
polynomial<T> operator + (const polynomial<T>& a, const polynomial<T>& b);
|
||||
template <class T>
|
||||
polynomial<T> operator - (const polynomial<T>& a, const polynomial<T>& b);
|
||||
template <class T>
|
||||
polynomial<T> operator * (const polynomial<T>& a, const polynomial<T>& b);
|
||||
template <class T>
|
||||
polynomial<T> operator / (const polynomial<T>& a, const polynomial<T>& b);
|
||||
template <class T>
|
||||
polynomial<T> operator % (const polynomial<T>& a, const polynomial<T>& b);
|
||||
|
||||
template <class T, class U>
|
||||
polynomial<T> operator + (const polynomial<T>& a, const U& b);
|
||||
template <class T, class U>
|
||||
polynomial<T> operator - (const polynomial<T>& a, const U& b);
|
||||
template <class T, class U>
|
||||
polynomial<T> operator * (const polynomial<T>& a, const U& b);
|
||||
template <class T, class U>
|
||||
polynomial<T> operator / (const polynomial<T>& a, const U& b);
|
||||
template <class T, class U>
|
||||
polynomial<T> operator % (const polynomial<T>& a, const U& b);
|
||||
|
||||
template <class U, class T>
|
||||
polynomial<T> operator + (const U& a, const polynomial<T>& b);
|
||||
template <class U, class T>
|
||||
polynomial<T> operator - (const U& a, const polynomial<T>& b);
|
||||
template <class U, class T>
|
||||
polynomial<T> operator * (const U& a, const polynomial<T>& b);
|
||||
|
||||
template <class T>
|
||||
polynomial<T> operator - (const polynomial<T>& a);
|
||||
|
||||
template <class T>
|
||||
polynomial<T> operator >>= (const U& a);
|
||||
template <class T>
|
||||
polynomial<T> operator >> (polynomial<T> const &a, const U& b);
|
||||
template <class T>
|
||||
polynomial<T> operator <<= (const U& a);
|
||||
template <class T>
|
||||
polynomial<T> operator << (polynomial<T> const &a, const U& b);
|
||||
|
||||
template <class T>
|
||||
bool operator == (const polynomial<T> &a, const polynomial<T> &b);
|
||||
template <class T>
|
||||
bool operator != (const polynomial<T> &a, const polynomial<T> &b);
|
||||
|
||||
template <class T>
|
||||
polynomial<T> pow(polynomial<T> base, int exp);
|
||||
|
||||
template <class charT, class traits, class T>
|
||||
std::basic_ostream<charT, traits>& operator <<
|
||||
(std::basic_ostream<charT, traits>& os, const polynomial<T>& poly);
|
||||
|
||||
template <typename T>
|
||||
std::pair< polynomial<T>, polynomial<T> >
|
||||
quotient_remainder(const polynomial<T>& a, const polynomial<T>& b);
|
||||
|
||||
} // namespace tools
|
||||
}} // namespace boost { namespace math
|
||||
|
||||
[h4 Description]
|
||||
|
||||
This is a somewhat trivial class for polynomial manipulation.
|
||||
|
||||
See
|
||||
|
||||
* [@https://en.wikipedia.org/wiki/Polynomial Polynomial] and
|
||||
* Donald E. Knuth, The Art of Computer Programming: Volume 2, Third edition, (1998)
|
||||
Chapter 4.6.1, Algorithm D: Division of polynomials over a field.
|
||||
|
||||
Implementation is currently of the "naive" variety, with [bigo](N[super 2])
|
||||
multiplication, for example. This class should not be used in
|
||||
high-performance computing environments: it is intended for the
|
||||
simple manipulation of small polynomials, typically generated
|
||||
for special function approximation.
|
||||
|
||||
It does has division for polynomials over a [@https://en.wikipedia.org/wiki/Field_%28mathematics%29 field]
|
||||
(here floating point, complex, etc)
|
||||
and over a unique factorization domain (integers).
|
||||
Division of polynomials over a field is compatible with
|
||||
[@https://en.wikipedia.org/wiki/Euclidean_algorithm Euclidean GCD].
|
||||
|
||||
Advanced manipulations: the FFT, factorisation etc are
|
||||
not currently provided. Submissions for these are of course welcome :-)
|
||||
|
||||
[h4:polynomial_examples Polynomial Arithmetic Examples]
|
||||
|
||||
[import ../../example/polynomial_arithmetic.cpp]
|
||||
|
||||
[polynomial_arithmetic_0]
|
||||
[polynomial_arithmetic_1]
|
||||
|
||||
[polynomial_arithmetic_2]
|
||||
for output:
|
||||
[polynomial_output_1]
|
||||
|
||||
[polynomial_arithmetic_3]
|
||||
for output
|
||||
[polynomial_output_2]
|
||||
|
||||
[h5 Division, Quotient and Remainder]
|
||||
[polynomial_arithmetic_4]
|
||||
|
||||
The source code is at [@../../example/polynomial_arithmetic.cpp polynomial_arithmetic.cpp]
|
||||
|
||||
[endsect] [/section:polynomials Polynomials]
|
||||
|
||||
[/
|
||||
Copyright 2006 John Maddock and Paul A. Bristow.
|
||||
Copyright 2015 Jeremy William Murphy.
|
||||
|
||||
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).
|
||||
]
|
||||
@@ -0,0 +1,176 @@
|
||||
[section:rational Polynomial and Rational Function Evaluation]
|
||||
|
||||
[h4 Synopsis]
|
||||
|
||||
``
|
||||
#include <boost/math/tools/rational.hpp>
|
||||
``
|
||||
|
||||
// Polynomials:
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_polynomial(const T(&poly)[N], const V& val);
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_polynomial(const boost::array<T,N>& poly, const V& val);
|
||||
|
||||
template <class T, class U>
|
||||
U evaluate_polynomial(const T* poly, U z, std::size_t count);
|
||||
|
||||
// Even polynomials:
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_even_polynomial(const T(&poly)[N], const V& z);
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_even_polynomial(const boost::array<T,N>& poly, const V& z);
|
||||
|
||||
template <class T, class U>
|
||||
U evaluate_even_polynomial(const T* poly, U z, std::size_t count);
|
||||
|
||||
// Odd polynomials
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_odd_polynomial(const T(&a)[N], const V& z);
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_odd_polynomial(const boost::array<T,N>& a, const V& z);
|
||||
|
||||
template <class T, class U>
|
||||
U evaluate_odd_polynomial(const T* poly, U z, std::size_t count);
|
||||
|
||||
// Rational Functions:
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_rational(const T(&a)[N], const T(&b)[N], const V& z);
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_rational(const boost::array<T,N>& a, const boost::array<T,N>& b, const V& z);
|
||||
|
||||
template <class T, class U, class V>
|
||||
V evaluate_rational(const T* num, const U* denom, V z, unsigned count);
|
||||
|
||||
[h4 Description]
|
||||
|
||||
Each of the functions come in three variants: a pair of overloaded functions
|
||||
where the order of the polynomial or rational function is evaluated at
|
||||
compile time, and an overload that accepts a runtime variable for the size
|
||||
of the coefficient array. Generally speaking, compile time evaluation of the
|
||||
array size results in better type safety, is less prone to programmer errors,
|
||||
and may result in better optimised code. The polynomial evaluation functions
|
||||
in particular, are specialised for various array sizes, allowing for
|
||||
loop unrolling, and one hopes, optimal inline expansion.
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_polynomial(const T(&poly)[N], const V& val);
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_polynomial(const boost::array<T,N>& poly, const V& val);
|
||||
|
||||
template <class T, class U>
|
||||
U evaluate_polynomial(const T* poly, U z, std::size_t count);
|
||||
|
||||
Evaluates the [@http://en.wikipedia.org/wiki/Polynomial polynomial] described by
|
||||
the coefficients stored in /poly/.
|
||||
|
||||
If the size of the array is specified at runtime, then the polynomial
|
||||
most have order /count-1/ with /count/ coefficients. Otherwise it has
|
||||
order /N-1/ with /N/ coefficients.
|
||||
|
||||
Coefficients should be stored such that the coefficients for the x[super i] terms
|
||||
are in poly[i].
|
||||
|
||||
The types of the coefficients and of variable
|
||||
/z/ may differ as long as /*poly/ is convertible to type /U/.
|
||||
This allows, for example, for the coefficient table
|
||||
to be a table of integers if this is appropriate.
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_even_polynomial(const T(&poly)[N], const V& z);
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_even_polynomial(const boost::array<T,N>& poly, const V& z);
|
||||
|
||||
template <class T, class U>
|
||||
U evaluate_even_polynomial(const T* poly, U z, std::size_t count);
|
||||
|
||||
As above, but evaluates an even polynomial: one where all the powers
|
||||
of /z/ are even numbers. Equivalent to calling
|
||||
`evaluate_polynomial(poly, z*z, count)`.
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_odd_polynomial(const T(&a)[N], const V& z);
|
||||
|
||||
template <std::size_t N, class T, class V>
|
||||
V evaluate_odd_polynomial(const boost::array<T,N>& a, const V& z);
|
||||
|
||||
template <class T, class U>
|
||||
U evaluate_odd_polynomial(const T* poly, U z, std::size_t count);
|
||||
|
||||
As above but evaluates a polynomial where all the powers are odd numbers.
|
||||
Equivalent to `evaluate_polynomial(poly+1, z*z, count-1) * z + poly[0]`.
|
||||
|
||||
template <std::size_t N, class T, class U, class V>
|
||||
V evaluate_rational(const T(&num)[N], const U(&denom)[N], const V& z);
|
||||
|
||||
template <std::size_t N, class T, class U, class V>
|
||||
V evaluate_rational(const boost::array<T,N>& num, const boost::array<U,N>& denom, const V& z);
|
||||
|
||||
template <class T, class U, class V>
|
||||
V evaluate_rational(const T* num, const U* denom, V z, unsigned count);
|
||||
|
||||
Evaluates the rational function (the ratio of two polynomials) described by
|
||||
the coefficients stored in /num/ and /demom/.
|
||||
|
||||
If the size of the array is specified at runtime then both
|
||||
polynomials most have order /count-1/ with /count/ coefficients.
|
||||
Otherwise both polynomials have order /N-1/ with /N/ coefficients.
|
||||
|
||||
Array /num/ describes the numerator, and /demon/ the denominator.
|
||||
|
||||
Coefficients should be stored such that the coefficients for the x[super i ] terms
|
||||
are in num[i] and denom[i].
|
||||
|
||||
The types of the coefficients and of variable
|
||||
/v/ may differ as long as /*num/ and /*denom/ are convertible to type /V/.
|
||||
This allows, for example, for one or both of the coefficient tables
|
||||
to be a table of integers if this is appropriate.
|
||||
|
||||
These functions are designed to safely evaluate the result, even when the value
|
||||
/z/ is very large. As such they do not take advantage of compile time array
|
||||
sizes to make any optimisations. These functions are best reserved for situations
|
||||
where /z/ may be large: if you can be sure that numerical overflow will not occur
|
||||
then polynomial evaluation with compile-time array sizes may offer slightly
|
||||
better performance.
|
||||
|
||||
[h4 Implementation]
|
||||
|
||||
Polynomials are evaluated by
|
||||
[@http://en.wikipedia.org/wiki/Horner_algorithm Horners method].
|
||||
If the array size is known at
|
||||
compile time then the functions dispatch to size-specific implementations
|
||||
that unroll the evaluation loop.
|
||||
|
||||
Rational evaluation is by
|
||||
[@http://en.wikipedia.org/wiki/Horner_algorithm Horners method]:
|
||||
with the two polynomials being evaluated
|
||||
in parallel to make the most of the processors floating-point pipeline.
|
||||
If /v/ is greater than one, then the polynomials are evaluated in reverse
|
||||
order as polynomials in ['1\/v]: this avoids unnecessary numerical overflow when the
|
||||
coefficients are large.
|
||||
|
||||
Both the polynomial and rational function evaluation algorithms can be
|
||||
tuned using various configuration macros to provide optimal performance
|
||||
for a particular combination of compiler and platform. This includes
|
||||
support for second-order Horner's methods. The various options are
|
||||
[link math_toolkit.tuning documented here]. However, the performance
|
||||
benefits to be gained from these are marginal on most current hardware,
|
||||
consequently it's best to run the
|
||||
[link math_toolkit.perf_test_app performance test application] before
|
||||
changing the default settings.
|
||||
|
||||
[endsect] [/section:rational Polynomial and Rational Function Evaluation]
|
||||
|
||||
[/
|
||||
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).
|
||||
]
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
[section:error_test Relative Error and Testing]
|
||||
|
||||
[h4 Synopsis]
|
||||
|
||||
``
|
||||
#include <boost/math/tools/test.hpp>
|
||||
``
|
||||
|
||||
[important
|
||||
The header `boost/math/tools/test.hpp` is located under `libs/math/include_private`
|
||||
and is not installed to the usual locations by default, you will need to add `libs/math/include_private`
|
||||
to your compiler's include path in order to use this header.]
|
||||
|
||||
template <class T>
|
||||
T relative_error(T a, T b);
|
||||
|
||||
template <class A, class F1, class F2>
|
||||
test_result<see-below> test(const A& a, F1 test_func, F2 expect_func);
|
||||
|
||||
[h4 Description]
|
||||
|
||||
template <class T>
|
||||
T relative_error(T a, T v);
|
||||
|
||||
Returns the relative error between /a/ and /v/ using the usual formula:
|
||||
|
||||
[equation error1]
|
||||
|
||||
In addition the value returned is zero if:
|
||||
|
||||
* Both /a/ and /v/ are infinite.
|
||||
* Both /a/ and /v/ are denormalised numbers or zero.
|
||||
|
||||
Otherwise if only one of /a/ and /v/ is zero then the value returned is 1.
|
||||
|
||||
template <class A, class F1, class F2>
|
||||
test_result<see-below> test(const A& a, F1 test_func, F2 expect_func);
|
||||
|
||||
This function is used for testing a function against tabulated test data.
|
||||
|
||||
The return type contains statistical data on the relative errors (max, mean,
|
||||
variance, and the number of test cases etc), as well as the row of test data that
|
||||
caused the largest relative error. Public members of type test_result are:
|
||||
|
||||
[variablelist
|
||||
[[`unsigned worst()const;`][Returns the row at which the worst error occurred.]]
|
||||
[[`T min()const;`][Returns the smallest relative error found.]]
|
||||
[[`T max()const;`][Returns the largest relative error found.]]
|
||||
[[`T mean()const;`][Returns the mean error found.]]
|
||||
[[`boost::uintmax_t count()const;`][Returns the number of test cases.]]
|
||||
[[`T variance()const;`][Returns the variance of the errors found.]]
|
||||
[[`T variance1()const;`][Returns the unbiased variance of the errors found.]]
|
||||
[[`T rms()const`][Returns the Root Mean Square, or quadratic mean of the errors.]]
|
||||
[[`test_result& operator+=(const test_result& t)`][Combines two test_result's into
|
||||
a single result.]]
|
||||
]
|
||||
|
||||
The template parameter of test_result, is the same type as the values in the two
|
||||
dimensional array passed to function /test/, roughly that's
|
||||
`A::value_type::value_type`.
|
||||
|
||||
Parameter /a/ is a matrix of test data: and must be a standard library Sequence type,
|
||||
that contains another Sequence type:
|
||||
typically it will be a two dimensional instance of
|
||||
[^boost::array]. Each row
|
||||
of /a/ should contain all the parameters that are passed to the function
|
||||
under test as well as the expected result.
|
||||
|
||||
Parameter /test_func/ is the function under test, it is invoked with each row
|
||||
of test data in /a/. Typically type F1 is created with Boost.Lambda: see
|
||||
the example below.
|
||||
|
||||
Parameter /expect_func/ is a functor that extracts the expected result
|
||||
from a row of test data in /a/. Typically type F2 is created with Boost.Lambda: see
|
||||
the example below.
|
||||
|
||||
If the function under test returns a non-finite value when a finite result is
|
||||
expected, or if a gross error is found, then a message is sent to `std::cerr`,
|
||||
and a call to BOOST_ERROR() made (which means that including this header requires
|
||||
you use Boost.Test). This is mainly a debugging/development aid
|
||||
(and a good place for a breakpoint).
|
||||
|
||||
[h4 Example]
|
||||
|
||||
Suppose we want to test the tgamma and lgamma functions, we can create a
|
||||
two dimensional matrix of test data, each row is one test case, and contains
|
||||
three elements: the input value, and the expected results for the tgamma and
|
||||
lgamma functions respectively.
|
||||
|
||||
static const boost::array<boost::array<TestType, 3>, NumberOfTests>
|
||||
factorials = {
|
||||
/* big array of test data goes here */
|
||||
};
|
||||
|
||||
Now we can invoke the test function to test tgamma:
|
||||
|
||||
using namespace boost::math::tools;
|
||||
using namespace boost::lambda;
|
||||
|
||||
// get a pointer to the function under test:
|
||||
TestType (*funcp)(TestType) = boost::math::tgamma;
|
||||
|
||||
// declare something to hold the result:
|
||||
test_result<TestType> result;
|
||||
//
|
||||
// and test tgamma against data:
|
||||
//
|
||||
result = test(
|
||||
factorials,
|
||||
bind(funcp, ret<TestType>(_1[0])), // calls tgamma with factorials[row][0]
|
||||
ret<TestType>(_1[1]) // extracts the expected result from factorials[row][1]
|
||||
);
|
||||
//
|
||||
// Print out some results:
|
||||
//
|
||||
std::cout << "The Mean was " << result.mean() << std::endl;
|
||||
std::cout << "The worst error was " << (result.max)() << std::endl;
|
||||
std::cout << "The worst error was at row " << result.worst_case() << std::endl;
|
||||
//
|
||||
// same again with lgamma this time:
|
||||
//
|
||||
funcp = boost::math::lgamma;
|
||||
result = test(
|
||||
factorials,
|
||||
bind(funcp, ret<TestType>(_1[0])), // calls tgamma with factorials[row][0]
|
||||
ret<TestType>(_1[2]) // extracts the expected result from factorials[row][2]
|
||||
);
|
||||
//
|
||||
// etc ...
|
||||
//
|
||||
|
||||
[endsect][/section:error_test Relative Error and Testing]
|
||||
|
||||
[/
|
||||
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).
|
||||
]
|
||||
@@ -0,0 +1,141 @@
|
||||
[section:series_evaluation Series Evaluation]
|
||||
|
||||
[h4 Synopsis]
|
||||
|
||||
``
|
||||
#include <boost/math/tools/series.hpp>
|
||||
``
|
||||
|
||||
namespace boost{ namespace math{ namespace tools{
|
||||
|
||||
template <class Functor, class U, class V>
|
||||
inline typename Functor::result_type sum_series(Functor& func, const U& tolerance, boost::uintmax_t& max_terms, const V& init_value);
|
||||
|
||||
template <class Functor, class U, class V>
|
||||
inline typename Functor::result_type sum_series(Functor& func, const U& tolerance, boost::uintmax_t& max_terms);
|
||||
|
||||
//
|
||||
// The following interfaces are now deprecated:
|
||||
//
|
||||
template <class Functor>
|
||||
typename Functor::result_type sum_series(Functor& func, int bits);
|
||||
|
||||
template <class Functor>
|
||||
typename Functor::result_type sum_series(Functor& func, int bits, boost::uintmax_t& max_terms);
|
||||
|
||||
template <class Functor, class U>
|
||||
typename Functor::result_type sum_series(Functor& func, int bits, U init_value);
|
||||
|
||||
template <class Functor, class U>
|
||||
typename Functor::result_type sum_series(Functor& func, int bits, boost::uintmax_t& max_terms, U init_value);
|
||||
|
||||
template <class Functor>
|
||||
typename Functor::result_type kahan_sum_series(Functor& func, int bits);
|
||||
|
||||
template <class Functor>
|
||||
typename Functor::result_type kahan_sum_series(Functor& func, int bits, boost::uintmax_t& max_terms);
|
||||
|
||||
}}} // namespaces
|
||||
|
||||
[h4 Description]
|
||||
|
||||
These algorithms are intended for the
|
||||
[@http://en.wikipedia.org/wiki/Series_%28mathematics%29 summation of infinite series].
|
||||
|
||||
Each of the algorithms takes a nullary-function object as the first argument:
|
||||
the function object will be repeatedly invoked to pull successive terms from
|
||||
the series being summed.
|
||||
|
||||
The second argument is the precision required,
|
||||
summation will stop when the next term is less than
|
||||
/tolerance/ times the result. The deprecated versions of sum_series
|
||||
take an integer number of bits here - internally they just convert this to
|
||||
a tolerance and forward the call.
|
||||
|
||||
The third argument /max_terms/ sets an upper limit on the number
|
||||
of terms of the series to evaluate. In addition, on exit the function will
|
||||
set /max_terms/ to the actual number of terms of the series that were
|
||||
evaluated: this is particularly useful for profiling the convergence
|
||||
properties of a new series.
|
||||
|
||||
The final optional argument /init_value/ is the initial value of the sum
|
||||
to which the terms of the series should be added. This is useful in two situations:
|
||||
|
||||
* Where the first value of the series has a different formula to successive
|
||||
terms. In this case the first value in the series can be passed as the
|
||||
last argument and the logic of the function object can then be simplified
|
||||
to return subsequent terms.
|
||||
* Where the series is being added (or subtracted) from some other value:
|
||||
termination of the series will likely occur much more rapidly if that other
|
||||
value is passed as the last argument. For example, there are several functions
|
||||
that can be expressed as /1 - S(z)/ where S(z) is an infinite series. In this
|
||||
case, pass -1 as the last argument and then negate the result of the summation
|
||||
to get the result of /1 - S(z)/.
|
||||
|
||||
The two /kahan_sum_series/ variants of these algorithms maintain a carry term
|
||||
that corrects for roundoff error during summation.
|
||||
They are inspired by the
|
||||
[@http://en.wikipedia.org/wiki/Kahan_Summation_Algorithm /Kahan Summation Formula/]
|
||||
that appears in
|
||||
[@http://docs.sun.com/source/806-3568/ncg_goldberg.html What Every Computer Scientist Should Know About Floating-Point Arithmetic].
|
||||
However, it should be pointed out that there are very few series that require
|
||||
summation in this way.
|
||||
|
||||
[h4 Example]
|
||||
|
||||
Let's suppose we want to implement /log(1+x)/ via its infinite series,
|
||||
|
||||
[equation log1pseries]
|
||||
|
||||
We begin by writing a small function object to return successive terms
|
||||
of the series:
|
||||
|
||||
template <class T>
|
||||
struct log1p_series
|
||||
{
|
||||
// we must define a result_type typedef:
|
||||
typedef T result_type;
|
||||
|
||||
log1p_series(T x)
|
||||
: k(0), m_mult(-x), m_prod(-1){}
|
||||
|
||||
T operator()()
|
||||
{
|
||||
// This is the function operator invoked by the summation
|
||||
// algorithm, the first call to this operator should return
|
||||
// the first term of the series, the second call the second
|
||||
// term and so on.
|
||||
m_prod *= m_mult;
|
||||
return m_prod / ++k;
|
||||
}
|
||||
|
||||
private:
|
||||
int k;
|
||||
const T m_mult;
|
||||
T m_prod;
|
||||
};
|
||||
|
||||
Implementing log(1+x) is now fairly trivial:
|
||||
|
||||
template <class T>
|
||||
T log1p(T x)
|
||||
{
|
||||
// We really should add some error checking on x here!
|
||||
assert(std::fabs(x) < 1);
|
||||
|
||||
// Construct the series functor:
|
||||
log1p_series<T> s(x);
|
||||
// Set a limit on how many iterations we permit:
|
||||
boost::uintmax_t max_iter = 1000;
|
||||
// Add it up, with enough precision for full machine precision:
|
||||
return tools::sum_series(s, std::numeric_limits<T>::epsilon(), max_iter);
|
||||
}
|
||||
|
||||
[endsect][/section Series Evaluation]
|
||||
|
||||
[/
|
||||
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).
|
||||
]
|
||||
@@ -0,0 +1,446 @@
|
||||
[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).
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
[section:tuples Tuples]
|
||||
|
||||
[h4 Synopsis]
|
||||
|
||||
``
|
||||
#include <boost/math/tools/tuple.hpp>
|
||||
``
|
||||
|
||||
[h4 Description]
|
||||
|
||||
This header defines the type `boost::math::tuple`,
|
||||
the associated free functions `ignore`, `tie`, `make_tuple`, `get`,
|
||||
and associated types `tuple_size` and `tuple_element`.
|
||||
|
||||
These types and functions are aliases for:
|
||||
|
||||
* `std::tuple` etc when available, otherwise:
|
||||
* `std::tr1::tuple` etc when available, otherwise:
|
||||
* `boost::fusion::tuple` etc if the compiler supports it, otherwise:
|
||||
* `boost::tuple`.
|
||||
|
||||
So this `boost::math::tuple` is strongly recommended for maximum portability.
|
||||
|
||||
[endsect] [/section:Tuples Tuples]
|
||||
[/
|
||||
Copyright 2010 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).
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user