![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
Graphing, Profiling, and Generating Test Data for Special FunctionsBoost , Math Toolkit 2.5.0 , Internal tools
|
![]() |
Important |
---|---|
Это непрофильный буст. Математический заголовок, который преимущественно используется для внутреннего обслуживания библиотеки: в результате библиотека расположена под |
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
Этот инструмент лучше всего иллюстрируется следующей серией примеров.
Функциональность test_data разделена на следующие части:
Например, предположим, что мы хотим сделать график функции lgamma между -3 и 100.
#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; }
В результате чего, если нарисовать, получается:
В качестве второго примера предположим, что мы хотим создать высокоточные тестовые данные для специальной функции. Поскольку многие специальные функции имеют два или более независимых параметров, очень трудно эффективно покрыть все возможное пространство параметров без генерации гигабайт данных за большие вычислительные расходы. Второй лучший подход заключается в предоставлении инструментов, с помощью которых пользователь (или хранитель библиотеки) может быстро генерировать больше данных по запросу для зондирования функции по определенной интересующей области.
В этом примере мы будем генерировать тестовые данные для бета-функции, используяNTL::RRс точностью 1000 бит. Вместо того, чтобы называть нашу общую версию бета-функции, мы реализуем намеренно наивную версию бета-функции с использованием lgamma и полагаемся на высокую точность типа данных, используемую для получения результатов с точностью не менее 128 бит. Таким образом, наши тестовые данные не зависят от каких-либо хитрых трюков, которые мы можем использовать в нашей бета-функции.
Для начала, вот объект функции, который создает тестовые данные:
#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; } };
Для создания данных нам потребуется ввести домены для a и b, для которых будет протестирована функция: функцияget_user_parameter_info
предназначена именно для этой цели. Начало основного будет выглядеть примерно так:
// 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 |
---|---|
На этом этапе следует упомянуть один потенциальный камень преткновения: test_data<>::insert создаст матрицу тестовых данных, когда есть два или более параметров, поэтому, если у нас есть два параметра и нас просят за тысячу точек на каждом, этомиллионов тестовых точек в общей сложности. Не говори, что тебя не предупредили! |
Теперь есть только один последний шаг, и это запись тестовых данных в файл:
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");
Формат тестовых данных выглядит примерно так:
#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 */ };
Первые два значения в каждой строке являются входными параметрами, которые были переданы нашему функтору, а последнее значение - это возвращаемое значение от функтора. Если бы наш функтор вернул импульс::math::tuple, а не значение, то у нас была бы одна запись для каждого элемента в кортеже в дополнение к входным параметрам.
Первое определение #define служит двум целям:
static_cast
's складываются в множество байтов в противном случае (они необходимы для подавления предупреждений компилятора, когдаT
уже, чемдлинныйдвойной
).длинныйдвойной
, мы могли бы изменить его на:#define SC_(x) lexical_cast
Для обеспечения того, чтобы усечение значений не происходило до преобразования вT
. Обратите внимание, что это не используется по умолчанию, так как это довольно сложно для компилятора, когда таблица большая.
В качестве альтернативы, скажем, мы хотим профилировать непрерывную фракцию для конвергенции и ошибки. В качестве примера мы будем использовать непрерывную дробь для верхней неполной гамма-функции, следующий объект функции возвращает следующие aNи bNнепрерывной дроби каждый раз, когда она вызывается:
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); } };
Мы хотим измерить как относительную погрешность, так и скорость сходимости этой фракции, поэтому мы напишем функтор, который возвращает оба какповышение::math::tuple: Класс test_data распаковывает для нас набор и создает один столбец данных для каждого элемента в наборе (в дополнение к входным параметрам):
#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 boost::math::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); } };
Подача этого функтора в тестовые данные позволяет быстро выводить данные csv для любого типаT
, который может нас заинтересовать:
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; }
На этот раз нет необходимости строить график, первые несколько строк:
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
Таким образом, совершенно ясно, что эта фракция не должна использоваться для малых значений a и z.
Большая часть этого инструмента уже была описана в примерах выше, мы просто добавим следующие заметки о функциях, не являющихся членами:
template <class T> parameter_info<T> make_random_param(T start_range, T end_range, int n_points);
Рассказывает классу test_data проверитьn_pointsслучайные значения в диапазоне [start_range,end_range].
template <class T> parameter_info<T> make_periodic_param(T start_range, T end_range, int n_points);
Класс test_data проверяетn_pointsравномерно распределенные значения в диапазоне [start_range,end_range].
template <class T> parameter_info<T> make_power_param(T basis, int start_exponent, int end_exponent);
Рассказывает тестовым точкам класса test_data по формеоснование + R * 2экспондля каждогоэкспонв диапазоне [старт_экспонент, конец_экспонент] иRслучайное число в [0,5, 1].
template <class T> bool get_user_parameter_info(parameter_info<T>& info, const char* param_name);
Позволяет пользователю использовать диапазон параметров и форму.
Наконец, если мы не хотим, чтобы параметр был включен в вывод, мы можем сказать test_data, установив его «параметр тупости»:
parameter_info<double> p = make_random_param(2.0, 5.0, 10); p.type |= dummy_param;
Это полезно, когда используемый функтор каким-то образом преобразует параметр перед его передачей тестируемой функции, обычно функтор затем возвращает как преобразованный вход, так и результат в кортеж, поэтому нет необходимости включать исходный псевдопараметр в вывод программы.
Статья Graphing, Profiling, and Generating Test Data for Special Functions раздела Math Toolkit 2.5.0 Internal tools может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.
:: Главная :: Internal tools ::
реклама |