Недостаточная числовая точность процедур LAPACK (Fortran 90)? - PullRequest
2 голосов
/ 08 июля 2020

Я написал простой код на Фортране, который выполняет полиномиальную интерполяцию n+1 точек из R^2. Он решает систему линейных уравнений (я создаю матрицу Вандермонда) с двумя процедурами LAPACK (в моем коде все имеет двойную точность): Сначала он факторизует матрицу: http://sites.science.oregonstate.edu/~landaur/nacphy/lapack/routines/dgetrf.html Позже он решает систему: http://sites.science.oregonstate.edu/~landaur/nacphy/lapack/routines/dgetrs.html

программа отлично работает для нескольких проверенных случаев данных, полученных из полиномов степеней: 0,1,2,3,4. Однако, когда я даю 11 точек из полинома P(x) = x^10, он выводит совершенно неправильные коэффициенты.

Вход (x..., y...):

1.0
1.001
1.002
1.003
1.004
1.005
1.006
1.007
1.008
1.009
1.01
1.0
1.01004512021
1.02018096336
1.03040825707
1.04072773401
1.05114013204
1.06164619412
1.07224666847
1.08294230847
1.0937338728
1.10462212541

Выход (a_n,...,a_0):

-4.6992230177E+004
2.2042918738E+005
-3.2949938635E+005
5.0740528522E+004
2.4764893257E+005
-3.1846974845E+004
-1.7195378863E+005
-1.4751075818E+005
4.1766709741E+005
-2.6476448046E+005
5.6082872757E+004

Я сталкиваюсь с проблемами численной стабильности? Или я что-то не так сделал?

Изменить: я прилагаю код для процедуры интерполяции (берегитесь, у нас на самом деле n точек, а не n+1).

module InterpolatorModule
contains
subroutine interpolate(n, x, y, a)
  implicit none
  integer :: n, i, j, info
  integer, dimension (:), allocatable :: ipiv
  real(8), dimension (:), pointer :: x, y, a
  real(8), dimension(:,:), allocatable :: Mat_X, Mat_B

  ! create the Vandermonde matrix:
  allocate ( Mat_X(n,n) )
  do i = 1,n
    do j = 1,n
      Mat_X(i,j) = x(i) ** ( n - j )
    end do
  end do

  ! reshape ipnut data into a matrix form:
  allocate ( Mat_B(n,1) )
  do i = 1,n
    Mat_B(i,1) = y(i)
  end do

  ! prepare an array for the pivot indices from DGETRF:
  allocate(ipiv(n))

  call DGETRF(n, n, Mat_X, n, ipiv, info)
  call DGETRS("N", n, 1, Mat_X, n, ipiv, Mat_B, n, info)

  ! save the results into the argument array
  do i = 1,n
    a(i) = Mat_B(i,1)
  end do

  deallocate(ipiv)
  deallocate ( Mat_X )
  deallocate ( Mat_B )

end subroutine interpolate
end module InterpolatorModule

Редактировать: программа main:

program MyInterpolation
use InterpolatorModule
implicit none

  integer :: line, n
  real(8), dimension (:), pointer :: p_x, p_y, p_a
  real(8), dimension(:), target, allocatable :: in1_x, in1_y, in1_a 
  character(len=23) :: str

  open (1, file="test/in.txt", status="old", action="read", form="formatted")

    n = 11 ! this value is not known at compulation time, simplification for SO

    ! allocate memory for the input data
    allocate ( in1_x(n) )
    allocate ( in1_y(n) )
    allocate ( in1_a(n) )

    ! read the x_i coordinates:
    do line = 1,n
      read(1,*) in1_x(line)
    end do
    ! read the y_i coordinates:
    do line = 1,n
      read(1,*) in1_y(line)
    end do
  close(1)
  ! assign pointers to the arrays:
  p_x => in1_x
  p_y => in1_y
  p_a => in1_a
  ! call the interpolating procedure:
  call interpolate(n, p_x, p_y, p_a)

  ! print out calculated coefficients:
  do line = 1,n
    write(str,'(ES23.10 E3)') in1_a(line)
    write(*,'(a)') adjustl(trim(str))
  end do

  ! free the allocated memory
  deallocate (in1_x)
  deallocate (in1_y)
  deallocate (in1_a)

  ! ===========================================================================

end program MyInterpolation

1 Ответ

5 голосов
/ 10 июля 2020

Широко признано - и было указано в комментариях - что матрица Вандермонда часто плохо обусловлена. Например, стандартный учебник по численному анализу, D. Kincaid и W. Cheney, "Numerical Analysis, 2nd ed.", Brooks / Cole Publishing 1996, утверждает на странице 336:

Матрица коэффициентов в Уравнение (12) называется матрицей Вандермонда . Это не единственное число, потому что система имеет уникальное решение для любого выбора y 0 , y 1 , ..., y n [...]. Следовательно, определитель матрицы Вандермонда отличен от нуля для различных узлов x 0 , x 1 , ..., x п . [...] Однако матрица Вандермонда часто плохо обусловлена ​​, и поэтому коэффициенты a i могут быть определены неточно путем решения Системы (12). (См. Gautschi [1984]). [...] Следовательно, этот подход не рекомендуется .

Проверка матрицы, возвращаемой GETRF, уже сообщает нам, что у нас проблемы с полиномами довольно низкого градусов. При степени 4 величина наименьшего релевантного элемента после разложения LU составляет порядка 10 -12 , а при степени 5 наименьший такой элемент составляет 10 -14 . Исходя из того факта, что все элементы исходной матрицы были близки к единице, и зная основные c шагов процесса разложения LU, ясно, что крошечные элементы в результате GETRF получаются путем вычитания.

Величина элементов, приближающихся к эпсилону двойной точности (≈ 10 -16 ), говорит нам, что большая часть точности была потеряна. Для полиномов степени 6 и выше код в основном работает на чистом шуме.

Мы можем дополнительно подтвердить это, сравнив вычисленные коэффициенты интерполяционного полинома с более надежным эталоном. Для простоты я использовал Wolfram Alpha для этого сравнения. Для полинома степени 4 код на Фортране вычисляет коэффициенты с точностью примерно до трех десятичных цифр, а для полинома степени 5 это сжимается до единственного правильного десятичного числа di git.

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

JN Lyness и C .B. Молер, "Системы Ван-дер-Монд и численное дифференцирование". Numerische Mathematik 8, 458-464 (1966)

Я перевел код Algol в статье на ISO-C99, и он, кажется, дает разумные результаты до степени 8 по сравнению с Wolfram Alpha . Wolfram Alpha отказывается от оценок выше 8, и у меня нет под рукой других справочных материалов. Даже с этим более надежным алгоритмом, похоже, наблюдается значительная потеря точности для более высоких степеней, с совпадением только 6 десятичных цифр между Wolfram Alpha и алгоритмом Luness / Moler.

#include <stdio.h>
#include <stdlib.h>

/* J. N. Lyness and C.B. Moler, "Van Der Monde Systems and Numerical 
   Differentiation." Numerische Mathematik 8, 458-464 (1966)
*/
void update (int k, int p, double *x, double fxk, double *C)
{
    int d, s, m, n;
    double xk, xkd;
    xk = x[k];
    m = k*(k+3)/2;
    C[m] = fxk;
    for (d = 1; d <= k; d++) {
        xkd = xk - x[k-d];
        for (s = 0; s <= ((d > p) ? p : d); s++) {
            m = m - 1;
            n = m + d;
            if (s == 0) {
                C[m] = C[n] + xk * (C[n-k-1] - C[n]) / xkd;
            } else if (s == d) {
                C[m] = (C[n+1] - C[n-k]) / xkd;
            } else {
                C[m] = C[n] + (xk * (C[n-k-1] - C[n]) + (C[n+1] - C[n-k]))/ xkd;
            }
            if (d > p) {
                m = m - d + p;
            }
        }
    }        
}

/* Solve (k+1) x (k+1) system Vc = f, where V[i,j] = x[i]**j  */
void vandal (int k, double *x, double *f, double *c)
{
    double C [k*(k+3)/2+1];
    for (int i = 0; i <= k; i++) {
        update (i, k, x, f[i], C);
    }
    for (int i = 0; i <= k; i++) {
        c[i] = C[k-i];
    }
}

#define k 10
int main (void)
{
    double x[k+1] = {1.000, 1.001, 1.002, 1.003, 1.004, 1.005, 
                     1.006, 1.007, 1.008, 1.009, 1.010};
    double f[k+1] = {1.00000000000, 1.01004512021, 1.02018096336, 
                     1.03040825707, 1.04072773401, 1.05114013204, 
                     1.06164619412, 1.07224666847, 1.08294230847, 
                     1.09373387280, 1.10462212541};
    double c[k+1] = {0};
    
    vandal (k, x, f, c);

    for (int i = 0; i <= k; i++) {
        printf ("c[%2d] = % 23.16e\n", i, c[i]);
    }
    return EXIT_SUCCESS;
}
...