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

Testing

Boost , Math Toolkit 2.5.0 , Tutorial: How to Write a New Special Function

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

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

Spot Tests

Спот-тесты состоят из проверки того, что ожидаемое исключение генерируется, когда входы ошибочны (или иным образом генерируют неопределенные значения), и проверки любых специальных значений. Мы можем проверить ожидаемые исключения с помощью BOOST_CHECK_THROW, например, если это ошибка домена для последнего параметра, который находится за пределами. Диапазон [0,1 тогда у нас может быть:

BOOST_CHECK_THROW(my_special(0, -0.1), std::domain_error);
BOOST_CHECK_THROW(my_special(0, 1.1), std::domain_error);

Когда функция знает точные значения (обычно целые значения), мы можем использовать BOOST_CHECK_EQUAL

BOOST_CHECK_EQUAL(my_special(1.0, 0.0), 0);
BOOST_CHECK_EQUAL(my_special(1.0, 1.0), 1);

Когда функция имеет известные значения, которые не являются точными (с точки зрения плавающей точки), тогда мы можем использовать BOOST_CHECK_CLOSE_FRACTION

// Assumes 4 epsilon is as close as we can get to a true value of 2Pi:
BOOST_CHECK_CLOSE_FRACTION(my_special(0.5, 0.5), 2 * constants::pi<double>(), std::numeric_limits<double>::epsilon() * 4);
Independent Test Values

Если функция реализована каким-либо другим известным хорошим источником (например, Mathematica или это онлайн-версии functions.wolfram.com или www.wolframalpha.com, то неплохо проверить нашу реализацию, имея по крайней мере одно независимо сгенерированное значение для каждой ветви кода, которую может занять наша реализация). Чтобы хорошо сочетать их с нашей системой тестирования, лучше всего сформулировать их следующим образом:

// function values calculated on http://functions.wolfram.com/
static const boost::array<boost::array<T, 3>, 10> my_special_data = {{
    {{ SC_(0), SC_(0), SC_(1) }},
    {{ SC_(0), SC_(1), SC_(1.26606587775200833559824462521471753760767031135496220680814) }},
    /* More values here... */
}};

Позже мы увидим, как использовать эту таблицу и значение макроса SC_. Один важный момент заключается в том, чтобы убедиться, что входные значения имеют точные двоичные представления: поэтому выберите значения, такие как 1.5, 1.25, 1.125 и т. Д. Это гарантирует, что если my_special необычайно чувствителен в одной области, мы не получим, по-видимому, больших ошибок только потому, что входы по ошибке равны 0,5 ulp.

Random Test Values

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

#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/math/tools/test_data.hpp>
#include <boost/test/included/prg_exec_monitor.hpp>
#include <fstream>
using namespace boost::math::tools;
using namespace boost::math;
using namespace std;
using namespace boost::multiprecision;
template <class T>
T my_special(T a, T b)
{
   // Implementation of my_special here...
   return a + b;
}
int cpp_main(int argc, char*argv [])
{
   //
   // We'll use so many digits of precision that any
   // calculation errors will still leave us with
   // 40-50 good digits.  We'll only run this program
   // once so it doesn't matter too much how long this takes!
   //
   typedef number<cpp_dec_float<500> > bignum;
   parameter_info<bignum> arg1, arg2;
   test_data<bignum> data;
   bool cont;
   std::string line;
   if(argc < 1)
      return 1;
   do{
      //
      // User interface which prompts for 
      // range of input parameters:
      //
      if(0 == get_user_parameter_info(arg1, "a"))
         return 1;
      if(0 == get_user_parameter_info(arg2, "b"))
         return 1;
      //
      // Get a pointer to the function and call
      // test_data::insert to actually generate
      // the values.
      //
      bignum (*fp)(bignum, bignum) = &my_special;
      data.insert(fp, arg2, arg1);
      std::cout << "Any more data [y/n]?";
      std::getline(std::cin, line);
      boost::algorithm::trim(line);
      cont = (line == "y");
   }while(cont);
   //
   // Just need to write the results to a file:
   //
   std::cout << "Enter name of test data file [default=my_special.ipp]";
   std::getline(std::cin, line);
   boost::algorithm::trim(line);
   if(line == "")
      line = "my_special.ipp";
   std::ofstream ofs(line.c_str());
   line.erase(line.find('.'));
   ofs << std::scientific << std::setprecision(50);
   write_code(ofs, data, line.c_str());
   return 0;
}

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

The Test File Header

Мы разделили фактический тестовый файл на 2 отдельные части: заголовок, который содержит тестовый код в виде серии шаблонов функций, и фактический тест-драйвер .cpp, который решает, какие типы тестируются, и устанавливает «ожидаемые» показатели ошибок для этих типов. Это делается таким образом, потому что:

  • Мы хотим протестировать как с построенными типами с плавающей запятой, так и с многоточными типами. Тем не менее, как компиляция, так и время выполнения с последними могут быть слишком длинными для людей, которые проводят тесты, чтобы реально справиться с ними, поэтому имеет смысл разделить тест на (по крайней мере) 2 части.
  • Определение макроса SC_, используемого в наших таблицах данных, может отличаться в зависимости от типа тестирования (см. ниже). Опять же, это в значительной степени вопрос управления временем компиляции, поскольку большие таблицы определяемых пользователем типов могут занять безумное количество времени для компиляции с некоторыми компиляторами.

Заголовок теста содержит 2 функции:

template <class Real, class T>
void do_test(const T& data, const char* type_name, const char* test_name);
template <class T>
void test(T, const char* type_name);

Перед их реализацией мы включим необходимые заголовки и предоставим определение по умолчанию для макроса SC_:

// A couple of Boost.Test headers in case we need any BOOST_CHECK_* macros:
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
// Our function to test:
#include <boost/math/special_functions/my_special.hpp>
// We need boost::array for our test data, plus a few headers from
// libs/math/test that contain our testing machinary:
#include <boost/array.hpp>
#include "functor.hpp"
#include "handle_test_result.hpp"
#include "table_type.hpp"
#ifndef SC_
#define SC_(x) static_cast<typename table_type<T>::type>(BOOST_JOIN(x, L))
#endif

Самая простая функция для реализации — это функция «тест», которую мы будем называть программой тест-драйвера. Он просто включает в себя файлы, содержащие табличные тестовые данные и вызовы функции do_test для каждой таблицы, а также описание тестируемого:

template <class T>
void test(T, const char* type_name)
{
   //
   // The actual test data is rather verbose, so it's in a separate file
   //
   // The contents are as follows, each row of data contains
   // three items, input value a, input value b and my_special(a, b):
   //
#  include "my_special_1.ipp"
   do_test<T>(my_special_1, name, "MySpecial Function: Mathematica Values");
#  include "my_special_2.ipp"
   do_test<T>(my_special_2, name, "MySpecial Function: Random Values");
#  include "my_special_3.ipp"
   do_test<T>(my_special_3, name, "MySpecial Function: Very Small Values");
}

Функция do_test берет каждую таблицу данных и вычисляет значения для каждой строки данных, наряду со статистикой для максимальной и средней ошибки и т. д., большая часть этого обрабатывается некоторым кодом boilerplate:

template <class Real, class T>
void do_test(const T& data, const char* type_name, const char* test_name)
{
   // Get the type of each row and each element in the rows:
   typedef typename T::value_type row_type;
   typedef Real                   value_type;
   // Get a pointer to our function, we have to use a workaround here
   // as some compilers require the template types to be explicitly
   // specified, while others don't much like it if it is!
   typedef value_type (*pg)(value_type, value_type);
#if defined(BOOST_MATH_NO_DEDUCED_FUNCTION_POINTERS)
   pg funcp = boost::math::my_special<value_type, value_type>;
#else
   pg funcp = boost::math::my_special;
#endif
   // Somewhere to hold our results:
   boost::math::tools::test_result<value_type> result;
   // And some pretty printing:
   std::cout << "Testing " << test_name << " with type " << type_name
      << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
   //
   // Test my_special against data:
   //
   result = boost::math::tools::test_hetero<Real>(
      /* First argument is the table */
      data,
      /* Next comes our function pointer, plus the indexes of it's arguments in the table */
      bind_func<Real>(funcp, 0, 1),
      /* Then the index of the result in the table - potentially we can test several
      related functions this way, each having the same input arguments, and different
      output values in different indexes in the table */
      extract_result<Real>(2));
   //
   // Finish off with some boilerplate to check the results were within the expected errors,
   // and pretty print the results:
   //
   handle_test_result(result, data[result.worst()], result.worst(), type_name, "boost::math::my_special", test_name);
}

Теперь нам просто нужно написать программу тест-драйвера, на самом базовом она выглядит примерно так:

#include <boost/math/special_functions/math_fwd.hpp>
#include <boost/math/tools/test.hpp>
#include <boost/math/tools/stats.hpp>
#include <boost/type_traits.hpp>
#include <boost/array.hpp>
#include "functor.hpp"
#include "handle_test_result.hpp"
#include "test_my_special.hpp"
BOOST_AUTO_TEST_CASE( test_main )
{
   //
   // Test each floating point type, plus real_concept.
   // We specify the name of each type by hand as typeid(T).name()
   // often gives an unreadable mangled name.
   //
   test(0.1F, "float");
   test(0.1, "double");
   //
   // Testing of long double and real_concept is protected
   // by some logic to disable these for unsupported
   // or problem compilers.
   //
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
   test(0.1L, "long double");
#ifndef BOOST_MATH_NO_REAL_CONCEPT_TESTS
#if !BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582))
   test(boost::math::concepts::real_concept(0.1), "real_concept");
#endif
#endif
#else
   std::cout << "<note>The long double tests have been disabled on this platform "
      "either because the long double overloads of the usual math functions are "
      "not available at all, or because they are too inaccurate for these tests "
      "to pass.</note>" << std::cout;
#endif
}

Это почти все, что есть - за исключением того, что если вышеупомянутая программа запущена, очень вероятно, что все тесты потерпят неудачу, поскольку по умолчанию максимально допустимая ошибка составляет 1 эпсилон. Таким образом, мы определим функцию (не забудьте назвать ее с самого начала test_main выше), чтобы увеличить пределы чего-то разумного, основываясь как на функции, которую мы вызываем, так и на конкретных тестах плюс платформа и компилятор:

void expected_results()
{
   //
   // Define the max and mean errors expected for
   // various compilers and platforms.
   //
   const char* largest_type;
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
   if(boost::math::policies::digits<double, boost::math::policies::policy<> >() == boost::math::policies::digits<long double, boost::math::policies::policy<> >())
   {
      largest_type = "(long\\s+)?double|real_concept";
   }
   else
   {
      largest_type = "long double|real_concept";
   }
#else
   largest_type = "(long\\s+)?double";
#endif
   //
   // We call add_expected_result for each error rate we wish to adjust, these tell
   // handle_test_result what level of error is acceptable.  We can have as many calls
   // to add_expected_result as we need, each one establishes a rule for acceptable error
   // with rules set first given preference.
   //
   add_expected_result(
      /* First argument is a regular expression to match against the name of the compiler
         set in BOOST_COMPILER */
      ".*",
      /* Second argument is a regular expression to match against the name of the
         C++ standard library as set in BOOST_STDLIB */
      ".*",
      /* Third argument is a regular expression to match against the name of the
         platform as set in BOOST_PLATFORM */
      ".*",
      /* Forth argument is the name of the type being tested, normally we will
         only need to up the acceptable error rate for the widest floating
         point type being tested */
      largest_real,
      /* Fifth argument is a regular expression to match against
         the name of the group of data being tested */
      "MySpecial Function:.*Small.*",
      /* Sixth argument is a regular expression to match against the name
         of the function being tested */
      "boost::math::my_special",
      /* Seventh argument is the maximum allowable error expressed in units
         of machine epsilon passed as a long integer value */
      50,
      /* Eighth argument is the maximum allowable mean error expressed in units
         of machine epsilon passed as a long integer value */
      20);
}
Testing Multiprecision Types

Тестирование многоточного типа проводится тест-драйверами в libs/multiprecision/test/math, см. Обратите внимание, что эти тесты выполняются только профессионально, поскольку для их создания и запуска требуется много циклов процессора.

Improving Compile Times

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

  • Используйте предварительно составленный заголовок.
  • Используйте отдельную компиляцию наших шаблонов специальных функций.

Мы можем внести эти изменения, изменив список из:

#include <boost/math/special_functions/math_fwd.hpp>
#include <boost/math/tools/test.hpp>
#include <boost/math/tools/stats.hpp>
#include <boost/type_traits.hpp>
#include <boost/array.hpp>
#include "functor.hpp"
#include "handle_test_result.hpp"

Чтобы просто:

#include <pch_light.hpp>

И меняться

#include <boost/math/special_functions/my_special.hpp>

Для:

#include <boost/math/special_functions/math_fwd.hpp>

Цель Jamfile, которая строит программу тестирования, будет нуждаться в целях

test_instances//test_instances pch_light

добавление к списку зависимостей источника (см. пример Jamfile).

Наконец, для реализации проекта в libs/math/test/test_instances потребуется изменение функции my_special.

Эти изменения должны быть сделаны последними, когда my_special стабилен и код находится в Trunk.

Concept Checks

Наши концептуальные проверки подтверждают, что реализация вашей функции не делает никаких предположений, которые не требуются нашими концептуальными требованиями Реальное число . Они также проверяют различные распространенные ошибки и ловушки программирования, в которые мы попали с течением времени. Чтобы добавить свою функцию в эти тесты, отредактируйте libs/math/test/compile_test/instantiate.hpp, чтобы добавить вызовы к вашей функции: есть 7 вызовов к каждой функции, каждый с другой целью. Ищите что-то вроде «ibeta» или «gamm_p» и следуйте их примеру.


PrevUpHomeNext

Статья Testing раздела Math Toolkit 2.5.0 Tutorial: How to Write a New Special Function может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Tutorial: How to Write a New Special Function ::


реклама


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

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 20:56:39/0.030354976654053/1