SpectraRust/src/math/lineqs.rs
2026-03-21 09:12:18 +08:00

224 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 线性方程组求解。
//!
//! 重构自 TLUSTY `lineqs.f`。
//!
//! 使用高斯消元法(带部分选主元)求解线性方程组 A*X = B。
// ============================================================================
// LINEQS - 高斯消元法求解线性方程组
// ============================================================================
/// 使用高斯消元法(带部分选主元)求解线性方程组 A*X = B。
///
/// 这是简化版本,假设物理维度等于逻辑维度。
/// 对于物理维度大于逻辑维度的情况,使用 `lineqs_nr`。
///
/// # 参数
///
/// - `a` - 系数矩阵,大小为 n×n列优先存储会被修改
/// - `b` - 右端向量,大小为 n会被修改
/// - `x` - 解向量,大小为 n输出
/// - `n` - 实际方程数
pub fn lineqs(a: &mut [f64], b: &mut [f64], x: &mut [f64], n: usize) {
lineqs_nr(a, b, x, n, n);
}
/// 使用高斯消元法(带部分选主元)求解线性方程组 A*X = B。
///
/// 支持物理维度大于逻辑维度的情况。
///
/// # 参数
///
/// - `a` - 系数矩阵,物理大小为 nr×nr列优先存储会被修改
/// - `b` - 右端向量,物理大小为 nr会被修改
/// - `x` - 解向量,物理大小为 nr输出
/// - `n` - 实际方程数(逻辑维度)
/// - `nr` - 物理维度
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE LINEQS(A,B,X,N,NR)
/// DIMENSION A(NR,NR),B(NR),X(NR),D(MLEVEL),IP(MLEVEL)
/// ...
/// END
/// ```
///
/// # 注意
///
/// - 矩阵 A 和向量 B 在求解过程中会被修改
pub fn lineqs_nr(a: &mut [f64], b: &mut [f64], x: &mut [f64], n: usize, nr: usize) {
// 特殊情况2×2 系统,直接求解
if n == 2 {
let a11 = a[0];
let a12 = a[nr]; // Fortran 列优先: A(1,2) = A[0 + nr*1]
let a21 = a[1]; // Fortran 列优先: A(2,1) = A[1 + nr*0]
let a22 = a[nr + 1];
let det = a11 * a22 - a12 * a21;
x[0] = (a22 * b[0] - a12 * b[1]) / det;
x[1] = (b[1] - a21 * x[0]) / a22;
return;
}
// 工作数组
let mut d = vec![0.0; n];
let mut ip = vec![0usize; n];
// LU 分解(带部分选主元)
for i in 0..n {
// 复制第 i 列到 d
for j in 0..n {
d[j] = a[j + i * nr];
}
// 前向消元
if i >= 1 {
for j in 0..i {
let it = ip[j];
a[j + i * nr] = d[it];
d[it] = d[j];
for k in (j + 1)..n {
d[k] = d[k] - a[k + j * nr] * a[j + i * nr];
}
}
}
// 选主元
let mut am = d[i].abs();
ip[i] = i;
for k in i..n {
if am < d[k].abs() {
ip[i] = k;
am = d[k].abs();
}
}
// 交换行
let it = ip[i];
a[i + i * nr] = d[it];
d[it] = d[i];
// 计算乘数
if i + 1 < n {
for k in (i + 1)..n {
a[k + i * nr] = d[k] / a[i + i * nr];
}
}
}
// 前向替换(处理右端向量)
for i in 0..n {
let it = ip[i];
x[i] = b[it];
b[it] = b[i];
if i + 1 < n {
for j in (i + 1)..n {
b[j] = b[j] - a[j + i * nr] * x[i];
}
}
}
// 后向替换
for i in 0..n {
let k = n - 1 - i;
let mut sum = 0.0;
if k + 1 < n {
for j in (k + 1)..n {
sum = sum + a[k + j * nr] * x[j];
}
}
x[k] = (x[k] - sum) / a[k + k * nr];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lineqs_2x2() {
// 2×2 系统
// [2, 1] [x1] [5]
// [1, 3] [x2] = [7]
// 解x1 = 1.6, x2 = 1.8
let mut a = vec![2.0, 1.0, 1.0, 3.0]; // 列优先
let mut b = vec![5.0, 7.0];
let mut x = vec![0.0; 2];
lineqs(&mut a, &mut b, &mut x, 2);
assert!((x[0] - 1.6).abs() < 1e-10);
assert!((x[1] - 1.8).abs() < 1e-10);
}
#[test]
fn test_lineqs_3x3() {
// 3×3 系统
// [1, 2, 3] [x1] [6]
// [4, 5, 6] [x2] = [15]
// [7, 8, 10] [x3] [25]
// 解x1 = 1, x2 = 1, x3 = 1
// Fortran 列优先存储
let mut a = vec![1.0, 4.0, 7.0, 2.0, 5.0, 8.0, 3.0, 6.0, 10.0];
let mut b = vec![6.0, 15.0, 25.0];
let mut x = vec![0.0; 3];
lineqs(&mut a, &mut b, &mut x, 3);
assert!((x[0] - 1.0).abs() < 1e-10);
assert!((x[1] - 1.0).abs() < 1e-10);
assert!((x[2] - 1.0).abs() < 1e-10);
}
#[test]
fn test_lineqs_identity() {
// 单位矩阵
let mut a = vec![1.0, 0.0, 0.0, 1.0];
let mut b = vec![3.0, 4.0];
let mut x = vec![0.0; 2];
lineqs(&mut a, &mut b, &mut x, 2);
assert!((x[0] - 3.0).abs() < 1e-10);
assert!((x[1] - 4.0).abs() < 1e-10);
}
#[test]
fn test_lineqs_diagonal() {
// 对角矩阵
let mut a = vec![2.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 4.0];
let mut b = vec![6.0, 9.0, 16.0];
let mut x = vec![0.0; 3];
lineqs(&mut a, &mut b, &mut x, 3);
assert!((x[0] - 3.0).abs() < 1e-10);
assert!((x[1] - 3.0).abs() < 1e-10);
assert!((x[2] - 4.0).abs() < 1e-10);
}
#[test]
fn test_lineqs_pivoting() {
// 需要选主元的系统
// [0.001, 1] [x1] [1]
// [1, 1] [x2] = [2]
// 解x1 ≈ 1.001, x2 ≈ 0.999
let mut a = vec![0.001, 1.0, 1.0, 1.0];
let mut b = vec![1.0, 2.0];
let mut x = vec![0.0; 2];
lineqs(&mut a, &mut b, &mut x, 2);
// 直接验证 A*X = B
let res1 = 0.001 * x[0] + 1.0 * x[1];
let res2 = 1.0 * x[0] + 1.0 * x[1];
assert!((res1 - 1.0).abs() < 1e-10, "First equation: {} != 1", res1);
assert!((res2 - 2.0).abs() < 1e-10, "Second equation: {} != 2", res2);
}
}