共轭梯度法求解线性方程组+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) 收藏 举报
浙公网安备 33010602011771号