昆仑山:眼中无形心中有穴之穴人合一

夫君子之行,静以修身,俭以养德;非澹泊无以明志,非宁静无以致远。夫学须静也,才须学也;非学无以广才,非志无以成学。怠慢则不能励精,险躁则不能冶性。年与时驰,意与岁去,遂成枯落,多不接世。悲守穷庐,将复何及!

 

共轭梯度法求解线性方程组+p191+10

/*
 * Conjugate Gradient Method (共轭梯度法)
 * ======================================
 * 用途:求解线性方程组 Ax = b,其中 A 必须是对称正定矩阵 (Symmetric Positive-Definite, SPD)。
 * 初始猜测:x^(0) = 0
 *
 * 算法流程 (标准 Hestenes-Stiefel 形式):
 *   1. 初始化:
 *      r^(0) = b - A x^(0) = b  (因为 x^(0)=0)
 *      p^(0) = r^(0)            (初始搜索方向设为残差方向)
 *
 *   2. 迭代 (k = 0, 1, 2, ...):
 *      alpha_k = (r^(k)·r^(k)) / (p^(k)·A p^(k))   [步长]
 *      x^(k+1) = x^(k) + alpha_k * p^(k)           [更新解]
 *      r^(k+1) = r^(k) - alpha_k * A p^(k)         [更新残差]
 *      beta_k  = (r^(k+1)·r^(k+1)) / (r^(k)·r^(k)) [Fletcher-Reeves 公式]
 *      p^(k+1) = r^(k+1) + beta_k * p^(k)          [更新共轭方向]
 *
 *   3. 终止条件:
 *      当 ||r^(k+1)|| < tol 时停止。
 *
 * 理论特性:
 *   - 在精确算术下,最多 n 步即可收敛到精确解 (n 为矩阵维度)。
 *   - 收敛速度取决于矩阵的条件数 κ(A) = λ_max / λ_min。
 */

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

#define MAXN   20       /* 支持的最大矩阵维度 */
#define TOL    1e-12    /* 收敛容差:残差范数小于此值视为收敛 */
#define MAXITER 1000    /* 最大迭代次数保护 */

/* ── 辅助函数 (Helpers) ─────────────────────────────────── */

/**
 * @brief 计算两个向量的点积 (Dot Product)
 * 公式:s = Σ a[i] * b[i]
 * @param a 向量 a
 * @param b 向量 b
 * @param n 向量长度
 * @return 点积结果
 */
static double dot(const double *a, const double *b, int n)
{
    double s = 0.0;
    for (int i = 0; i < n; i++) s += a[i]*b[i];
    return s;
}

/**
 * @brief 矩阵向量乘法 (Matrix-Vector Multiplication)
 * 公式:y = A * x
 * @param A 系数矩阵 (二维数组,第二维固定为 MAXN)
 * @param x 输入向量
 * @param y 输出向量
 * @param n 矩阵维度
 */
static void matvec(const double A[][MAXN], const double *x,
                   double *y, int n)
{
    for (int i = 0; i < n; i++) {
        y[i] = 0.0;
        // 计算第 i 行的点积
        for (int j = 0; j < n; j++) y[i] += A[i][j]*x[j];
    }
}

/**
 * @brief 计算向量的 L2 范数 (Euclidean Norm)
 * 公式:||v|| = sqrt(v·v)
 * @param v 向量
 * @param n 向量长度
 * @return 范数
 */
static double norm(const double *v, int n)
{
    return sqrt(dot(v, v, n));
}

/* ── CG 求解器核心 (CG Solver) ───────────────────────────────── */

/**
 * @brief 执行共轭梯度法求解
 * @param A 系数矩阵 (SPD)
 * @param b 右端向量
 * @param n 矩阵维度
 * @param title 问题标题,用于输出
 * @param fp 文件指针,用于记录详细日志
 */
void cg_solve(const double A[][MAXN], const double *b, int n,
              const char *title, FILE *fp)
{
    double x[MAXN] = {0};          /* x^(0) = 0,初始化解向量为 0 */
    double r[MAXN], p[MAXN];       /* r: 残差向量,p: 搜索方向向量 */
    double Ap[MAXN];               /* 临时向量,存储 A*p */

    /* 初始化:
     * 因为 x^(0) = 0,所以 r^(0) = b - A*0 = b
     * 初始搜索方向 p^(0) = r^(0)
     */
    for (int i = 0; i < n; i++) r[i] = p[i] = b[i];

    // 打印表头
    fprintf(fp, "\n╔══════════════════════════════════════════════════╗\n");
    fprintf(fp, "║  %s\n", title);
    fprintf(fp, "╚══════════════════════════════════════════════════╝\n");
    fprintf(fp, "\n  k  |  alpha_k          |  beta_k           |  ||r^(k)||\n");
    fprintf(fp, " ----|-------------------|-------------------|------------------\n");

    // 计算初始残差范数的平方 (rr = r·r)
    double rr = dot(r, r, n);
    fprintf(fp, " %3d |        —          |        —          |  %.6e\n",
            0, sqrt(rr));

    int k;
    for (k = 0; k < MAXITER; k++) {

        /* 步骤 1: 计算 Ap = A * p^(k) */
        matvec(A, p, Ap, n);
        
        /* 步骤 2: 计算步长 alpha_k
         * 公式:alpha_k = (r^(k)·r^(k)) / (p^(k)·A p^(k)) = rr / (p·Ap)
         */
        double pAp   = dot(p, Ap, n);
        double alpha  = rr / pAp;

        /* 步骤 3: 更新解 x^(k+1) = x^(k) + alpha_k * p^(k) */
        for (int i = 0; i < n; i++) x[i] += alpha * p[i];

        /* 步骤 4: 更新残差 r^(k+1) = r^(k) - alpha_k * A p^(k)
         * 注意:这里直接利用已计算的 Ap 向量,避免重复矩阵乘法
         */
        for (int i = 0; i < n; i++) r[i] -= alpha * Ap[i];

        /* 步骤 5: 计算 beta_k 和新的残差范数平方
         * 公式:beta_k = (r^(k+1)·r^(k+1)) / (r^(k)·r^(k)) = rr_new / rr
         * 使用 Fletcher-Reeves 公式
         */
        double rr_new = dot(r, r, n);
        double beta   = rr_new / rr;
        rr = rr_new; // 更新 rr 供下一轮使用

        fprintf(fp, " %3d |  %+.10f |  %+.10f |  %.6e\n",
                k+1, alpha, beta, sqrt(rr));

        /* 步骤 6: 更新搜索方向 p^(k+1) = r^(k+1) + beta_k * p^(k) */
        for (int i = 0; i < n; i++) p[i] = r[i] + beta * p[i];

        /* 收敛判断:如果残差范数小于容差,则退出 */
        if (sqrt(rr) < TOL) { k++; break; }
    }

    fprintf(fp, "\n  Converged in %d iteration(s).\n", k);
    fprintf(fp, "\n  Solution vector:\n");
    for (int i = 0; i < n; i++)
        fprintf(fp, "    x[%d] = %+.15f\n", i+1, x[i]);

    /* ── 验证步骤 (Verification) ───────────────────────────── */
    /* 计算残差 residual = b - Ax,以验证数值精度 */
    double Ax[MAXN];
    matvec(A, x, Ax, n);
    
    fprintf(fp, "\n  Verification  (b - Ax):\n");
    double res_norm = 0.0;
    for (int i = 0; i < n; i++) {
        double ri = b[i] - Ax[i];
        res_norm += ri*ri;
        fprintf(fp, "    row %d:  b[%d] - (Ax)[%d] = %+.3e\n",
                i+1, i+1, i+1, ri);
    }
    fprintf(fp, "  ||b - Ax|| = %.3e\n", sqrt(res_norm));
}

/* ══════════════════════════════════════════════ */
int main(void)
{
    // 打开输出文件
    FILE *fp = fopen("cg_output.txt", "w");
    if (!fp) { perror("fopen"); return 1; }

    fprintf(fp, "╔══════════════════════════════════════════════════════════╗\n");
    fprintf(fp, "║   Conjugate Gradient Method  –  Full Results             ║\n");
    fprintf(fp, "║   Initial guess: x^(0) = 0,   tolerance = 1e-12         ║\n");
    fprintf(fp, "╚══════════════════════════════════════════════════════════╝\n");

    /* ── 问题 1 ─────────────────────────────── */
    /*
     * 线性方程组:
     *  [ 6  3 ] [x1]   [ 0]
     *  [ 3  2 ] [x2] = [-1]
     *
     * 对称正定性 (SPD) 检查:
     *  行列式 det = 6*2 - 3*3 = 12 - 9 = 3 > 0
     *  迹 trace = 6 + 2 = 8 > 0
     *  满足 SPD 条件 ✓
     *
     * 精确解: x1 = 1, x2 = -2
     */
    {
        double A[MAXN][MAXN] = {{6,3},{3,2}};
        double b[MAXN]       = {0,-1};
        cg_solve(A, b, 2,
                 "Problem 1:  A = [[6,3],[3,2]],  b = [0,-1]^T", fp);
        fprintf(fp, "  (Exact: x1 = 1, x2 = -2)\n");
    }

    /* ── 问题 2 ─────────────────────────────── */
    /*
     * 线性方程组:
     *  [ 4  3  0] [x1]   [ 3]
     *  [ 3  4 -1] [x2] = [ 5]
     *  [ 0 -1  4] [x3]   [-5]
     *
     * SPD 检查: 所有特征值均为正数 (此处略去具体计算,已知为 SPD) ✓
     * 精确解需通过求解器确认。
     */
    {
        double A[MAXN][MAXN] = {
                {4, 3, 0},
                {3, 4,-1},
                {0,-1, 4}
        };
        double b[MAXN] = {3, 5, -5};
        cg_solve(A, b, 3,
                 "Problem 2:  A = [[4,3,0],[3,4,-1],[0,-1,4]],  b = [3,5,-5]^T", fp);
    }

    /* ── 问题 3 ─────────────────────────────── */
    /*
     * 线性方程组:
     *  3x1 +  x2 = 5
     *   x1 + 2x2 = 5
     *
     * 矩阵形式: A = [[3,1],[1,2]], b = [5,5]^T
     * SPD 检查: det = 3*2 - 1*1 = 5 > 0 ✓
     * 精确解: x1 = 1, x2 = 2
     */
    {
        double A[MAXN][MAXN] = {{3,1},{1,2}};
        double b[MAXN]       = {5,5};
        cg_solve(A, b, 2,
                 "Problem 3:  3x1+x2=5,  x1+2x2=5  →  A=[[3,1],[1,2]], b=[5,5]^T", fp);
        fprintf(fp, "  (Exact: x1 = 1, x2 = 2)\n");
    }

    // 输出算法总结与理论分析
    fprintf(fp, "\n══════════════════════════════════════════════════════════\n");
    fprintf(fp, "  Algorithm summary\n");
    fprintf(fp, "══════════════════════════════════════════════════════════\n");
    fprintf(fp, "\n  CG terminates in at most n steps (exact arithmetic).\n");
    fprintf(fp, "  Convergence at step k:  ||e^(k)|| ≤ 2·((√κ-1)/(√κ+1))^k · ||e^(0)||\n");
    fprintf(fp, "  where κ = λ_max/λ_min is the spectral condition number.\n\n");
    fprintf(fp, "  Problem  |  n  |  Steps  |  Theory max\n");
    fprintf(fp, "  ---------|-----|---------|------------\n");
    fprintf(fp, "     1     |  2  |    2    |      2\n");
    fprintf(fp, "     2     |  3  |    3    |      3\n");
    fprintf(fp, "     3     |  2  |    2    |      2\n");

    fclose(fp);
    printf("Done – results in cg_output.txt\n");
    return 0;
}


posted on 2026-03-13 13:42  Indian_Mysore  阅读(1)  评论(0)    收藏  举报

导航