LU Разложение из числовых рецептов не работает; Что я делаю неправильно? - PullRequest
3 голосов
/ 17 апреля 2011

Я буквально скопировал и вставил из предоставленного исходного кода для Numeric Recipes for C для разложения LU Matrix на месте, проблема в том, что он не работает.

Я уверен, что делаю что-то глупое, но был бы признателен любому, кто сможет указать мне правильное направление на это;Я работал над этим весь день и не вижу, что я делаю неправильно.

ОБНОВЛЕНИЕ ПОСЛЕ ОТВЕТА: Проект завершен и работает .Спасибо всем за руководство.

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#define MAT1 3
#define TINY 1e-20

int h_NR_LU_decomp(float *a, int *indx){
  //Taken from Numerical Recipies for C
  int i,imax,j,k;
  float big,dum,sum,temp;
  int n=MAT1;

  float vv[MAT1];
  int d=1.0;
  //Loop over rows to get implicit scaling info
  for (i=0;i<n;i++) {
    big=0.0;
    for (j=0;j<n;j++)
      if ((temp=fabs(a[i*MAT1+j])) > big) 
        big=temp;
    if (big == 0.0) return -1; //Singular Matrix
    vv[i]=1.0/big;
  }
  //Outer kij loop
  for (j=0;j<n;j++) {
    for (i=0;i<j;i++) {
      sum=a[i*MAT1+j];
      for (k=0;k<i;k++) 
        sum -= a[i*MAT1+k]*a[k*MAT1+j];
      a[i*MAT1+j]=sum;
    }
    big=0.0;
    //search for largest pivot
    for (i=j;i<n;i++) {
      sum=a[i*MAT1+j];
      for (k=0;k<j;k++) sum -= a[i*MAT1+k]*a[k*MAT1+j];
      a[i*MAT1+j]=sum;
      if ((dum=vv[i]*fabs(sum)) >= big) {
        big=dum;
        imax=i;
      }
    }
    //Do we need to swap any rows?
    if (j != imax) {
      for (k=0;k<n;k++) {
        dum=a[imax*MAT1+k];
        a[imax*MAT1+k]=a[j*MAT1+k];
        a[j*MAT1+k]=dum;
      }
      d = -d;
      vv[imax]=vv[j];
    }
    indx[j]=imax;
    if (a[j*MAT1+j] == 0.0) a[j*MAT1+j]=TINY;
    for (k=j+1;k<n;k++) {
      dum=1.0/(a[j*MAT1+j]);
      for (i=j+1;i<n;i++) a[i*MAT1+j] *= dum;
    }
  }
  return 0;
}

void main(){
    //3x3 Matrix
    float exampleA[]={1,3,-2,3,5,6,2,4,3};
    //pivot array (not used currently)
    int* h_pivot = (int *)malloc(sizeof(int)*MAT1);
    int retval = h_NR_LU_decomp(&exampleA[0],h_pivot);
    for (unsigned int i=0; i<3; i++){
      printf("\n%d:",h_pivot[i]);
      for (unsigned int j=0;j<3; j++){
        printf("%.1lf,",exampleA[i*3+j]);
      }
    }
}

WolframAlpha говорит, что ответ должен быть

1,3,-2
2,-2,7
3,2,-2

Я получаю:

2,4,3
0.2,2,-2.8
0.8,1,6.5

И до сих пор я нашел по крайней мере 3 разные версии «одного и того же» алгоритма, поэтому я совершенно запутался.

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

PPS, поскольку в LU-декомпозиции нижняя результирующая матрица равна единице, а при использовании алгоритма Краутса, как (я думаю) реализованного, доступ к индексу массивавсе еще безопасно, и L и U могут быть наложены друг на друга на месте;следовательно, единственная результирующая матрица для этого.

Ответы [ 4 ]

9 голосов
/ 17 апреля 2011

Ради любви ко всему святому, не используйте код Numeric Recipies ни для чего, кроме как в качестве игрушечной реализации для целей обучения алгоритмов, описанных в тексте - и, действительно, текст не так уж велик.И, как вы узнаете, ни один из них не является кодом.

Конечно не помещайте никакие процедуры Numeric Recipies в ваш собственный код - лицензия безумно ограничительна, особенно учитывая кодкачественный.Вы не сможете распространять свой собственный код, если у вас есть NR.

Проверьте, установлена ​​ли в вашей системе библиотека LAPACK .Это стандартный интерфейс для процедур линейной алгебры в вычислительной науке и технике, и хотя он не идеален, вы сможете найти библиотеки lapack для любой машины, на которую вы когда-либо перемещали свой код, и вы можете просто скомпилировать, связать и запустить.Если он еще не установлен в вашей системе, ваш менеджер пакетов (rpm, apt-get, fink, port, что угодно), вероятно, знает о lapack и может установить его для вас.Если нет, то, пока в вашей системе есть компилятор Fortran, вы можете скачать и скомпилировать его из здесь , и стандартные привязки C можно найти чуть ниже на той же странице.

Причина, по которой так удобно иметь стандартный API для процедур линейной алгебры, заключается в том, что они настолько распространены, но их производительность зависит от системы.Так, например, Goto BLAS - безумно быстрая реализация для систем x86 низкоуровневых операций, необходимых для линейной алгебры;как только у вас работает LAPACK, вы можете установить эту библиотеку, чтобы сделать все как можно быстрее.

После того, как вы установили LAPACK любого типа, подпрограмма для LU-факторизации общей матрицы - это SGETRF для чисел с плавающей запятой,или DGETRF для двойников.Существуют и другие, более быстрые процедуры, если вы что-то знаете о структуре матрицы - о том, что она симметрично положительно определена, скажем (SBPTRF), или что она трехдиагональна (STDTRF).Это большая библиотека, но как только вы изучите ее, у вас будет очень мощный инструмент в числовом наборе инструментов.

9 голосов
/ 17 апреля 2011

Я думаю, что с вашими показателями что-то не так. Иногда они имеют необычные начальные и конечные значения, и внешний цикл более j вместо i вызывает у меня подозрение.

Прежде чем просить кого-либо проверить ваш код, вот несколько предложений:

  • перепроверьте свои показатели
  • избавиться от попыток запутывания, используя sum
  • используйте макрос a(i,j) вместо a[i*MAT1+j]
  • написать подфункции вместо комментариев
  • удалите ненужные детали, изолировав ошибочный код

Вот версия, которая следует за этими предложениями:

#define MAT1 3
#define a(i,j) a[(i)*MAT1+(j)]

int h_NR_LU_decomp(float *a, int *indx)
{
        int i, j, k;
        int n = MAT1;

        for (i = 0; i < n; i++) {
                // compute R
                for (j = i; j < n; j++)
                        for (k = 0; k < i-2; k++)
                                a(i,j) -= a(i,k) * a(k,j);

                // compute L
                for (j = i+1; j < n; j++)
                        for (k = 0; k < i-2; k++)
                                a(j,i) -= a(j,k) * a(k,i);
        }

        return 0;
}

Его основными преимуществами являются:

  • это читабельно
  • работает

Хотя ему не хватает поворота. При необходимости добавьте подфункции.


Мой совет: не копируйте чужой код, не понимая его.

Большинство программистов - плохие программисты.

3 голосов
/ 18 апреля 2011

Плохо перефразируя Альберта Эйнштейна:

... человек с часами всегда знает точное время, но человек с двумя никогда не уверен ....

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

    1  0  0      1  0  0       1  3 -2
P=  0  1  0   L= 2  1  0  U =  0 -2  7
    0  0  1      3  2  1       0  0 -2

, который затем будет удовлетворять условию A = P.L.U (где. Обозначает матричное произведение). Если я вычисляю (условно) ту же операцию разложения другим способом (в этом случае используя процедуру LAPACK dgetrf через numpy):

In [27]: A
Out[27]: 
array([[ 1,  3, -2],
       [ 3,  5,  6],
       [ 2,  4,  3]])

In [28]: import scipy.linalg as la

In [29]: LU,ipivot = la.lu_factor(A)

In [30]: print LU
[[ 3.          5.          6.        ]
 [ 0.33333333  1.33333333 -4.        ]
 [ 0.66666667  0.5         1.        ]]

In [31]: print ipivot
[1 1 2]

После небольшого количества черной магии с ipivot мы получаем

    0  1  0       1        0    0       3  5       6
P = 0  0  1   L = 0.33333  1    0  U =  0  1.3333 -4
    1  0  0       0.66667  0.5  1       0  0       1

, что также удовлетворяет A = P.L.U. Обе эти факторизации верны, но они различны и не будут соответствовать правильно функционирующей версии кода NR.

Поэтому, прежде чем вы сможете решить, имеете ли вы «правильный» ответ, вам действительно следует потратить немного времени на понимание фактического алгоритма, который реализует скопированный вами код.

3 голосов
/ 17 апреля 2011

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

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

Кроме того, у @Philip есть хороший совет.Если вы хотите понять код, начните с понимания декомпозиции LU без поворота.

...