固定维(N = 9),对称,正半定的密集线性系统的快速解

时间:2012-11-13 21:53:03

标签: c++ algorithm math linear-algebra equation-solving

对于固定维密集线性系统(N = 9)的快速求解,你会推荐哪种算法(矩阵是对称的,正半定的)?

  • 高斯消除
  • LU分解
  • Cholesky分解
  • 等?

类型是32位和64位浮点。

这样的系统将被解决数百万次,因此算法在维度方面应该相当快(n = 9)。

P.S。我们赞赏提出的算法的健壮的 C ++实现的例子。

  

1)“解决了数百万次”是什么意思?相同的系数矩阵与一百万个不同的右手术语,或一百万个不同的矩阵?

百万个不同的矩阵。

  

2)正_semi_definite意味着矩阵可以是单数(对机器精度)。你想怎么处理这个案子?只是提出错误,或者试着回答一些明智的答案?

提出错误是可以的。

6 个答案:

答案 0 :(得分:7)

矩阵是对称的,正半定的, Cholesky分解严格优于LU分解。 (大约是LU的两倍,无论矩阵的大小如何。来源:"数值线性代数"由Trefethen和Bau提供)

它实际上也是小密集矩阵的标准(来源:我在计算数学方面有博士学位)迭代方法效率低于直接方法,除非系统变得足够大(快速的经验法则意味着什么,但是总是很高兴:在任何现代计算机上,任何小于100 * 100的矩阵绝对是一个需要直接方法而不是迭代方法的小矩阵。

现在,我建议不要自己动手。有大量优秀的图书馆已经过全面测试。但如果我不得不推荐你一个,那就是Eigen

  • 无需安装(仅限标题库,因此无链接库,仅#include<>)
  • 强大而高效(他们在主页上有很多基准,结果很好)
  • 易于使用且记录良好

顺便说一句,here in the documentation,你在一个漂亮,简洁的表格中有7种直接线性求解器的各种优缺点。看来在你的情况下,LDLT(Cholesky的变种)获胜

答案 1 :(得分:6)

一般来说,最好使用现有的库,而不是自己动手的方法,因为在追求快速,稳定的数值实现时需要注意许多繁琐的细节。

以下是一些可以帮助您入门的内容:

特征库(我个人喜好):
   http://eigen.tuxfamily.org/dox/QuickRefPage.html#QuickRef_Headers

犰狳:    http://arma.sourceforge.net/

搜索周围,你会发现很多其他人。

答案 2 :(得分:3)

我会推荐LU分解,特别是如果“解决了数百万次”真的意味着“解决一次并应用于数百万载体”。您将创建LU分解,保存它,并对尽可能多的r.h.s应用前向替换。矢量如你所愿。

如果你使用旋转,那么在面对完成时会更稳定。

答案 3 :(得分:2)

对于半对称矩阵的LU没有多大意义:你破坏了输入数据执行不必要操作的好属性。

LLT或LDLT之间的选择实际上取决于矩阵的条件数,以及您打算如何处理边缘情况。只有当您能够证明在准确性方面具有统计上显着的提高,或者稳健性对您的应用程序至关重要时,才应使用LDLT。

(如果没有你的矩阵样本,很难给出合理的建议,但我怀疑N = 9这么小的顺序,将小对角线项向D的底部转动实际上是没有必要的。所以我会从经典的Cholesky开始,如果相对于某些合理选择的公差,诊断项变小,则简单地中止因子分解。)

Cholesky的代码非常简单,如果你想要一个非常快速的代码,最好自己实现它。

答案 4 :(得分:2)

和上面的其他人一样,我推荐cholesky。我发现增加,减少和内存访问的数量增加意味着LDLt比cholesky慢。

实际上有很多关于cholesky的变体,哪一个最快取决于你为矩阵选择的表示。我通常使用fortran样式表示,即矩阵M是双* M,其中M(i,j)是m [i + dim * j];为此,我认为上三角形的cholesky是(一点点)最快的,就是用U'* U = M寻找上三角形U.

对于固定的,小的维度,绝对值得考虑编写一个不使用循环的版本。一个相对简单的方法是编写一个程序来完成它。我记得,使用一般处理一般情况作为模板的例程,只花了一个上午编写一个编写特定固定维度版本的程序。节省的费用可能相当可观。例如,我的通用版本需要0.47秒来完成一百万个9x9因子,而无环版本需要0.17秒 - 这些时序在2.6GHz个人计算机上运行单线程。

为了表明这不是一项重大任务,我在下面列出了这样一个程序的来源。它包括分解的一般版本作为评论。我已经在矩阵不接近单数的情况下使用了这个代码,我认为它在那里工作正常;然而,对于更精细的工作来说,它可能太粗糙了。

/*  ----------------------------------------------------------------
**  to write fixed dimension ut cholesky routines
**  ----------------------------------------------------------------
*/
#include    <stdio.h>
#include    <stdlib.h>
#include    <math.h>
#include    <string.h>
#include    <strings.h>
/*  ----------------------------------------------------------------
*/
#if 0
static  inline  double  vec_dot_1_1( int dim, const double* x, const double* y)
{   
double  d = 0.0;
    while( --dim >= 0)
    {   d += *x++ * *y++;
    }
    return d;
}

   /*   ----------------------------------------------------------------
    **  ut cholesky: solve U'*U = P for ut U in P (only ut of P accessed)
    **  ----------------------------------------------------------------
    */   

int mat_ut_cholesky( int dim, double* P)
{
int i, j;
double  d;
double* Ucoli;

    for( Ucoli=P, i=0; i<dim; ++i, Ucoli+=dim)
    {   /* U[i,i] = P[i,i] - Sum{ k<i | U[k,i]*U[k,i]} */
        d = Ucoli[i] - vec_dot_1_1( i, Ucoli, Ucoli);
        if ( d < 0.0)
        {   return 0;
        }
        Ucoli[i] = sqrt( d);
        d = 1.0/Ucoli[i];
        for( j=i+1; j<dim; ++j)
        {   /* U[i,j] = (P[i,j] - Sum{ k<i | U[k,i]*U[k,j]})/U[i,i] */
            P[i+j*dim] = d*(P[i+j*dim] - vec_dot_1_1( i, Ucoli, P+j*dim));
        }
    }
    return 1;
}
/*  ----------------------------------------------------------------
*/
#endif

/*  ----------------------------------------------------------------
**
**  ----------------------------------------------------------------
*/
static  void    write_ut_inner_step( int dim, int i, int off)
{
int j, k, l;
    printf( "\td = 1.0/P[%d];\n", i+off);
    for( j=i+1; j<dim; ++j)
    {   k = i+j*dim;
        printf( "\tP[%d] = d * ", k);
        if ( i)
        {   printf( "(P[%d]", k);
            printf( " - (P[%d]*P[%d]", off, j*dim);
            for( l=1; l<i; ++l)
            {   printf( " + P[%d]*P[%d]", l+off, l+j*dim);
            }
            printf( "));");
        }
        else
        {   printf( "P[%d];", k);
        }
        printf( "\n");
    }
}

static  void    write_dot( int n, int off)
{   
int i;
    printf( "P[%d]*P[%d]", off, off);
    for( i=1; i<n; ++i)
    {   printf( "+P[%d]*P[%d]", off+i, off+i);
    }
}

static  void    write_ut_outer_step( int dim, int i, int off)
{
    printf( "\td = P[%d]", off+i);
    if ( i)
    {   printf( " - (");
        write_dot( i, off);
        printf( ")");
    }
    printf( ";\n");

    printf( "\tif ( d <= 0.0)\n");
    printf( "\t{\treturn 0;\n");
    printf( "\t}\n");

    printf( "\tP[%d] = sqrt( d);\n", i+off);
    if ( i < dim-1)
    {   write_ut_inner_step( dim, i, off);  
    }
}

static  void    write_ut_chol( int dim)
{
int i;
int off=0;
    printf( "int\tut_chol_%.2d( double* P)\n", dim);
    printf( "{\n");
    printf( "double\td;\n");
    for( i=0; i<dim; ++i)
    {   write_ut_outer_step( dim, i, off);
        printf( "\n");
        off += dim;
    }
    printf( "\treturn 1;\n");
    printf( "}\n");
}
/*  ----------------------------------------------------------------
*/


/*  ----------------------------------------------------------------
**
**  ----------------------------------------------------------------
*/
static  int read_args( int* dim, int argc, char** argv)
{
    while( argc)
    {   if ( strcmp( *argv, "-h") == 0)
        {   return 0;
        }
        else if (strcmp( *argv, "-d") == 0)
        {   --argc; ++argv;
            *dim = atoi( (--argc, *argv++));
        }
        else
        {   break;
        }
    }
    return 1;
}

int main( int argc, char** argv)
{
int dim = 9;
    if( read_args( &dim, --argc, ++argv))
    {   write_ut_chol( dim);
    }
    else
    {   fprintf( stderr, "usage: wchol (-d dim)? -- writes to stdout\n");
    }
    return EXIT_SUCCESS;
}
/*  ----------------------------------------------------------------
*/

答案 5 :(得分:0)

由于易于使用,您可以将Eigen解算器用于比较。对于特定用例,特定解算器可能更快,但另一个应该更好。为此,您可以仅为选择来测量每个算法的运行时间。之后,您可以实现所需的选项(或找到最符合您需求的现有选项)。