Ярославский Государственный Университет им. П.Г. Демидова
Опубликован: 06.11.2008 | Доступ: свободный | Студентов: 987 / 62 | Оценка: 4.50 / 4.00 | Длительность: 10:47:00
Лекция 14:

Модель Бэкуса алгебры программ: умножение матриц и алгебраическое преобразование программы

< Лекция 13 || Лекция 14
Аннотация: Программа умножения матриц: пошаговая разработка программы; анализ программы с целью оптимизации; оптимизация программы и доказательство ее корректности с помощью алгебры программ.

Программа умножения матриц и ее алгебраическое преобразование

В качестве примера мощи введенной алгебры программ мы рассмотрим построение программы перемножения матриц. Сначала создадим простую программу, не задумываясь о ее эффективности. Затем построим более сложную, но эффективную программу, и докажем ее корректность, используя аппарат алгебры программ.

Напомним, что умножение матрицы B_1 размерности m\times n на матрицу B_2 размерности n\times k дает результирующую матрицу C размерности m\times k, каждый элемент c_{ij} которой получается как скалярное произведение i -й строки матрицы B_1 на j -й столбец матрицы B_2. Поэтому нужно образовать матрицу пар, где на месте каждого элемента результирующей матрицы C находится кортеж пары из соответствующей строки первой исходной матрицы и соответствующего столбца - второй. Поэтому мы можем определить пока функцию перемножения матриц таким образом:

DEF\text{  }MM\text{  }::\text{  }f_2*f_1,

где функция f_1 готовит матрицу пар, а функция f_2 производит скалярное умножение для каждой пары. Таким образом, мы разбили весь алгоритм на две части.

Для лучшего понимания процесса разработки программы мы будем демонстрировать отдельные части алгоритма на следующем примере исходных данных:

X=<<<3,5,7>,<2,4,6>>,<<9,5>,<6,3>,<1,2>>>.

В этом примере B_1=\left(
\begin{tabular}{lll}
3 & 5 & 7\\
2 & 4 & 6
\end{tabular}
\right) и B_2=\left(
\begin{tabular}{ll}
9 & 5\\
6 & 3\\
1 & 2
\end{tabular}
\right). Матрица пар должна выглядеть следующим кортежем:

<<<<3,5,7>,<9,6,1>>,<<3,5,7>,<5,3,2>>>,\\
\text{    }<<<2,4,6>,<9,6,1>>,<<2,4,6>,<5,3,2>>>>.

Для каждой пары функция f_2 должна выполнить следующую функцию f_3:

DEF\text{  }f_2\text{  }::\text{  }Af_3,

которая должна вычислить скалярное произведение:

DEF\text{  }f_3\text{  }::\text{  }AIP.

Поэтому функция f_2 определяется следующим образом (двойная общая аппликация):

DEF\text{  }f_2\text{  }::\text{  }AAIP

и заменяет кортеж матрицы пар на следующий кортеж

<<64,44>, <48,34>>,

который соответствует результирующей матрице C=\left(
\begin{tabular}{ll}
64 & 44\\
48 & 34
\end{tabular}
\right). Таким образом, функция f_2 определена полностью.

Для определения функции f_1, готовящей матрицу пар, необходимо применить функции расписывания distr и distl. Но так как каждая строка 1-й матрицы сочетается с каждым столбцом второй матрицы, то мы предварительно расположим 2-ю матрицу по столбцам. Таким образом мы разобьем алгоритм f_1 также на две части:

DEF\text{  }f_1\text{  }::\text{  }f_5*f_4,

где f4 транспонирует 2-ю матрицу

DEF\text{  }f_4\text{  }::\text{  }(1, trans*2),

а f_5 сначала расписывает для каждой строки 1-й матрицы всю 2-ю матрицу, и затем из каждой пары (строка, матрица) получает необходимый кортеж (строка, строка) расписыванием слева

DEF\text{  }f_5\text{  }::\text{  }(Adistl)*distr.

Подставляя все введенные промежуточные функции, получим окончательное определение функции перемножения матриц:

{\rm DEF}\ MM\ ::\ \underbrace{({\rm
AA}\,IP)}_{\mbox{скал.произв.}}*\underbrace{({\rm
A}distl*distr)}_{\mbox{подготовка пар}}*
\underbrace{(1,\ trans*2)}_{\mbox{подгот.2-й матр.}}.

В полученной программе нет ничего лишнего: структура программы-формулы получена из постановки задачи; имеется концептуальная ясность всех конструкций, входящих в формулу.

Программа не сразу понятна, так как новый стиль усваивается не сразу, но такой подход стоит усилий. В отличие от языков фон Неймановского типа нет промежуточных данных, нет переменных, нет управляющих конструкций, нет процедур, нет инициализации.

Зато есть концептуальная ясность, есть обобщенность, есть возможность распараллелить процесс (вычисление скалярных произведений), а также есть возможность оптимизации: скалярные произведения можно вычислять не параллельно, а последовательно, не требуя большой промежуточной памяти для вычислений.

Стиль ориентирован на концептуальную ясность программы и предполагает поэтому при разработке концентрацию внимания на решаемой задаче и игнорирование второстепенных пока вопросов, связанных, например, с необходимыми ресурсами. Когда решение найдено и оно не устраивает по каким-то свойствам (например, по ресурсоемкости), можно сосредоточить дальнейшие усилия на постановке и решении новой задачи, для чего проводится также пошаговая детализация в поиске оптимизации.

Проанализируем полученное решение MM умножения матриц с целью оптимизации. Прежде всего, мы видим, что MM требует много памяти:

  1. во-первых, при подготовке матрицы пар каждый элемент 1-й матрицы расписывается со всей 2-й матрицей; это требует памяти для mn^2k элементов;
  2. во-вторых, сама матрица пар содержит mk пар из 2n элементов, т. е. требует объем памяти для 2mnk элементов.

Улучшение программы, связанное с экономией памяти, никак не должно изменить ее первую часть - подготовку транспонированием 2-й матрицы. Поэтому мы будем изменять программу в остальной ее части, которую назовем MM^{'}:

{\rm DEF}\ MM^{'}\ ::\ {\rm
AA}\,IP*{\rm A}distl*distr.

Основная идея состоит в перемножении строки 1-й матрицы на 2-ю матрицу (образование строки результирующей матрицы). Тогда последующие строки можно перемножить на месте, освободившемся после перемножения предыдущих строк. Объем памяти, требующийся для этого, равен n+nk=(n+1)k, т. е. меньше по крайней мере в m раз, что существенно. Для этого мы сначала определим программу mM умножения 1-й строки 1-й матрицы на 2-ю матрицу:

{\rm DEF}\ mM\ ::\ {\rm A}\, IP*
distl* (1*1,\ 2).

Определим программу R:

{\rm DEF}\ R\ ::\ null*1 \rightarrow
const(<>);\ appendl*(mM,\ MM^{'}*(t1*1,\ 2)).

Если удастся доказать эквивалентность программ MM^{'} и R, то в определении программы R можно будет заменить MM^{'} на R:

{\rm DEF}\ R\ ::\ null*1 \rightarrow const(<>);\ appendl*(mM,\ R*(t1*1,\ 2)),

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

Таким образом, для верификации программы R (доказательства ее корректности) нужно показать, что R=MM^{'}. Могут быть разные способы такого доказательства, но нас будет интересовать алгебраический подход к доказательству эквивалентности программ.

Теорема 2. pair \Rightarrow MM^{'}=R.

Доказательство. Рассмотрим 2 случая:

  1. 1-я матрица пуста (null*1) ;
  2. 1-я матрица не пуста (not*null*1).
  • pair\ \&\ (null*1) \Rightarrow R=const(<>)\text{   (по определению R).}\\
pair\ \&\ (null*1) \Rightarrow MM^{'}=const(<>) \\
\text{(так как }distr:(<>,X)=<>\text{   по определению distr и}\\           
A f:<>=<>\text{   по определению A.)}
  • Надо доказать: pair\text{  }&\text{ } (not*null*1) \Rightarrow MM^{'}=R.

    В этом случае R=R^{''}, где

    {\rm DEF}\ R^{''}\ ::\ appendl*(mM,\ MM^{'}*(t1*1,\ 2)) - по определению R. Распишем mM. Тогда

    R^{''}=appendl*(\underbrace{{\rm
A}IP*distl}_{f}*\underbrace{(1*1,2)}_{g},\\
          \mbox{\qquad\qquad\qquad\qquad\qquad\qquad\quad
\underbrace{{\rm AA}IP*{\rm
A}distl}_{k}*\underbrace{distr*(t1*1,2)}_{h}).}\\

    Из аксиомы 6^o следует {\rm A}(\underbrace{{\rm A}Ip*distl}_{f})=\underbrace{{\rm AA}IP*{\rm A}distl}_{k}, так как k={\rm A}f. Поэтому при таких обозначениях

    R^{''}= appendl*(f*g,\ {\rm A}f*h). И потому из аксиомы 5^o следует R^{''}=f*appendl*(g,\ h)={\rm A}f*appendl*((1*1,\ 2),\ distr*(t1*1,\ 2)). Из теоремы 1 следует R^{''}={\rm A}f*distr=MM^{'}. Теорема доказана.

Упражнения

  1. Упростите формулу алгебры программ: f*(g*1,f*(g*2,\dots,f*g*n)\dots)), где n - селектор выбора n -го элемента кортежа.
  2. Упростите формулу алгебры программ: (f*1,\dots,f*n)*(g*1,\dots,g*n), где n - селектор выбора n -го элемента кортежа.
< Лекция 13 || Лекция 14