|
1 | 1 | \section{Прикладные особенности} |
2 | | - \tikzsetfigurename{LinearAlgebra_Applied_} |
| 2 | +\tikzsetfigurename{LinearAlgebra_Applied_} |
3 | 3 |
|
4 | | -\mytodo{Написать раздел} |
5 | | -Планируемое содержание раздела: Взгляд программиста: типы данных, не совсем честные алгебраические структуры (<<просто лишь бы типизировалось>>), GraphBLAS, разреженность, параллельность. Операции типа маски, map2 и так далее. |
6 | | -\mytodo{Перенести из GraphBLAS_in_functional_style_КИО. Про map2, Option, GraphBLAS и его проблемы. Связать с определением обобщённой матрицы смежности, с поэлементными операциями. } |
| 4 | +В предыдущих разделах мы определили полугруппы, моноиды, группы, полукольца и кольца как <<честные>> алгебраические структуры: сформулировали аксиомы, которым они должны удовлетворять, и привели примеры. |
| 5 | +На практике же при реализации алгоритмов анализа графов эти аксиомы часто соблюдаются лишь частично, а на первый план выходят вопросы представления данных, эффективности операций и удобства программного интерфейса. |
| 6 | +Данный раздел посвящён именно таким прикладным аспектам~\sidecite{7761646}. |
| 7 | + |
| 8 | +Начнём с фундаментального вопроса~--- представления данных. |
| 9 | +При определении матрицы смежности (раздел~\ref{chpt:GraphTheoryIntro}) мы ввели множество $\Opt{L} = \{\Bbbzero\} \cup L$, где $\Bbbzero$ означает <<ребро отсутствует>>. |
| 10 | +В языках программирования такой конструкции естественно соответствует тип \texttt{Option} (OCaml, F\#) или \texttt{Maybe} (Haskell): значение \texttt{Some x} означает <<есть ребро с меткой \texttt{x}>>, а \texttt{None}~--- <<ребра нет>>. |
| 11 | +Таким образом, матрица смежности графа с метками типа \texttt{Lbl} имеет тип $\texttt{Matrix}\langle\texttt{Option}\langle\texttt{Lbl}\rangle\rangle$. |
| 12 | + |
| 13 | +В реальных графах число рёбер, как правило, существенно меньше $|V|^2$, поэтому матрицы смежности разрежены: подавляющее большинство ячеек содержат \texttt{None}. |
| 14 | +Хранить их все нет смысла~--- достаточно хранить лишь те ячейки, которые соответствуют реально существующим рёбрам, то есть имеют значение \texttt{Some x}. |
| 15 | +Для этого существует множество форматов разреженных матриц: CSR (Compressed Sparse Row), координатный, Quad Tree и другие. |
| 16 | +Выбор формата определяется конкретной задачей и используемой библиотекой, но все они основаны на одной и той же идее: хранить только <<непустые>> ячейки. |
| 17 | + |
| 18 | +Определившись со способом хранения, перейдём к операциям над разреженными матрицами. |
| 19 | +Поэлементная операция над двумя матрицами~--- это операция, которая применяется независимо к каждой паре ячеек с одинаковыми индексами. |
| 20 | +Наивная типизация такой операции имеет вид |
| 21 | +\[ |
| 22 | + \texttt{Option}\langle T_1\rangle \to \texttt{Option}\langle T_2\rangle \to \texttt{Option}\langle T_3\rangle. |
| 23 | +\] |
| 24 | +Однако эта сигнатура недостаточно ограничительна: она позволяет определить операцию, возвращающую \texttt{Some x} даже тогда, когда оба аргумента равны \texttt{None}. |
| 25 | +Иными словами, можно <<создать значение из двух пустот>>. |
| 26 | +Эта проблема известна в сообществе GraphBLAS как \emph{проблема явных и неявных нулей}~\sidecite{7761646}: разреженная матрица хранит только явные (ненулевые) значения, но комбинация двух не-хранимых нулей не должна порождать новое хранимое значение. |
| 27 | + |
| 28 | +Решение~--- ввести тип $\texttt{AtLeastOne}\langle T_1, T_2\rangle$, который гарантирует, что хотя бы один из аргументов непуст: |
| 29 | +\begin{align*} |
| 30 | + \texttt{AtLeastOne}\langle T_1, T_2\rangle =\ &\texttt{Both}\ \texttt{of}\ T_1 * T_2 \\ |
| 31 | + \mid\ &\texttt{Left}\ \texttt{of}\ T_1 \\ |
| 32 | + \mid\ &\texttt{Right}\ \texttt{of}\ T_2. |
| 33 | +\end{align*} |
| 34 | +Теперь поэлементная операция получает сигнатуру |
| 35 | +\[ |
| 36 | + \texttt{op}: \texttt{AtLeastOne}\langle T_1, T_2\rangle \to \texttt{Option}\langle T_3\rangle, |
| 37 | +\] |
| 38 | +которая на уровне типов гарантирует, что операция никогда не будет вызвана с двумя \texttt{None}. |
| 39 | +При этом тип результата остаётся $\texttt{Option}\langle T_3\rangle$: операция всё ещё может вернуть \texttt{None}, и это просто означает, что в данной позиции результирующая ячейка не должна храниться. |
| 40 | + |
| 41 | +Использование $\texttt{AtLeastOne}$ позволяет унифицировать поэлементные операции, заменив несколько специализированных функций одной обобщённой высокопорядковой функцией $\texttt{map2}$~\sidecite{7761646}: |
| 42 | +\begin{align*} |
| 43 | + \texttt{map2}\ & : \\ |
| 44 | + & \quad \texttt{op}: \texttt{AtLeastOne}\langle T_1, T_2\rangle \to \texttt{Option}\langle T_3\rangle \\ |
| 45 | + & \to \texttt{m}_1: \texttt{Matrix}\langle\texttt{Option}\langle T_1\rangle\rangle \\ |
| 46 | + & \to \texttt{m}_2: \texttt{Matrix}\langle\texttt{Option}\langle T_2\rangle\rangle \\ |
| 47 | + & \to \texttt{result}: \texttt{Matrix}\langle\texttt{Option}\langle T_3\rangle\rangle. |
| 48 | +\end{align*} |
| 49 | +Одна и та же функция $\texttt{map2}$, параметризованная разными операциями $\texttt{op}$, реализует и поэлементное сложение, и поэлементное умножение, и маскирование. |
| 50 | +Рассмотрим конкретные примеры. |
| 51 | + |
| 52 | +Операция поэлементного сложения для целых чисел определяется следующим образом: |
| 53 | +\begin{align*} |
| 54 | + \texttt{op\_int\_add}\ (\texttt{Both}\ (x, y)) &= \texttt{Some}\ (x + y), \\ |
| 55 | + \texttt{op\_int\_add}\ (\texttt{Left}\ x \mid \texttt{Right}\ y) &= \texttt{Some}\ x. |
| 56 | +\end{align*} |
| 57 | +(Если в результате сложения получился <<честный>> ноль, можно дополнительно вернуть \texttt{None}, чтобы не сохранять его.) |
| 58 | + |
| 59 | +Операция поэлементного умножения, напротив, возвращает непустое значение только тогда, когда оба аргумента непусты: |
| 60 | +\begin{align*} |
| 61 | + \texttt{op\_int\_mult}\ (\texttt{Both}\ (x, y)) &= \texttt{Some}\ (x * y), \\ |
| 62 | + \texttt{op\_int\_mult}\ (\texttt{Left}\ x \mid \texttt{Right}\ y) &= \texttt{None}. |
| 63 | +\end{align*} |
| 64 | + |
| 65 | +Наконец, маска (оставить значения первой матрицы только там, где во второй матрице есть ненулевые значения) также является частным случаем $\texttt{map2}$: |
| 66 | +\begin{align*} |
| 67 | + \texttt{op\_mask}\ (\texttt{Both}\ (x, y)) &= \texttt{Some}\ x, \\ |
| 68 | + \texttt{op\_mask}\ (\texttt{Left}\ x \mid \texttt{Right}\ y) &= \texttt{None}. |
| 69 | +\end{align*} |
| 70 | + |
| 71 | +Идея параметризации операции может быть распространена и на матричное умножение. |
| 72 | +Напомним, что в разделе~\ref{chpt:LinearAlgebraIntro} мы определили матричное умножение над полукольцом $(S, \oplus, \otimes)$ как |
| 73 | +\[ |
| 74 | + P[i,j] = \bigoplus_{l} M[i,l] \otimes N[l,j], |
| 75 | +\] |
| 76 | +где $\oplus$~--- операция <<сложения>> (агрегации) частичных результатов, а $\otimes$~--- операция <<умножения>> ячеек. |
| 77 | + |
| 78 | +В терминах типов этому соответствует функция $\texttt{mxm}$: |
| 79 | +\begin{align*} |
| 80 | + \texttt{mxm}\ & : \\ |
| 81 | + & \quad \texttt{m}_1: \texttt{Matrix}\langle\texttt{Option}\langle T_1\rangle\rangle \\ |
| 82 | + & \to \texttt{m}_2: \texttt{Matrix}\langle\texttt{Option}\langle T_2\rangle\rangle \\ |
| 83 | + & \to \texttt{op\_mult}: \texttt{Option}\langle T_1\rangle \to \texttt{Option}\langle T_2\rangle \to \texttt{Option}\langle T_3\rangle \\ |
| 84 | + & \to \texttt{op\_add}: \texttt{Option}\langle T_3\rangle \to \texttt{Option}\langle T_3\rangle \to \texttt{Option}\langle T_3\rangle \\ |
| 85 | + & \to \texttt{zero}: \texttt{Option}\langle T_3\rangle \\ |
| 86 | + & \to \texttt{result}: \texttt{Matrix}\langle\texttt{Option}\langle T_3\rangle\rangle. |
| 87 | +\end{align*} |
| 88 | + |
| 89 | +Здесь $\texttt{op\_mult}$ задаёт, как перемножить ячейку первой матрицы с ячейкой второй, $\texttt{op\_add}$~--- как сложить несколько частичных произведений для одной позиции результата, а $\texttt{zero}$~--- нейтральный элемент $\texttt{op\_add}$. |
| 90 | +Преимущество такого подхода перед классическим определением через полукольцо в том, что $\texttt{op\_mult}$ и $\texttt{op\_add}$ не обязаны удовлетворять аксиомам ассоциативности, коммутативности или дистрибутивности. |
| 91 | +На практике при решении задач анализа графов используемые операции часто не образуют полукольцо в математическом смысле, но это не мешает им быть полезными: достаточно, чтобы они были согласованы с конкретной решаемой задачей. |
| 92 | +Так, полукольцом может называться структура с доменами разных типов для аргументов умножения, что формально не является полукольцом в алгебраическом смысле, однако такая <<вольность>> оправдана практическими потребностями. |
| 93 | + |
| 94 | +Именно такой подход реализован в стандарте GraphBLAS~\sidecite{7761646}~--- открытом стандарте, описывающем набор примитивов и операций разреженной линейной алгебры, предназначенных для построения алгоритмов анализа графов. |
| 95 | +Он вводит абстракции моноидов и полуколец, объекты (скаляры, векторы, матрицы) и операции над ними (поэлементные, матричное умножение, маскирование, редукция). |
| 96 | +Стандарт позволяет параметризовать операции пользовательскими полукольцами и тем самым единообразно выражать широкий класс графовых алгоритмов~--- от поиска кратчайших путей (тропическое полукольцо) до вычисления достижимости (булево полукольцо). |
| 97 | + |
| 98 | +На практике этот стандарт реализуется в нескольких библиотеках: наиболее известная из них~--- SuiteSparse:GraphBLAS~\sidecite{Davis2018Algorithm9S}, широко используемая в высокопроизводительных графовых вычислениях. |
| 99 | +Активно развиваются и GPGPU-реализации, такие как GraphBLAST. |
| 100 | + |
| 101 | +Независимо от конкретной реализации, главное преимущество GraphBLAS-подхода для программиста~--- возможность выражать графовые алгоритмы через композицию матричных операций, делегируя заботу о разреженности, параллелизме и низкоуровневых оптимизациях библиотеке. |
| 102 | +Операции $\texttt{map2}$ и $\texttt{mxm}$, параметризованные пользовательскими функциями, являются естественным обобщением этого подхода: они позволяют отказаться от жёсткого требования задавать полукольцо и вместо этого указывать ровно те операции, которые нужны для конкретной задачи, с явными гарантиями корректности на уровне типов~\sidecite{7761646}. |
0 commit comments