Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Graphing, Profiling, and Generating Test Data for Special Functions

Boost , Math Toolkit 2.5.0 , Internal tools

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

КлассTest_dataи связанные с ними вспомогательные функции разработаны таким образом, чтобы всего в нескольких строках кода вы могли:

  • Профиль непрерывной фракции или бесконечного ряда для конвергенции и точности.
  • Создавайте данные csv из специальной функции, которую можно импортировать в вашу любимую графическую программу (или электронную таблицу) для дальнейшего анализа.
  • Создание высокоточных тестовых данных.
Synopsis
#include <boost/math/tools/test_data.hpp>
[Important] Important

Это непрофильный буст. Математический заголовок, который преимущественно используется для внутреннего обслуживания библиотеки: в результате библиотека расположена подlibs/math/включает в себяи вам нужно будет добавить этот каталог в свой путь включения, чтобы использовать эту функцию.

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
Description

Этот инструмент лучше всего иллюстрируется следующей серией примеров.

Функциональность test_data разделена на следующие части:

  • Функтор, который реализует функцию, для которой генерируются данные: это бит, который вы должны написать.
  • Еще один параметр, который следует передать функтору, описывается довольно абстрактно: дайте мне N точек, распределенных какэтои т. д.
  • Данные класса test_data, которые берут функтор и описания параметров и вычисляют, сколько точек вывода было запрошено, хранятся в сортированном контейнере.
  • Режимы итерации по контейнеру test_data и вывода данных либо в формате csv, либо в виде исходного кода C++ (в виде таблицы с использованием Boost.Array).
Example 1: Output Data for Graph Plotting

Например, предположим, что мы хотим сделать график функции 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;
}

В результате чего, если нарисовать, получается:

Example 2: Creating Test Data

В качестве второго примера предположим, что мы хотим создать высокоточные тестовые данные для специальной функции. Поскольку многие специальные функции имеют два или более независимых параметров, очень трудно эффективно покрыть все возможное пространство параметров без генерации гигабайт данных за большие вычислительные расходы. Второй лучший подход заключается в предоставлении инструментов, с помощью которых пользователь (или хранитель библиотеки) может быстро генерировать больше данных по запросу для зондирования функции по определенной интересующей области.

В этом примере мы будем генерировать тестовые данные для бета-функции, используя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] 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(BOOST_STRINGIZE(x))

Для обеспечения того, чтобы усечение значений не происходило до преобразования вT. Обратите внимание, что это не используется по умолчанию, так как это довольно сложно для компилятора, когда таблица большая.

Example 3: Profiling a Continued Fraction for Convergence and Accuracy

В качестве альтернативы, скажем, мы хотим профилировать непрерывную фракцию для конвергенции и ошибки. В качестве примера мы будем использовать непрерывную дробь для верхней неполной гамма-функции, следующий объект функции возвращает следующие 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.

reference

Большая часть этого инструмента уже была описана в примерах выше, мы просто добавим следующие заметки о функциях, не являющихся членами:

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;

Это полезно, когда используемый функтор каким-то образом преобразует параметр перед его передачей тестируемой функции, обычно функтор затем возвращает как преобразованный вход, так и результат в кортеж, поэтому нет необходимости включать исходный псевдопараметр в вывод программы.


PrevUpHomeNext

Статья Graphing, Profiling, and Generating Test Data for Special Functions раздела Math Toolkit 2.5.0 Internal tools может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: Internal tools ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-07-05 05:11:59/0.0075781345367432/0