Вычисление обратной матрицы квадратной матрицы высокого порядка, а именно, вычисление алгебраических дополнений и определителя матрицы займёт большое количество машинных ресурсов. Поэтому обращение матрицы при помощи алгебраических дополнений нерационально. В этой статье для вычисления обратной матрицы были использованы нижеследующие методы:
Решение системы AX = E, где A, X, E - квадратные матрицы порядка n, X - обратная A матрица, E - единичная матрица,
Использование LU разложения матрицы A = LU [1,2,3]
Обращение матриц было реализовано в виде шаблонов функций шаблона класса MATRIX<T>. В данной статье обсуждается решение вышеуказанными методами обращения квадратной матрицы порядка n и полученные результаты.
Решение системы AX = E
Решение матричного уравнения AX = E для матрицы порядка n A сводится к решению n систем линейных уравнений:
Код реализации решения приводится ниже:
/// <summary>
/// Формирование матриц alpha и gamma в компактной схеме исключения
/// Матрица alpha верхняя треугольная, gamma - нижняя треугольная
/// </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>
/// Копировать вектор в j-ю колонку матрицы
/// </summary>
/// <param name="v">вектор</param>
/// <param name="j">номер колонки</param>
template <typename T>
void MATRIX<T>::CopyColumn(VECTOR<T>& v, int j)
{
if (m_columns < j)
{
return;
}
if (m_rows != v.size())
{
return;
}
for (int i = 0; i < m_rows; i++)
*(m_data + i * m_columns + j) = v[i];
}
/// <summary>
/// Вычисляет первую норму матрицы
/// </summary>
/// <returns>значение первой нормы матрицы</returns>
template <typename T>
T MATRIX<T>::normI()
{
T norm = 0;
for (int i = 0; i < m_rows; i++)
{
T sum = 0;
for (int j = 0; j < m_columns; j++)
sum += abs(*(m_data + i * m_columns + j));
if (sum > norm) norm = sum;
}
return norm;
}
/// <summary>
/// Обращение марицы при помощи решения систем уравнений A*X = E,
/// столбцы матрицы X - столбцы обратной к A матрицы,
/// E - единичная матрица
/// </summary>
/// <returns>обратную матрицу</returns>
template <typename T>
MATRIX<T> MATRIX<T>::Invert()
{
MATRIX A(m_rows, m_columns);
if (m_rows != m_columns)
return A;
MATRIX alpha(m_rows, m_columns);
T det = FormMatrixCompactScheme(alpha);
if (abs(det) < 1.0e-36)
return A;
for (int i = 0; i < m_rows; i++)
{
VECTOR<T> e(m_rows), x(m_rows);
e[i] = 1;
TriangleSolve(alpha, e, x);
A.CopyColumn(x, i);
}
// итерационное уточнение обратной матрицы
if (m_rows >= MIN_SIZE_INVERSION)
{
MATRIX E(m_rows, m_columns);
for (int i = 0; i < m_rows; i++)
*(E.m_data + i * m_columns + i) = 1;
int iter = 0;
T norm;
do
{
MATRIX R(E - *this * A);
A = A * (E + R);
norm = R.normI();
if (++iter > MAX_ITER_NUMBER) break;
} while (norm < 1.0e-17);
}
return A;
}
Перегрузка операторов *, =, +, - реализована в шаблоне класса MATRIX<T>. Решение системы уравнений осуществлялось с использованием компактной схемы исключения [4].
Обращение матрицы с использованием LU разложения матрицы
Разложение матрицы A = LU на произведение нижней треугольной L и верхней треугольной U может быть использовано не только для решения системы линейных алгебраических уравнений, но и для обращения матрицы. Описание метода обращения матрицы при помощи LU разложения приведён в источниках [2,3] и за��лючается в следующем:
Обозначая искомые элементы матрицы через
и учитывая, что треугольные матрицы при обращении сохраняют свою структуру, последние соотношения можно записать в виде:
где символом '*' обозначены элементы, знание значений которых для нахождения обратной матрицы не требуется.
Из этих соотношений можно получить выражения для коэффициентов обратной матрицы :
Код реализации решения приводится ниже:
/// <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>
/// Вычисляет первую норму матрицы
/// </summary>
/// <returns>значение первой нормы матрицы</returns>
template <typename T>
T MATRIX<T>::normI()
{
T norm = 0;
for (int i = 0; i < m_rows; i++)
{
T sum = 0;
for (int j = 0; j < m_columns; j++)
sum += abs(*(m_data + i * m_columns + j));
if (sum > norm) norm = sum;
}
return norm;
}
/// <summary>
/// Обращение матриы с пощью LU разложения
/// </summary>
/// <returns>обратную матрицу</returns>
template <typename T>
MATRIX <T> MATRIX <T>::InvertLT()
{
MATRIX D(m_rows, m_columns), alpha(m_rows, m_columns);
if (m_rows != m_columns) // матрица должна быть квадратной
return D;
T det = LUDecomposition(alpha); // декомпозиция A = LU
if (abs(det) < 1.0e-36)
return D;
// формирование элементов обратной матрицы
int n = m_rows;
for (int i = n-1; i >= 0; i--)
for(int j = n-1;j >= 0; j--)
{
T sum = 0;
if (i < j)
{
for (int k = i + 1; k < n; k++)
sum -= *(alpha.m_data + i * alpha.m_columns + k) * *(D.m_data + k * D.m_columns + j);
*(D.m_data + i * D.m_columns + j) = sum / *(alpha.m_data + i * alpha.m_columns + i);
}
else if (i == j)
{
for (int k = j + 1; k < n; k++)
sum += *(alpha.m_data + j * alpha.m_columns + k) * *(D.m_data + k * D.m_columns + j);
*(D.m_data + j * D.m_columns + i) = (1-sum) / *(alpha.m_data + j * alpha.m_columns + j);
}
else
{
for (int k = j + 1; k < n; k++)
sum -= *(alpha.m_data + k * alpha.m_columns + j) * *(D.m_data + i * D.m_columns + k);
*(D.m_data + i * D.m_columns + j) = sum;
}
}
// итерационное уточнение обратной матрицы
if (m_rows >= MIN_SIZE_INVERSION)
{
MATRIX E(m_rows, m_columns);
for (int i = 0; i < m_rows; i++)
*(E.m_data + i * m_columns + i) = 1;
int iter = 0;
T norm;
do
{
MATRIX R(E - *this * D);
D = D * (E + R);
norm = R.normI();
if (++iter > MAX_ITER_NUMBER) break;
} while (norm < 1.0e-17);
}
return D;
}
Результаты расчётов
Расчёты по обращению квадратной матрицы порядка n проводились обоими методами для разных значений n. Исходная матрица A порядка n заполнялась при помощи генератора случайных чисел в интервале от -1000 до 1000. В качестве меры погрешности вычислений была использована I-норма
матрицы , где E - единичная матрица.
Расчёты проводились без итерационного уточнения и с итерационным уточнением обратной матрицы.
Итерационное уточнение осуществлялось следующим образом [2]. Пусть первое приближение матрицы
. Если при этом
, то можно построить итерационный процесс:
Непосредственной проверкой можно убедиться в том, что [2]:
Откуда далее следует оценка погрешности [2]:
Расчёт без итерационного уточнения
Результаты расчётов без итерационного уточнения матрицы приведены в нижеследующей таблице:
Порядок матрицы A | Решение уравнения AX=E | Декомпозиция A=LU | ||
Время, сек | Погрешность | Время, сек | Погрешность | |
50 | 0,0007115 | 0,0003418 | ||
100 | 0,0030217 | 0,0024037 | ||
200 | 0,0238938 | 0,0193017 | ||
500 | 0,349852 | 0,288407 | ||
1000 | 2,83838 | 2,36379 | 0,0509241 | |
2000 | 27,9798 | 33,5439 | 0,00136141 | |
Расчёт с итерационным уточнением
По приведённым результатам расчёта без итерационного уточнения было сделано заключение о нецелесообразности итерационного уточнения обращения квадратной матрицы порядка ниже 200. Результаты расчётов с итерационным уточнением матрицы приведены в нижеследующей таблице:
Порядок матрицы A | Решение уравнения AX=E | Декомпозиция A=LU | ||
Время, сек | Погрешность | Время, сек | Погрешность | |
200 | 0,0981653 | 0,0836408 | ||
500 | 1,44216 | 1,31034 | ||
1000 | 11,4696 | 10,9775 | ||
2000 | 106,481 | 109,584 | ||
Выводы
Обращение матрицы без итерационного уточнения метод LU декомпозиции показывает лучшие результаты по времени, а метод решения системы AX = E демонстрирует лучшие результаты по точности. При этом, с ростом порядка квадратной матрицы разница в точности расчёта методом LU декомпозиции и методом решения системы AX = E возрастает на несколько порядков, а при обращении матрицы порядка 2000 метод решения системы AX = E демонстрирует лучшие результаты как по времени, так и по точности.
Сравнительный анализ обращения матрицы с итерационным уточнением методом LU декомпозиции и методом решения системы AX = E показывает, что разница как по точности расчёта, так по времени несущественна, а итерационное уточнение вычисления обратной матрицы даёт хорошие результаты по точности методом LU в сравнении с решением без итерационного уточнения.
Список источников
Википедия. LU - разложение/ URL: https://ru.wikipedia.org/wiki/LU-разложение (дата посещения 18.12.2022)
Фаддеев, Д.К. Вычислительные методы линейной алгебры: Учебник/ Фаддеев Д.К, Фаддеева В.Н. — 4‑е изд.— СПб : Лань, 2009. — 736 с.
Численные методы на Python: LU разложение/ URL: https://orion1401.gitbooks.io/numerical_analysys_python/content/lu_razlozhenie.html (дата посещения 22.12.2025)
Корн, Г. Справочник по математике для научных работников и инженеров : определения, теоремы, формулы / Г. Корн, Т. Корн ; пер. с англ. под общ. ред. И. Г. Арамановича. — 3‑е изд. — Москва : Наука, 1973. — 832 с.
