3.2. LU-разложение#
На практике оказывается, что проще работать не с самой матрицей, а с представлением её в виде произведения нескольких матриц, которое называется разложением. В данном разделе выводится алгоритм решения системы уравнений, представляющий исходную матрицу в виде произведения \(\mathbf{A}=\mathbf{L}\mathbf{U}\), после чего пользующийся прямой и обратной подстановкой.
3.2.1. Внешнее произведение#
Вывод алгоритма разложения основывается на внешнем произведении векторов (outer product). Для двух векторов \(\mathbf{u} \in \real^m\), \(\mathbf{v} \in \real^n\) внешнее произведение является матрицей размера \(m \times n\)
Другими словами, столбец \(\mathbf{u}\) умножается на строку \(\mathbf{v}\).
Произведение матриц \(\mathbf{A}\mathbf{B}\) является суммой внешних произведений столбцов \(\mathbf{a}_{k}\) матрицы \(\mathbf{A}\) на строки \(\mathbf{b}^{\top}_{k}\) матрицы \(\mathbf{B}\), т.е.
Рассмотрим теперь произведение нижнетреугольной матрицы на верхнетреугольную \(\mathbf{L}\mathbf{U}\). Обозначим \(\mathbf{l}_k\) столбцы матрицы \(\mathbf{L}\), а \(\mathbf{u}_k^\top\) – строки матрицы \(\mathbf{U}\). Тогда произведение запишется в виде (3.1)
Первая строка этого произведения имеет вид (здесь \(\mathbf{e}_1^\top\) – первая строка единичной матрицы, домножение на неё слева выделяет первую строку)
поскольку только первый элемент столбца \(\mathbf{l}_k\) ненулевой только у \(\mathbf{l}_1\). Посмотрите на схему ниже
L U
| □ ⋅ ⋅ ⋅ | | □ □ □ □ |
| □ □ ⋅ ⋅ | | ⋅ □ □ □ |
| □ □ □ ⋅ | | ⋅ ⋅ □ □ |
| □ □ □ □ | | ⋅ ⋅ ⋅ □ |
Первый же столбец \(\mathbf{L}\mathbf{U}\) имеет вид
Воспользуемся свойствами (3.2) и (3.3) для вывода LU-разложения.
3.2.2. Разложение#
Для квадратной матрицы \(\mathbf{A}\) размера \(n \times n\) необходимо найти
где \(\mathbf{L}\) – нижнетреугольная матрица, а \(\mathbf{U}\) – верхнетреугольная матрица.
LU-разложение сводится к нахождению \(n^2 + n\) неизвестных.
Пусть диагональные элементы \(\mathbf{L}\) единичны \(L_{11} = L_{22} = \cdots = L_{nn} = 1\). Теперь, зная \(L_{11} = 1\), применим (3.2), и получим
т.е. первая строка матрицы \(\mathbf{U}\) равняется первой строке матрицы \(\mathbf{A}\), \(\mathbf{u}_1^\top = \mathbf{a}_1^\top\).
На этом шаге нам стало известно значение \(U_{11}\). Воспользуемся теперь (3.3)
откуда следует выражение для первого столбца матрицы \(\mathbf{L}\)
Теперь вычтем из исходной матрицы внешнее произведение
Полученная матрица \(\mathbf{A}_2\) имеет нулевую первую строку и нулевой первый столбец.
Таким образом, задача свелась к предыдущей, но, фактически, на квадратной матрице меньшего размера. Повторяя эту процедуру дальше, получим остальные строки и столбцы разложения. Ниже приведена схема вычислений и демонстрация на небольшой матрице.
A₁ = A A₂ A₃ A₄
| □ □ □ □ | | ⋅ ⋅ ⋅ ⋅ | | ⋅ ⋅ ⋅ ⋅ | | ⋅ ⋅ ⋅ ⋅ |
| □ □ □ □ | | ⋅ □ □ □ | | ⋅ ⋅ ⋅ ⋅ | | ⋅ ⋅ ⋅ ⋅ |
| □ □ □ □ | | ⋅ □ □ □ | | ⋅ ⋅ □ □ | | ⋅ ⋅ ⋅ ⋅ |
| □ □ □ □ | | ⋅ □ □ □ | | ⋅ ⋅ □ □ | | ⋅ ⋅ ⋅ □ |
Рассмотрим пример работы алгоритма на матрице размера \(4 \times 4\). Исходную матрицу обозначим A₁
, нижнетреугольную L
проинициализируем единичной матрицей, а верхнетреугольную U
нулями.
A₁ = Float64[
5 1 0 9;
4 2 -1 4;
8 -1 4 1;
5 7 4 6;
]
L = diagm(0 => ones(4))
U = zeros(4, 4);
Первый шаг алгоритма основывается на (3.2): вычисляем первую строку U
U[1, :] .= A₁[1, :]
U
4×4 Matrix{Float64}:
5.0 1.0 0.0 9.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
Теперь вычислим первый столбец L
(3.3)
L[:, 1] .= A₁[:, 1] ./ U[1, 1]
L
4×4 Matrix{Float64}:
1.0 0.0 0.0 0.0
0.8 1.0 0.0 0.0
1.6 0.0 1.0 0.0
1.0 0.0 0.0 1.0
Вычтем из A₁
внешнее произведение первого столбца L
и первой строки U
A₂ = A₁ - L[:, 1] * U[1, :]'
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 1.2 -1.0 -3.2
0.0 -2.6 4.0 -13.4
0.0 6.0 4.0 -3.0
\(\mathbf{A}_2 = \mathbf{l}_2 \mathbf{u}_2^\top + \mathbf{l}_3 \mathbf{u}_3^\top + \mathbf{l}_4 \mathbf{u}_4^\top\). Так, задача свелась к предыдущей, на матрице размера \(3 \times 3\).
U[2, :] .= A₂[2, :]
L[:, 2] .= A₂[:, 2] ./ U[2, 2]
A₃ = A₂ - L[:, 2] * U[2, :]'
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 1.83333 -20.3333
0.0 0.0 9.0 13.0
Предпоследний шаг
U[3, :] .= A₃[3, :]
L[:, 3] .= A₃[:, 3] ./ U[3, 3]
A₄ = A₃ - L[:, 3] * U[3, :]'
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 112.818
И последний
U[4, 4] = A₄[4, 4];
Теперь известно разложение матрицы
U
4×4 Matrix{Float64}:
5.0 1.0 0.0 9.0
0.0 1.2 -1.0 -3.2
0.0 0.0 1.83333 -20.3333
0.0 0.0 0.0 112.818
L
4×4 Matrix{Float64}:
1.0 0.0 0.0 0.0
0.8 1.0 0.0 0.0
1.6 -2.16667 1.0 0.0
1.0 5.0 4.90909 1.0
Проверим, что \(\mathbf{A} = \mathbf{LU}\). Из-за погрешность вычислений с плавающей точкой, возможны неточности.
A₁ - L * U
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 2.22045e-16 0.0 0.0
0.0 0.0 0.0 0.0
Следуя примеру выше несложно написать реализацию.
LU-разложение (нестабильное)
"Нестабильное LU-разложение квадратной матрицы `A`. Возвращает `L`, `U`."
function lufact(A::AbstractMatrix)
n = size(A, 1)
L = diagm(0 => ones(n))
U = zeros(n, n)
Aₖ = float(copy(A))
for k in 1:n-1
U[k, :] .= Aₖ[k, :]
L[:, k] .= Aₖ[:, k] ./ U[k, k]
Aₖ .-= L[:, k] * U[k, :]'
end
U[n, n] = Aₖ[n, n]
return LowerTriangular(L), UpperTriangular(U)
end
Примечание
В строке Aₖ = float(copy(A))
используется копирование входной матрицы, поскольку в Julia массивы передаются в функцию по ссылке.
В теле цикла использован броадкаст операций для избежания выделений памяти. Например, операция Aₖ[:, k] / U[k, k]
выделяет вектор Aₖ[:, k]
, а затем выделяет новый вектор в результате деления. В случае с броадкастом операций такие выделения памяти не происходят.
3.2.3. Решение системы уравнений#
Пусть дана система уравнений \(\mathbf{A x} = \mathbf{b}\). Алгоритм решения следующий
Разложить матрицу \(\mathbf{A} = \mathbf{LU}\);
Решить \(\mathbf{L z} = \mathbf{b}\), используя прямую подставновку;
Решить \(\mathbf{U x} = \mathbf{z}\), используя обратную подстановку.
Решение системы уравнений через LU-разложение имеет преимущество над методом Гаусса. Так, в методе Гаусса составляется матрица \([\mathbf{A}\:\mathbf{b}]\), где левая часть приводится к треугольному виду. В результате, для нескольких систем с разными правыми частями приходится проводить процедуру заново. В LU-разложении достаточно один раз разложить исходную матрицу, а затем воспользоваться шагами 2 и 3 алгоритма выше для разных правых частей \(\mathbf{b}\).
Рассмотрим решение системы
A = [5 1 0 9; 4 2 -1 4; 8 -1 4 1; 5 7 4 6]
b = [1, 2, 7, 3];
Сначала выполним разложение, а затем последовательно воспользуемся прямой и обратной подстановками
L, U = lufact(A)
z = forwardsub(L, b)
x = backwardsub(U, z)
4-element Vector{Float64}:
0.876712328767123
0.06849315068493178
0.10958904109589054
-0.38356164383561636
Невязка при этом составила
b - A*x
4-element Vector{Float64}:
-4.440892098500626e-16
2.220446049250313e-16
8.881784197001252e-16
-1.7763568394002505e-15