Подавляющая часть прикладных задач математической физики решается численными методами, неотъемлемой частью которых является решение системы линейных алгебраических уравнений (СЛАУ) , где A - матрица коэффициентов системы, b - вектор правой части, x - вектор решений.
Поэтому, для получения наиболее точного решения прикладной задачи необходимо научиться наиболее точно решать СЛАУ. В данной статье будет рассмотрено решение СЛАУ несколькими методами:
методом Гаусса;
LU декомпозицией исходной матрицы;
QR декомпозицией исходной матрицы;
компактной схемой исключения.
Шаблоны классов MATRIX<T> (Матрица) и VECTOR<T>
Для упрощения работы с матрицами и векторами были созданы шаблоны классов MATRIX<T> (Матрица) и VECTOR<T> (Вектор). элементов матрицы типа T, где n - число строк, а m - число столбцов, размещаются в одномерном массиве данных по строкам. Элементы n-мерного вектора располагаются в одномерном массиве типа T. Шаблоны классов MATRIX<T> и VECTOR<T> содержат перегруженные операции =, *, +, -. Шаблоны классов MATRIX<T> и VECTOR<T> представлены в файле MatrixVectorTemplate.hpp проекта MatrixVector. Функции, осуществляющие решение СЛАУ, представлены дружественными функциями классов MATRIX<T> и VECTOR<T>
template <typename T> friend bool Gauss(const MATRIX<T>& a, const VECTOR<T>& b, VECTOR<T>& x);
template <typename T> friend void CompactSchemeSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x);
template <typename T> friend void QRDecompositionSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x);
template <typename T> friend void LLTDecompositionSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x);
template <typename T> friend void LUDecompositionSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x);Решение СЛАУ
Проверка решения СЛАУ различными методами осуществлялась на примере матрицы коэффициентов СЛАУ A и вектора правой части b.
Производилась оценка точности решения в виде евклидовой нормы невязки , а также вычислялось время решения.
Метод Гаусса
Метод Гаусса является, пожалуй, самым известным методом решения СЛАУ. Он хорошо описан в методической, учебной и научной литературе. Код представлен в виде дружественной классам MATRIX<T> и VECTOR<T> функции:
/// <summary>
/// Решение системы линейных алгебраических уравнений (СЛАУ) методом Гаусса с вычислением определителя матрицы системы
/// </summary>
/// <param name="a">матрица коэффициентов СЛАУ</param>
/// <param name="b">вектор правой части СЛАУ</param>
/// <param name="x">решение СЛАУ</param>
/// <returns></returns>
template <typename T>
bool Gauss(const MATRIX<T>& a, const VECTOR<T>& b, VECTOR<T>& x)
{
int i, k, m;
T amm, aim;
// матрица должна быть квадратной и размерность вектора должна совпадать
// с размерностью матрицы
if (a.m_columns != b.m_size || a.m_columns != a.m_rows) return false;
int size = a.m_rows;
// сведение исходной системы к системе с верхней треугольной матрицей
MATRIX<T> alf(a.m_rows, a.m_columns);
VECTOR<T> bet(b.m_size);
alf = a;
bet = b;
for (m = 0; m <= size - 2; m++)
{
amm = *(alf.m_data + m * size + m);
for (k = m; k <= size - 1; k++)
*(alf.m_data + m * size + k) /= amm;
*(bet.m_data + m) /= amm;
for (i = m + 1; i <= size - 1; i++)
{
aim = *(alf.m_data + i * size + m);
for (k = m; k <= size - 1; k++)
*(alf.m_data + i * size + k) -= *(alf.m_data + m * size + k) * aim;
*(bet.m_data + i) -= *(bet.m_data + m) * aim;
}//end i
}//end m
// нахождение решения СЛАУ с верхней треугольной матрицей
*(x.m_data + size - 1) = *(bet.m_data + size - 1) / *(alf.m_data + size * (size - 1) + size - 1);
for (i = size - 2; i >= 0; i--)
{
*(x.m_data + i) = *(bet.m_data + i);
for (k = i + 1; k < size; k++)
*(x.m_data + i) -= *(alf.m_data + i * size + k) * *(x.m_data + k);
}//end i
return true;
}
LU декомпозиция
Суть метода заключается в том, что исходная матрица A представляется в виде произведения A = LU, где L - нижняя треугольная матрица, U - верхняя треугольная матрица [2]. В таком случае решение исходной системы уравнений распадается на последовательное решение двух систем уравнений:
Код представлен в виде дружественной классам MATRIX<T> и VECTOR<T> функции:
/// <summary>
/// Декомпозиция матрицы A = LU
/// L - нижняя треугольная матрица, на главной диагонали которой расположены единицы
/// U - верхняя треугольная матрица
/// </summary>
/// <param name="alfa">Матрицы L и U, ниже гланой диагонали которой
/// расположены внедиагональные элементы L,
/// на главной диагонали и выше расположены элементы матрицы U</param>
/// <returns>Определитель матрицы</returns>
template <typename T>
T MATRIX<T>::LUDecomposition(MATRIX& alfa)
{
T det = 0;
if (m_rows != m_columns) return det;
// декомпозиция матрицы
for (int i = 0; i < m_rows; i++)
for (int j = 0; j < m_columns; j++)
{
T val = 0;
if (i <= j)
{
for (int k = 0; k <= i - 1; k++)
val += *(alfa.m_data + i * alfa.m_columns + k) * *(alfa.m_data + k * alfa.m_columns + j);
*(alfa.m_data + i * alfa.m_columns + j) = *(m_data + i * m_columns + j) - val;
}
else
{
for (int k = 0; k <= j - 1; k++)
val += *(alfa.m_data + i * alfa.m_columns + k) * *(alfa.m_data + k * alfa.m_columns + j);
if (abs(*(alfa.m_data + j * alfa.m_columns + j)) < 1.0e-36) return 0;
*(alfa.m_data + i * alfa.m_columns + j) = (*(m_data + i * m_columns + j) - val) / *(alfa.m_data + j * alfa.m_columns + j);
}
}
det = 1;
for(int i = 0; i < alfa.m_rows; i++)
det *= *(alfa.m_data + i * alfa.m_columns + i);
return det;
}
/// <summary>
/// Решение системы линейных алгебраических уравнений с применением метода LU декомпозиции A = LU
/// L - нижняя тругольная матрица
/// U - верхняя треугольная матрица
/// </summary>
/// <param name="A">матрица</param>
/// <param name="b">вектор правой части системы</param>
/// <param name="x">решение системы</param>
template <typename T>
void LUDecompositionSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x)
{
if (A.m_rows != A.m_columns || A.m_rows != b.m_size) return;
MATRIX<T> alfa(A.m_rows, A.m_columns);
int n = A.m_rows;
T det = A.LUDecomposition(alfa);
if (abs(det) > 1.0e-36)
{
// решение СЛАУ
// Решение системы Ly = b
VECTOR<T> y(n);
for (int k = 0; k < n; k++)
{
T val = 0;
for (int j = 0; j <= k - 1; j++)
val += *(alfa.m_data + k * alfa.m_columns + j) * *(y.m_data + j);
*(y.m_data + k) = *(b.m_data + k) - val;
}
// Решение системы Ux = y
for (int k = n - 1; k >= 0; k--)
{
T sum = 0;
for (int j = n - 1; j > k; j--)
sum += *(alfa.m_data + k * alfa.m_columns + j) * *(x.m_data + j);
*(x.m_data + k) = (*(y.m_data + k) - sum) / *(alfa.m_data + k * alfa.m_columns + k);
}
}
}
Компактная схема исключения
Компактная схема исключения описана в справочнике [1]. Компактная схема исключения заключается в сведении решения СЛАУ к решению СЛАУ с верхней треугольной матрицей. Код представлен в виде дружественной классам MATRIX<T> и VECTOR<T> функции:
/// <summary>
/// Формирование матрицы alpha. Верхняя треугольная матрица
/// раполагается на главной диагонали и выше главной диагонали.
/// Значение определителя - произведпение диагональных элементов
/// </summary>
/// <param name="alpha">формируемые матрицы</param>
/// <returns>значение определителя матрицы</returns>
template <typename T>
T MATRIX<T>::FormMatrixCompactScheme(MATRIX& alpha)
{
if (this->m_columns != this->m_rows) return NAN;
int n = this->m_rows;
for (int i = 0; i < n; i++)
{
*(alpha.m_data + i * n) = *(this->m_data + i * n);
if (i > 0) *(alpha.m_data + i) = *(this->m_data + i) / *this->m_data;
}
T sum = 0;
int k = 1, i = 1;
while (i < n)
{
if (k >= n)
{
k = 1; i++;
if (i >= n) break;
}
if (i >= k)
{
sum = 0.0;
for (int j = 0; j <= k - 1; j++)
{
sum += *(alpha.m_data + i * n + j) * *(alpha.m_data + j * n + k);
}
*(alpha.m_data + i * n + k) = *(this->m_data + i * n + k) - sum;
}
else
{
sum = 0.0;
for (int j = 0; j <= i - 1; j++)
{
sum += *(alpha.m_data + i * n + j) * *(alpha.m_data + j * n + k);
}
if (abs(*(alpha.m_data + i * n + i)) < 1.0e-18)
{
return 0.0;
}
*(alpha.m_data + i * n + k) = (*(this->m_data + i * n + k) - sum) / *(alpha.m_data + i * n + i);
}
k++;
}
// вычисление определителя
T det = 1;
for (int i = 0; i < n; i++)
det *= *(alpha.m_data + i * n + i);
return det;
}
/// <summary>
/// Решение системы линейных алгебраических уравнений A*x = b
/// с верхней треугольной матрицей
/// </summary>
/// <param name="A">верхняя треугольная матрица системы</param>
/// <param name="b">вектор правой части системы</param>
/// <param name="x">решение системы</param>
template <typename T>
void TriangleSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x)
{
int n = b.m_size;
VECTOR<T> beta(n);
T sum = 0;
*beta.m_data = *b.m_data / *A.m_data;
for (int i = 1; i < n; i++)
{
sum = 0.0;
for (int j = 0; j <= i - 1; j++)
sum += *(A.m_data + i * n + j) * *(beta.m_data + j);
*(beta.m_data + i) = (*(b.m_data + i) - sum) / *(A.m_data + i * n + i);
}
// решение системы уравнений с труегольной матрицей
*(x.m_data + n - 1) = *(beta.m_data + n - 1);
for (int i = n - 2; i >= 0; i--)
{
sum = 0.0;
for (int j = n - 1; j > i; j--)
sum += *(A.m_data + i * n + j) * *(x.m_data + j);
*(x.m_data + i) = *(beta.m_data + i) - sum;
}
}
/// <summary>
/// Решение системы линейных уравнений компактной схемой исключения
/// </summary>
/// <param name="A">матрица системы уравнений</param>
/// <param name="b">вектор правой части системы уравнений</param>
/// <param name="x">решение системы уравнений</param>
template <typename T>
void CompactSchemeSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x)
{
if (A.m_rows != A.m_columns) return;
MATRIX<T> alpha(A.m_rows, A.m_columns);
T det = A.FormMatrixCompactScheme(alpha);
// решение только для неособенной матрицы
if (det != 0.0)
TriangleSolve(alpha, b, x);
}QR декомпозиция
Суть метода заключается в том, что исходная матрица представляется в виде произведения ортогональной матрицы Q и верхней треугольной матрицы R, A = QR [3,4]. Декомпозиция матрицы осуществляется применением процедуры ортогонализации Грама-Шмидта к столбцам матрицы СЛАУ A. Таким образом, исходная система уравнений сводится к системе уравнений с верхней треугольной матрицей:
Код представлен в виде дружественной классам MATRIX<T> и VECTOR<T> функции:
/// <summary>
/// Получение транспонированной матрицы
/// </summary>
/// <returns>транспонированную матрицу</returns>
template <typename T>
MATRIX<T> MATRIX<T>::Transpose()
{
MATRIX<T> tr(m_columns, m_rows);
for (int i = 0; i < m_columns; i++)
for (int j = 0; j < m_rows; j++)
*(tr.m_data + i * tr.m_columns + j) = *(m_data + j * m_columns + i);
return tr;
}
/// <summary>
/// QR разложение квадратной матрицы
/// </summary>
/// <param name="Q">матрица Q, ортогональная матрица</param>
/// <param name="R">матрица R, верхняя треугольная матрица</param>
template <typename T>
bool MATRIX<T>::QRDecomposition(MATRIX& Q, MATRIX& R)
{
if (m_columns != m_rows) return false;
int n = m_rows;
T sum = 0;
for (int j = 0; j < n; j++)
{
// q(j) = a(j)
for (int k = 0; k < n; k++)
*(Q.m_data + k * n + j) = *(m_data + k * n + j);
for (int i = 0; i <= j - 1; i++)
{
// rij = q(i)^T*a(j)
sum = 0.0;
for (int k = 0; k < n; k++)
sum += *(Q.m_data + k * n + i) * *(m_data + k * n + j);
*(R.m_data + i * n + j) = sum;
// r(i,j)*q(i)
for (int k = 0; k < n; k++)
*(Q.m_data + k * n + j) -= *(R.m_data + i * n + j) * *(Q.m_data + k * n + i);
}
// r(j,j) = || q(j)) ||2
sum = 0.0;
for (int k = 0; k < n; k++)
sum += *(Q.m_data + k * n + j) * *(Q.m_data + k * n + j);
*(R.m_data + j * n + j) = sqrt(sum);
if (*(R.m_data + j * n + j) == 0.0)
{
return false;
}
for (int k = 0; k < n; k++)
*(Q.m_data + k * n + j) /= *(R.m_data + j * n + j);
}
return true;
}
/// <summary>
/// Решение СЛАУ с применением QR декомпозиции матрицы системы A
/// </summary>
/// <param name="A">матрица системы линейных уравнений</param>
/// <param name="b">вектор правой части системы линейных уравнений</param>
/// <param name="x">вектор решения системы линейныз уравнений</param>
template <typename T>
void QRDecompositionSolve(MATRIX<T>& A, VECTOR<T>& b, VECTOR<T>& x)
{
if (A.m_rows != A.m_columns || A.m_rows != b.m_size) return;
MATRIX<T> Q(A.m_rows, A.m_columns), R(A.m_rows, A.m_columns);
int n = A.m_rows;
A.QRDecomposition(Q, R);
VECTOR <T>beta(b.m_size);
beta = Q.Transpose() * b;
// решение системы уравнений с верхней труегольной матрицей
*(x.m_data + n - 1) = *(beta.m_data + n - 1) / *(R.m_data + (n - 1) * n + n - 1);
for (int i = n - 2; i >= 0; i--)
{
T sum = 0;
for (int j = n - 1; j > i; j--)
sum += *(R.m_data + i * n + j) * *(x.m_data + j);
*(x.m_data + i) = (*(beta.m_data + i) - sum) / *(R.m_data + i * n + i);
}
}
Результаты решения системы
Был получен результат решения СЛАУ, вектор решения x:
Время решения СЛАУ и полученная норма невязки представлены в нижеследующей таблице:
Метод решения | Время решения, сек | Норма невязки |
Метод Гаусса | ||
LU декомпозиция | ||
Компактная схема исключения | ||
QR декомпозиция |
Выводы
Как следует из результатов решения, приведённых в таблице выше, наибольшую точность даёт QR декомпозиция, а наилучший результат по времени решения даёт метод Гаусса. Самые худшие результаты и по времени решения, и по точности даёт LU декомпозиция, а наиболее оптимальным методом решения СЛАУ является компактная схема исключения.
Литература
Корн, Г. Справочник по математике для научных работников и инженеров : определения, теоремы, формулы / Г. Корн, Т. Корн ; пер. с англ. под общ. ред. И. Г. Арамановича. — 3‑е изд. — Москва : Наука, 1973. — 832 с.
Википедия. LU - разложение/ URL: https://ru.wikipedia.org/wiki/LU-разложение
Wikipedia. QR decomposition / URL: https://en.wikipedia.org/wiki/QR_decomposition (дата посещения 18.12.2022)
Ланкастер, П. Теория матриц / П. Ланкастер; пер. с англ. Демушкина С.П. — Москва : Наука, 1978. — 280 с.