【问题标题】:How to optimize Gauss-Seidel routine in C++ for sparse matrices?如何在 C++ 中针对稀疏矩阵优化 Gauss-Seidel 例程?
【发布时间】:2013-03-17 21:55:15
【问题描述】:

我用 C++ 编写了一个例程,它使用 Gauss-Seidel 方法求解方程组 Ax = b。但是,我想将此代码用于稀疏的特定“A”矩阵(大多数元素为零)。这样,该求解器花费的大部分时间都在忙于将某些元素乘以零。

例如,对于以下方程组:

| 4 -1  0  0  0 | | x1 |   | b1 |
|-1  4 -1  0  0 | | x2 |   | b2 |
| 0 -1  4 -1  0 | | x3 | = | b3 |
| 0  0 -1  4 -1 | | x4 |   | b4 |
| 0  0  0 -1  4 | | x5 |   | b5 |

使用 Gauss-Seidel 方法,我们将得到以下 x1 的迭代公式:

x1 = [b1 - (-1 * x2 + 0 * x3 + 0 * x4 + 0 * x5)] / 4

如您所见,求解器将零元素相乘是在浪费时间。由于我使用大矩阵(例如,10^5 x 10^5),这将对总 CPU 时间产生负面影响。我想知道是否有一种方法可以优化求解器,使其省略与零元素乘法相关的计算部分。

请注意,上例中“A”矩阵的形式是任意的,求解器必须能够处理任何“A”矩阵。

代码如下:

void GaussSeidel(double **A, double *b, double *x, int arraySize)
{   
const double tol = 0.001 * arraySize;
double error = tol + 1;

for (int i = 1; i <= arraySize; ++i)
    x[i] = 0;

double *xOld;
xOld = new double [arraySize];
for (int i = 1; i <= arraySize; ++i)
    xOld[i] = 101;

while (abs(error) > tol)
{

    for (int i = 1; i <= arraySize; ++i)
    {
        sum = 0;
        for (int j = 1; j <= arraySize; ++j)
        {
            if (j == i)
                continue;
            sum = sum + A[i][j] * x[j];
        }
        x[i] = 1 / A[i][i] * (b[i] - sum);
    }

    //cout << endl << "Answers:" << endl << endl;
    error = errorCalc(xOld, x, arraySize);

    for (int i = 1; i <= arraySize; ++i)
        xOld[i] = x[i];

cout << "Solution converged!" << endl << endl;
}

【问题讨论】:

  • 除非您更改数据结构,否则我认为这是不可能的。您是否事先确定该矩阵将是三对角矩阵而不仅仅是“稀疏”?
  • 矩阵只是稀疏矩阵,不一定是三对角矩阵。

标签: c++ sparse-matrix numerical-methods


【解决方案1】:

编写稀疏线性系统求解器很难。 非常难。

我只会选择现有的实现之一。任何合理的 LP 求解器内部都有一个稀疏线性系统求解器,例如参见 lp_solveGLPK 等。

如果您可以接受许可证,我推荐Harwell Subroutine library。不过,连接 C++ 和 Fortran 并不好玩……

【讨论】:

  • 为什么这么难。请您详细说明一下。无论如何我都会实施它,所以我想事先知道陷阱。谢谢。
  • @Yola 抱歉,我无法在评论中详细说明。如果您想了解其中的陷阱,请阅读 Duff、Erisman、Reid 的《Direct methods for sparse matrices》一书。
  • 一点也不难。在 Gauss-Seidel 中,您只需要从左到右遍历矩阵的每一行。将稀疏矩阵表示为 CSR(非常简单)。时间复杂度将对应于非零条目的数量。我用 35 行代码完成了这一切(CSR + Gauss-Seidel)。
  • @IvanKuckir 也许,如果你只需要一个稀疏的 Gauss-Seidel 方法就可以了。但很有可能你需要更多,然后你最终会编写自己的稀疏线性代数库。而且我不认为 Gauss-Seidel 是大型稀疏矩阵的正确选择,但也许我错了。我的说法是:“编写稀疏线性系统求解器很难”,我仍然认为它是正确的。
  • @Ali 你不能只说一些事情而不解释你为什么这么认为。 LP,你的意思是线性规划吗? “线性系统求解器”包括您的情况下的线性规划求解器?大型线性方程组很难解析求解(在立方时间内),因此使用迭代方法来逼近解。这些方法通常会遍历每一行/每一列,现有的稀疏矩阵数据结构并没有让它变得更难。
【解决方案2】:

你的意思是有多稀疏?

这是一个糟糕的稀疏实现,它应该可以很好地解决线性方程组的问题。这可能是一个幼稚的实现,我对工业强度稀疏矩阵中通常使用的数据结构知之甚少。

The code, and an example, is here.

这是完成大部分工作的类:

template <typename T>
class SparseMatrix
{
private:
    SparseMatrix();

public:
    SparseMatrix(int row, int col);

    T Get(int row, int col) const;
    void Put(int row, int col, T value);

    int GetRowCount() const;
    int GetColCount() const;

    static void GaussSeidelLinearSolve(const SparseMatrix<T>& A, const SparseMatrix<T>& B, SparseMatrix<T>& X);

private:
    int dim_row;
    int dim_col;

    vector<map<int, T> > values_by_row;
    vector<map<int, T> > values_by_col;
};

ideone 中包含其他方法定义。我不测试收敛性,而是简单地循环任意次数。

稀疏表示使用 STL 映射按行和列存储所有值的位置。对于像您提供的那样非常稀疏的矩阵(密度

我的实现应该足够通用,以支持任何支持比较的整数或用户定义类型,4 个算术运算符(+-*/),并且可以从0(空节点被赋予值(T) 0)。

【讨论】:

  • 另外,我怀疑对模板化类型的行为有额外的限制(具体来说,某些行为可能会导致系统无法收敛)。一般来说,定义域与实数同构的任何类型都可以工作。
  • 非常感谢,帮了大忙 :)
  • 我之前没想过要提到这一点,但你应该小心 - 这种实现对于非稀疏矩阵的性能会很差。
【解决方案3】:

最近,我面临同样的问题。 我的解决方案是使用向量数组来保存稀疏矩阵。 这是我的代码:

#define PRECISION   0.01

inline bool checkPricision(float x[], float pre[], int n) {
    for (int i = 0; i < n; i++) {
        if (fabs(x[i] - pre[i]) > PRECISION) return false;
    }
    return true;
}

/* mx = b */
void gaussIteration(std::vector< std::pair<int, float> >* m, float x[], float b[], int n) {
    float* pre = new float[n];
    int cnt = 0;
    while (true) {
        cnt++;
        memcpy(pre, x, sizeof(float)* n);
        for (int i = 0; i < n; i++) {
            x[i] = b[i];
            float mii = -1;
            for (int j = 0; j < m[i].size(); j++) {
                if (m[i][j].first != i) {
                    x[i] -= m[i][j].second * x[m[i][j].first];
                }
                else {
                    mii = m[i][j].second;
                }
            }
            if (mii == -1) {
                puts("Error: No Solution");
                return;
            }
            x[i] /= mii;
        }
        if (checkPricision(x, pre, n)) {
            break;
        }
    }
    delete[] pre;
}

【讨论】:

    【解决方案4】:

    试试 PETSC。为此,您需要 CRS(压缩行存储)格式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-20
      相关资源
      最近更新 更多