2
0
mirror of https://github.com/boostorg/math.git synced 2026-02-17 13:52:15 +00:00

First very rough prototype of Lambert W function, example of calculating diode current versus voltage, and some tests, including multiprecision and fixed_point types. Not yet using policies and trouble near the singularity at z=-exp(-1) and large z.

This commit is contained in:
pabristow
2016-12-22 18:30:27 +00:00
parent 78320adb7a
commit ffb025ca2c
21 changed files with 2064 additions and 495 deletions

389
test/test_lambert_w.cpp Normal file
View File

@@ -0,0 +1,389 @@
// Copyright Paul A. Bristow 2016.
// Copyright John Maddock 2016.
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
// test_lambertw.cpp
//! \brief Basic sanity tests for Lambert W function plog or productlog using algorithm from Thomas Luu.
// #include <boost/math/tools/test.hpp>
#include <boost/math/concepts/real_concept.hpp> // for real_concept
#define BOOST_TEST_MAIN
#define BOOST_LIB_DIAGNOSTIC
//#define BOOST_MATH_INSTRUMENT // #define for diagnostic output.
#include <boost/test/unit_test.hpp> // Boost.Test
#include <boost/test/floating_point_comparison.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp> // boost::multiprecision::cpp_dec_float_50
using boost::multiprecision::cpp_dec_float_50;
#include <boost/fixed_point/fixed_point.hpp>
#include <boost/math/special_functions/lambert_w.hpp> // for productlog function.
#include <boost/array.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/type_traits/is_constructible.hpp>
#include <limits>
#include <cmath>
#include <typeinfo>
#include <iostream>
#include <type_traits>
// BOOST_MATH_TEST_VALUE is used to create a test value of suitable type from a decimal digit string.
// Note there are two gotchas to this approach:
// * You need all values to be real floating-point values
// * and *MUST* include a decimal point.
// * It's slow to compile compared to a simple literal - not an issue for a
// few test values, but it's not generally usable in large tables
// where you really need everything to be statically initialized.
template <class T, class T1, class T2>
inline T create_test_value(long double val, const char*, const T1&, const T2&)
{ // Construct from long double parameter val (ignoring string/const char*)
return static_cast<T>(val);
}
template <class T>
inline T create_test_value(long double, const char* str, const boost::mpl::false_&, const boost::mpl::true_&)
{ // Construct from string (ignoring long double parameter).
return T(str);
}
template <class T>
inline T create_test_value(long double, const char* str, const boost::mpl::false_&, const boost::mpl::false_&)
{ // Create test value using from lexical cast of string.
return boost::lexical_cast<T>(str);
}
// T real type, x a decimal digits representation of a floating-point, for example: 12.34.
// x is converted to a long double by appending the letter L (to suit long double fundamental type)
// x is also passed as a const char* or string representation
// (to suit most other types that cannot be constructed from long double without loss)
// x##L is a long double version, suffix a letter L to suit long double fundamental type.
// #x is a string version to suit multiprecision constructors
// (constructing from double or long double would loose precision).
#define BOOST_MATH_TEST_VALUE(T, x) create_test_value<T>(\
x##L,\
#x,\
boost::mpl::bool_<\
std::numeric_limits<T>::is_specialized &&\
(std::numeric_limits<T>::radix == 2) && (std::numeric_limits<T>::digits\
<= std::numeric_limits<long double>::digits) && boost::is_convertible<long double, T>::value>(),\
boost::mpl::bool_<\
boost::is_constructible<T, const char*>::value>()\
)
template <class T>
void show()
{
std::cout << std::boolalpha << typeid(T).name() << std::endl
<< " is specialized " << std::numeric_limits<T>::is_specialized
<< " digits " << std::numeric_limits<T>::digits // binary digits 24
<< ", is_convertible " << boost::is_convertible<T, const char*>::value << std::endl // convertible to string
<< " is_constructible " << boost::is_constructible<T, const char*>::value // constructible from string
<< std::endl;
} // show
long double test_value = BOOST_MATH_TEST_VALUE(long double, 1.);
static const unsigned int noof_tests = 2;
static const boost::array<const char*, noof_tests> test_data =
{{
"1",
"6"
}}; // array test_data
//static const boost::array<const char*, noof_tests> test_expected =
//{ {
// BOOST_MATH_TEST_VALUE("0.56714329040978387299996866221035554975381578718651"), // Output from https://www.wolframalpha.com/input/?i=productlog(1)
// BOOST_MATH_TEST_VALUE("1.432404775898300311234078007212058694786434608804302025655") // Output from https://www.wolframalpha.com/input/?i=productlog(6)
// } }; // array test_data
//BOOST_MATH_TEST_VALUE("-0.36787944117144232159552377016146086744581113103176783450783680169746149574489980335714727434591964374662732527")
// w(-0.367879441171442321595523770161460867445811131031767834)
//
// ProductLog[-0.367879441171442321595523770161460867445811131031767834]
template <class RealType>
void test_spots(RealType)
{
// (Unused Parameter value, arbitrarily zero, only communicates the floating point type).
// test_spots(0.F); test_spots(0.); test_spots(0.L);
// // Check some bad parameters to the function,
//#ifndef BOOST_NO_EXCEPTIONS
// BOOST_MATH_CHECK_THROW(boost::math::normal_distribution<RealType> nbad1(0, 0), std::domain_error); // zero sd
// BOOST_MATH_CHECK_THROW(boost::math::normal_distribution<RealType> nbad1(0, -1), std::domain_error); // negative sd
//#else
// BOOST_MATH_CHECK_THROW(boost::math::normal_distribution<RealType>(0, 0), std::domain_error); // zero sd
// BOOST_MATH_CHECK_THROW(boost::math::normal_distribution<RealType>(0, -1), std::domain_error); // negative sd
//#endif
using boost::math::constants::expminusone;
RealType tolerance = boost::math::tools::epsilon<RealType>() * 5; // 5 eps as a fraction.
std::cout << "Testing type " << typeid(RealType).name() << std::endl;
std::cout << "Tolerance " << tolerance << std::endl;
std::cout << "Precision " << std::numeric_limits<RealType>::digits10 << " decimal digits." << std::endl;
std::cout.precision(std::numeric_limits<RealType>::digits10);
std::cout << std::showpoint << std::endl; // show trailing significant zeros.
std::cout << "-exp(-1) = " << -expminusone<RealType>() << std::endl;
show<RealType>();
using boost::math::productlog;
RealType test_value = BOOST_MATH_TEST_VALUE(RealType, 1.);
RealType expected_value = BOOST_MATH_TEST_VALUE(RealType, 0.56714329040978387299996866221035554975381578718651);
test_value = BOOST_MATH_TEST_VALUE(RealType, -0.36787944117144232159552377016146086744581113103176);
expected_value = BOOST_MATH_TEST_VALUE(RealType, -1.);
std::cout.precision(std::numeric_limits <RealType>::max_digits10);
test_value = -boost::math::constants::expminusone<RealType>();
std::cout << "test " << test_value << ", expected " << expected_value << std::endl;
BOOST_CHECK_CLOSE_FRACTION(
productlog(test_value),
expected_value,
tolerance); // OK
// This fails for reasons unclear (apparently identical test above passes)???
//BOOST_CHECK_CLOSE_FRACTION( // Check -exp(-1) = -0.367879450 = -1
// productlog(BOOST_MATH_TEST_VALUE(RealType, -0.36787944117144232159552377016146086744581113103176)),
// BOOST_MATH_TEST_VALUE(RealType, -1.),
// tolerance);
BOOST_CHECK_CLOSE_FRACTION(
productlog(BOOST_MATH_TEST_VALUE(RealType, 1.)),
BOOST_MATH_TEST_VALUE(RealType, 0.56714329040978387299996866221035554975381578718651),
// Output from https://www.wolframalpha.com/input/?i=productlog(1)
tolerance);
//Tests with some spot values computed using
//https://www.wolframalpha.com
//For example: N[ProductLog[-1], 50] outputs:
//1.3267246652422002236350992977580796601287935546380
BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 2.)),
BOOST_MATH_TEST_VALUE(RealType, 0.852605502013725491346472414695317466898453300151403508772),
// Output from https://www.wolframalpha.com/input/?i=productlog(2.)
tolerance);
BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 3.)),
BOOST_MATH_TEST_VALUE(RealType, 1.049908894964039959988697070552897904589466943706341452932),
// Output from https://www.wolframalpha.com/input/?i=productlog(3.)
tolerance);
BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 5.)),
BOOST_MATH_TEST_VALUE(RealType, 1.326724665242200223635099297758079660128793554638047479789),
// Output from https://www.wolframalpha.com/input/?i=productlog(0.5)
tolerance);
BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 0.5)),
BOOST_MATH_TEST_VALUE(RealType, 0.351733711249195826024909300929951065171464215517111804046),
// Output from https://www.wolframalpha.com/input/?i=productlog(0.5)
tolerance);
BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 6.)),
BOOST_MATH_TEST_VALUE(RealType, 1.432404775898300311234078007212058694786434608804302025655),
// Output from https://www.wolframalpha.com/input/?i=productlog(6)
tolerance);
BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 100.)),
BOOST_MATH_TEST_VALUE(RealType, 3.3856301402900501848882443645297268674916941701578),
// Output from https://www.wolframalpha.com/input/?i=productlog(100)
tolerance);
// Fails here with really big x.
/* BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 1.0e6)),
BOOST_MATH_TEST_VALUE(RealType, 11.383358086140052622000156781585004289033774706019),
// Output from https://www.wolframalpha.com/input/?i=productlog(1e6)
// tolerance * 1000); // fails exceeds 0.00015258789063
tolerance * 1000);
1>i:/modular-boost/libs/math/test/test_lambert_w.cpp(195):
error : in "test_main":
difference{2877.54} between
productlog(create_test_value<RealType>( 1.0e6L, "1.0e6", boost::mpl::bool_< std::numeric_limits<RealType>::is_specialized && (std::numeric_limits<RealType>::radix == 2) && (std::numeric_limits<RealType>::digits <= std::numeric_limits<long double>::digits) && boost::is_convertible<long double, RealType>::value>(), boost::mpl::bool_< boost::is_constructible<RealType, const char*>::value>()))
{
32767.399857
}
and
create_test_value<RealType>( 11.383358086140052622000156781585004289033774706019L, "11.383358086140052622000156781585004289033774706019", boost::mpl::bool_< std::numeric_limits<RealType>::is_specialized && (std::numeric_limits<RealType>::radix == 2) && (std::numeric_limits<RealType>::digits <= std::numeric_limits<long double>::digits) && boost::is_convertible<long double, RealType>::value>(), boost::mpl::bool_< boost::is_constructible<RealType, const char*>::value>())
{
11.383346558
}
exceeds 0.00015258789063
*/
BOOST_CHECK_CLOSE_FRACTION(productlog(BOOST_MATH_TEST_VALUE(RealType, 0.0)),
BOOST_MATH_TEST_VALUE(RealType, 0.),
tolerance);
// This is very close to the limit of -exp(1) * x
// (where the result has a non-zero imaginary part).
test_value = -expminusone<RealType>();
test_value += (std::numeric_limits<RealType>::epsilon() * 100);
expected_value = BOOST_MATH_TEST_VALUE(RealType, -1.0);
// std::cout << test_value << std::endl; // -0.367879
// Fatal error here with float.
//BOOST_CHECK_CLOSE_FRACTION(productlog(test_value),
// expected_value,
// tolerance);
/*
i:/modular-boost/libs/math/test/test_lambert_w.cpp(224):
error : in "test_main":
difference{1e+67108864} between productlog(test_value)
{
0
} and create_test_value<RealType>(
-0.99845210378072725931829498030640227316856835774851L,
"-0.99845210378072725931829498030640227316856835774851",
boost::mpl::bool_< std::numeric_limits<RealType>::is_specialized && (std::numeric_limits<RealType>::radix == 2) && (std::numeric_limits<RealType>::digits <= std::numeric_limits<long double>::digits) && boost::is_convertible<long double, RealType>::value>(), boost::mpl::bool_< boost::is_constructible<RealType, const char*>::value>())
{
-0.99845210378072725931829498030640227316856835774851
}
exceeds 5e-47
*/
/* Goes realy haywire here close to singularity.
test_value = BOOST_MATH_TEST_VALUE(RealType, -0.367879);
BOOST_CHECK_CLOSE_FRACTION(productlog(test_value),
BOOST_MATH_TEST_VALUE(RealType, -0.99845210378072725931829498030640227316856835774851),
// Output from https://www.wolframalpha.com/input/?i=productlog(2.)
// N[productlog(-0.367879), 50] = -0.99845210378072725931829498030640227316856835774851
tolerance * 100);
I:/modular-boost/libs/math/test/test_lambert_w.cpp(267): error : in "test_main":
difference
{
2.87298e+26
}
between productlog(test_value)
{
-2.86853068e+26
}
and create_test_value<RealType>( -0.99845210378072725931829498030640227316856835774851L, "-0.99845210378072725931829498030640227316856835774851", boost::mpl::bool_< std::numeric_limits<RealType>::is_specialized && (std::numeric_limits<RealType>::radix == 2) && (std::numeric_limits<RealType>::digits <= std::numeric_limits<long double>::digits) && boost::is_convertible<long double, RealType>::value>(), boost::mpl::bool_< boost::is_constructible<RealType, const char*>::value>())
{
-0.998452127
}
exceeds 5.96046448e-05
*/
// Checks on input that should throw.
/* This should throw when implemented.
BOOST_CHECK_CLOSE_FRACTION(productlog(-0.5),
BOOST_MATH_TEST_VALUE(RealType, ),
// Output from https://www.wolframalpha.com/input/?i=productlog(-0.5)
tolerance);
*/
} //template <class RealType>void test_spots(RealType)
BOOST_AUTO_TEST_CASE(test_main)
{
BOOST_MATH_CONTROL_FP;
std::cout << "Tests run with " << BOOST_COMPILER << ", "
<< BOOST_STDLIB << ", Boost Platform " << BOOST_PLATFORM << ", MSVC compiler "
<< BOOST_MSVC_FULL_VER << std::endl;
// Fundamental built-in types:
test_spots(0.0F); // float
test_spots(0.0); // double
if (sizeof(long double) > sizeof (double))
{ // Avoid pointless re-testing if double and long double are identical (for example, MSVC).
test_spots(0.0L); // long double
}
// Multiprecision types:
test_spots(static_cast<boost::multiprecision::cpp_dec_float_50>(0));
test_spots(static_cast<boost::multiprecision::cpp_bin_float_quad>(0));
// Fixed-point types:
test_spots(static_cast<boost::fixed_point::negatable<15,-16> >(0));
//test_spots(boost::math::concepts::real_concept(0.1)); // "real_concept" - was OK.
// link fails - need to add as a permanent constant.
/*
test_lambert_w.obj : error LNK2001 : unresolved external symbol "private: static class boost::math::concepts::real_concept __cdecl boost::math::constants::detail::constant_expminusone<class boost::math::concepts::real_concept>::compute<0>(void)" (? ? $compute@$0A@@?$constant_expminusone@Vreal_concept@concepts@math@boost@@@detail@constants@math@boost@@CA?AVreal_concept@concepts@34@XZ)
*/
} // BOOST_AUTO_TEST_CASE( test_main )
/*
Output:
tolerance just one epsilon fails for Lambert W (-0.367879) = -0.998452
1>------ Rebuild All started: Project: test_lambertW, Configuration: Release x64 ------
1> test_lambertW.cpp
1> Linking to lib file: libboost_unit_test_framework-vc140-mt-1_63.lib
1> Generating code
1> All 1827 functions were compiled because no usable IPDB/IOBJ from previous compilation was found.
1> Finished generating code
1> test_lambertW.vcxproj -> J:\Cpp\Misc\x64\Release\test_lambertW.exe
1> test_lambertW.vcxproj -> J:\Cpp\Misc\x64\Release\test_lambertW.pdb (Full PDB)
1> Running 1 test case...
1> Platform: Win32
1> Compiler: Microsoft Visual C++ version 14.0
1> STL : Dinkumware standard library version 650
1> Boost : 1.63.0
1> Tests run with Microsoft Visual C++ version 14.0, Dinkumware standard library version 650, Boost PlatformWin32, MSVC compiler 190024123
1> Tolerance 5.96046e-07
1> Precision 6 decimal digits.
1> Iteration #0, w0 0.577547, w1 = 0.567144, difference = 0.0289948, relative 0.018344
1> Iteration #1, w0 0.567144, w1 = 0.567143, difference = 9.53674e-07, relative 5.96046e-07
1> Final 0.567143 after 2 iterations, difference = 0
1> Tolerance 1.11022e-15
1> Precision 15 decimal digits.
1> Iteration #0, w0 0.577547206058041, w1 = 0.567143616915443, difference = 0.0289944962755619, relative 0.018343835374856
1> Iteration #1, w0 0.567143616915443, w1 = 0.567143290409784, difference = 9.02208135089566e-07, relative 5.75702234328901e-07
1> Final 0.567143290409784 after 2 iterations, difference = 0
1> Tolerance 5e-49
1> Precision 50 decimal digits.
1> Iteration #0, w0 0.57754720605804066335708515521786300470367806666917, w1 = 0.5671436169154433808085244686233766561058069997742, difference = 0.028994496275562314267349048621807448516020440172019, relative 0.018343835374855986875831315372982269135605651729877
1> Iteration #1, w0 0.5671436169154433808085244686233766561058069997742, w1 = 0.56714329040978387301011426674463910433535588015473, difference = 9.0220813517014912275046839365249789421357156514914e-07, relative 5.7570223438927562537725655919526667550991229663452e-07
1> Iteration #2, w0 0.56714329040978387301011426674463910433535588015473, w1 = 0.56714329040978387299996866221035554975381578718651, difference = 2.8034566117436458709490133985425058248551576808341e-20, relative 1.7888961583152904127717080041769389899099419098831e-20
1> Final 0.56714329040978387299996866221035554975381578718651 after 3 iterations, difference = 0
1>
1> *** No errors detected
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
*/