Длина дуги эллипса с радиусамиaиbдана:
L(a, b) = 4aE(k)
с:
k = √(1 - b2/a2)
гдеE(k)— полный эллиптический интеграл второго рода — см.ellint_2.
Предположим, что мы знаем длину дуги и один радиус, а затем можем вычислить другой радиус, перевернув формулу выше. Начнем с кодирования приведенной выше формулы в функтор, который могут вызвать наши алгоритмы поиска корней.
Обратите внимание, что, хотя это не совсем очевидно из приведенной выше формулы, функция полностью симметрична в двух радиусах, которые могут быть заменены по желанию, в этом случае нам нужно убедиться, что<a
>=b
>, чтобы мы случайно не взяли квадратный корень из отрицательного числа:
template <typename T = double>
struct elliptic_root_functor_noderiv
{
elliptic_root_functor_noderiv(T const& arc, T const& radius) : m_arc(arc), m_radius(radius)
{
}
T operator()(T const& x)
{
using std::sqrt;
T a = (std::max)(m_radius, x);
T b = (std::min)(m_radius, x);
T k = sqrt(1 - b * b / (a * a));
return 4 * a * boost::math::ellint_2(k) - m_arc;
}
private:
T m_arc;
T m_radius;
};
Нам также понадобится приличная оценка, чтобы начать поиск, приближение:
L(a, b) ≈ 4√(a2 + b2)
Легко перевернуть, чтобы дать нам то, что нам нужно, что с помощью поиска корней без производных приводит к алгоритму:
template <class T = double>
T elliptic_root_noderiv(T radius, T arc)
{
using namespace std;
using namespace boost::math::tools;
T guess = sqrt(arc * arc / 16 - radius * radius);
T factor = 1.2;
const boost::uintmax_t maxit = 50;
boost::uintmax_t it = maxit;
bool is_rising = true;
eps_tolerance<T> tol(std::numeric_limits<T>::digits - 2);
std::pair<T, T> r = bracket_and_solve_root(elliptic_root_functor_noderiv<T>(arc, radius), guess, factor, is_rising, tol, it);
return r.first + (r.second - r.first) / 2;
}
Эта функция обычно находит корень в пределах 8-10 итераций, поэтому, учитывая, что время выполнения полностью зависит от стоимости вызова эллиптического интеграла, было бы неплохо несколько уменьшить это количество. Мы постараемся сделать это, используя метод, основанный на производных; производные этой функции довольно трудно выработать вручную, но, к счастьюWolfram Alphaможет выполнить работу, которую мы можем дать:
d/da L(a, b) = 4(a2E(k) - b2K(k)) / (a2 - b2)
Обратите внимание, что теперь у нас естьдваэллиптических интегральных вызова, чтобы получить производную, так что наш функтор будет по крайней мере в два раза дороже звонить, чем производный-свободный один выше: нам придется значительно уменьшить количество итераций, чтобы сделать разницу!
Вот пересмотренный функтор:
template <class T = double>
struct elliptic_root_functor_1deriv
{
BOOST_STATIC_ASSERT_MSG(boost::is_integral<T>::value == false, "Only floating-point type types can be used!");
elliptic_root_functor_1deriv(T const& arc, T const& radius) : m_arc(arc), m_radius(radius)
{
}
std::pair<T, T> operator()(T const& x)
{
using std::sqrt;
T a = (std::max)(m_radius, x);
T b = (std::min)(m_radius, x);
T a2 = a * a;
T b2 = b * b;
T k = sqrt(1 - b2 / a2);
T Ek = boost::math::ellint_2(k);
T Kk = boost::math::ellint_1(k);
T fx = 4 * a * Ek - m_arc;
T dfx = 4 * (a2 * Ek - b2 * Kk) / (a2 - b2);
return std::make_pair(fx, dfx);
}
private:
T m_arc;
T m_radius;
};
Код поиска корней теперь почти такой же, как и раньше, но мы будем использовать Ньютон-итерацию, чтобы получить результат:
template <class T = double>
T elliptic_root_1deriv(T radius, T arc)
{
using namespace std;
using namespace boost::math::tools;
BOOST_STATIC_ASSERT_MSG(boost::is_integral<T>::value == false, "Only floating-point type types can be used!");
T guess = sqrt(arc * arc / 16 - radius * radius);
T min = 0;
T max = arc;
int get_digits = static_cast<int>(std::numeric_limits<T>::digits * 0.6);
const boost::uintmax_t maxit = 20;
boost::uintmax_t it = maxit;
T result = newton_raphson_iterate(elliptic_root_functor_1deriv<T>(arc, radius), guess, min, max, get_digits, it);
return result;
}
Количество итераций, требуемых для точности<double
>, теперь обычно составляет около 4 - так что мы чуть более чем вдвое сократили количество итераций, но сделали функтор вдвое дороже!
Интересно, что вторая производная не требует более дорогих эллиптических интегральных вызовов, чем первая, другими словами, она поставляется по существу «бесплатно», и в этом случае мы могли бы также использовать ее и использовать Галлей-итерацию. Это довольно типичная ситуация при инвертировании специальных функций. Вот пересмотренный функтор:
template <class T = double>
struct elliptic_root_functor_2deriv
{
BOOST_STATIC_ASSERT_MSG(boost::is_integral<T>::value == false, "Only floating-point type types can be used!");
elliptic_root_functor_2deriv(T const& arc, T const& radius) : m_arc(arc), m_radius(radius) {}
std::tuple<T, T, T> operator()(T const& x)
{
using std::sqrt;
T a = (std::max)(m_radius, x);
T b = (std::min)(m_radius, x);
T a2 = a * a;
T b2 = b * b;
T k = sqrt(1 - b2 / a2);
T Ek = boost::math::ellint_2(k);
T Kk = boost::math::ellint_1(k);
T fx = 4 * a * Ek - m_arc;
T dfx = 4 * (a2 * Ek - b2 * Kk) / (a2 - b2);
T dfx2 = 4 * b2 * ((a2 + b2) * Kk - 2 * a2 * Ek) / (a * (a2 - b2) * (a2 - b2));
return std::make_tuple(fx, dfx, dfx2);
}
private:
T m_arc;
T m_radius;
};
Фактический код поиска корней почти такой же, как и раньше, за исключением того, что мы можем использовать итерацию Галлея, а не Ньютона:
template <class T = double>
T elliptic_root_2deriv(T radius, T arc)
{
using namespace std;
using namespace boost::math::tools;
BOOST_STATIC_ASSERT_MSG(boost::is_integral<T>::value == false, "Only floating-point type types can be used!");
T guess = sqrt(arc * arc / 16 - radius * radius);
T min = 0;
T max = arc;
int get_digits = static_cast<int>(std::numeric_limits<T>::digits * 0.4);
const boost::uintmax_t maxit = 20;
boost::uintmax_t it = maxit;
T result = halley_iterate(elliptic_root_functor_2deriv<T>(arc, radius), guess, min, max, get_digits, it);
return result;
}
В то время как эта функция использует только немного меньше итераций (обычно около 3), чтобы найти корень, по сравнению с исходным методом без производных, мы перешли от 8-10 эллиптических интегральных вызовов к 6.
Полный код этого примера находится по адресуroot_elliptic_finding.cpp.