Сидел я значит на курсе Coursera. Курс был нацелен на начинающих по Java, и в одном из видеоуроков была поднята тема 7 Steps for Solving Problems.

Я с чистой совестью прослушал видеоурок, подумал про себя, пошло это дело далеко и надолго.

Остальная часть курса прошла легко, ненапряжно. Но, на одном из предметов в универе, а именно по так называемому «Научному программированию» появилось следующее задание:
Решить систему линейных уравнений $Ax=b$, используя следующее разложение матрицы $A$:

$A = LU,$

где $L$ — нижняя треугольная матрица, а $U$ — верхняя треугольная матрица.

Тема линейной алгебры, которую мы проходили на 1 курсе. Говоря откровенно — я помнил общие принципы операций с матрицами, векторами, ну и святая святых — Метод Гаусса. Если пораскинуть мозгами, то никакого LU-разложения мы не изучали, зато метод «загугли» всегда выручает. Выручал, до этого момента.

Я нашел общий алгоритм для нахождения матриц $L$ и $U$, но в половину четвертого утра никакой кофе мне не давал возможности нормально его реализовать (учитывая то, что кодили мы там на Octave). В итоге я психанул, и решил все же прислушаться к умно названной поговорке.

1. Опиши проблему руками


Все, чего тебе не хватает в жизни, это понять — в чем реально заключается проблема. Давай возьмем в пример вышеописанную задачку и распишем ее на листочке. По-хорошему, должны выполняться следующие условия для существования матриц $L$ и $U$:

— должна существовать матрица, обратная $A$, то бишь $\exists{A^{-1}}$;
— все главные миноры не должны быть вырожденным, то бишь $\Delta_{(1,...,n)}\neq{0}$

Отлично, значит нам как минимум нужна «проверка на дурака», потому что обязательно найдутся умники, которые захотят использовать неподходящую для нас матрицу. Но это пока опустим.

Проблема нам ясна: найти матрицы $L$ и $U$, которые будут подпадать под вышеописанные условия. Попробовав загуглить способы решения, вряд ли у тебя получится с первого раза грамотно реализовать алгоритм (если ты не хорошо соображаешь в линейной алгебре). Пройдя сквозь тонны попыток, у нас скорее всего выработается какая-то последовательность действий для решения нашей проблемы, и тогда мы переходим к шагу 2.

2. Опиши то, что ты сделал


Возьмем в пример матрицу

$A = \begin{pmatrix} 1&2&3 \newline 0&-2&4 \newline 1&-1&0 \end{pmatrix}.$



Теперь применим к ней наш метод Гаусса и избавимся от лишних элементов в 3 строке, засчет вычитания из нее первой строки, умноженной на отношение первого элемента 3 строки на первый элемент 1 строки:

$a_3=a_3-a_1 \cdot\frac { {a_3}_1 }{ {a_1}_1 }.$



Теперь, избавимся от второго элемента 3 строки похожим действием:

$a_3=a_3-a_2 \cdot\frac { {a_3}_2 }{ {a_2}_2 }.$



В результате получим матрицу

$A = \begin{pmatrix} 1&2&3 \newline 0&-2&4 \newline 0&0&0 \end{pmatrix}=U.$



А теперь, найденные нами коэффициенты (результат отношения элементов $\frac{{a_i}_j}{{a_j}_j}$) возьмем за значения матрицы $L$, и получим

$L = \begin{pmatrix} 1&0&0 \newline 0&1&0 \newline 1&\frac{1}{2}&1 \end{pmatrix}.$



Проглядывается шаблон, не правда ли?

3. Нахождение шаблонов


На этом этапе, вероятнее всего, ты будешь возвращаться к шагу 1 и 2, потому что не всегда найденный тобой шаблон будет верен для всех случаев. Но ничего, коль раз не получилось, получится в следующий.

Итак, исходя из примера, можно сформировать следующий алгоритм/шаблон решения:

1. Задаем квадратную матрицу $A, n$-го порядка;
2. Задаем матрицу $L = I$, где $I$ — единичная матрица $n$-го порядка;
3. Задаем матрицу $U = A$;
4. Циклично применяем к матрицам следующие последовательные преобразование, при условии, что $\begin{cases} i=2,\ldots, n \newline j=1,\ldots,i \end{cases}$

$\begin{array}{c} {l_i}_j=\frac{{u_i}_j}{{u_j}_j} \newline u_i = u_i-u_j\cdot{{{l_i}_j}} \end{array},$



При $n = 3$, получаем:

$ L = \begin{pmatrix} 1&0&0 \newline {l_2}_1&1&0 \newline {l_3}_1&{l_3}_2&1 \end{pmatrix},\quad U = \begin{pmatrix} {u_1}_1&{u_1}_2&{u_1}_3 \newline 0&{u_2}_2&{u_2}_3 \newline 0&0&{u_3}_3 \end{pmatrix}$



4. Проверка шаблона/алгоритма


Есть алгоритм — есть миллионы матриц для его проверки! За этот шаблон можете не переживать, он очевидно верен (лично проверено на самых невырожденных из возможных матриц). Но помните: поспешишь — людей насмешишь. Так что всегда проверяйте созданные вами шаблоны, чтобы дальше не скакать как чертям окаянным к самому началу.

5. Перевод алгоритма в код


Очевидный шаг, все нужное зачастую уже есть в синтаксисе языка, а если нет — никто не мешает вам создать классы и функции для решения этой проблемы, алгоритм то у вас есть. Так как я реализовывал этот алгоритм в рамках образовательной университетской программы, то и для реализации я мог использовать только Octave, поэтому весь код я представляю вам именно в его синтаксисе (он крайне схож с Python, поэтому для форматирования я буду использовать его подсветку):

A = input("Enter the matrix A: ")
numOfRows = size(A, 1)
numOfCols = size(A, 2)

if numOfRows ~= numOfCols
    disp("Wrong matrix.")
else
    n = numOfCols
    disp("----------------")
    L = eye(3)
    disp("Making U = A")
    U = A
    disp("----------------")

    for i=2:n
        disp("Step "), disp(i-1)
        for j=1:i-1
            L(i,j) = U(i,j)/U(j,j)
            U(i,:) = U(i,:) - U(j,:)*L(i,j)
        end
        disp("----------------")
    end

    disp("Check the answer of L*U: "), disp(L*U)
end

6. Поиск неудачных тестов


Всегда надо пытаться «сломать» свой код. Здесь (очевидно) нет полной «защиты от дурака», но я взял за основу допущение, что вводиться будет изначально верная матрица.

На этом шаге вы должны найти все возможные ошибки, неточности вашего кода, в следствие чего может выявиться и необъективность вами построенного алгоритма. Поэтому здесь вы должны опробовать как можно больше упущений, которые могли быть допущены.

7. Дебаг неудачных тестов


Самый логичный, и наверное, ожидаемый из шагов. Код должен быть продебажен, а алгоритм исправлен. Здесь особо больше ничего не добавишь.

Заключение


Могу сказать, что вряд ли я когда-нибудь бы серьезно отнесся к этому подходу решения проблем, если бы не мое сонное состояние. Но стоит обратить внимание на этот метод — он краток, и вся его трудоемкость заключяется лишь в том, что его надо использовать.

Примечание


Спасибо учебникам линейной алгебры за материал для решения задачи.