diff --git a/Figures/Graph.svg b/Figures/Graph.svg new file mode 100644 index 0000000..2c5706f --- /dev/null +++ b/Figures/Graph.svg @@ -0,0 +1,1217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id: 3 + Gender: MAdge: 42 + + + + + id: 1 + Gender: FAdge: 25 + + + + id: 2 + Gender: FAdge: 40 + + + + id: 9 + Gender: MAdge: 35 + + + + id: 0 + Gender: FAdge: 52 + + + + id: 10 + System: MIRLimit: 99 000 000 + + + + id: 11 + System: MIRLimit: 99 000 000 + + + + id: 5 + System: MIRLimit: 70 000 + + + + id: 6 + System: VISALimit: 80 000 + + + + id: 7 + System: MASTERCARDLimit: 90 000 + + + + + id: 8 + System: VISALimit: 10 000 000 + + + id: 4 + System: MIRLimit: 60 000 + + + + + + + + + + + + + + + + + + + + + + Sum: 23412Count: 6 + Sum: 13214.1Count: 5 + Sum: 19999.1Count: 6 + Sum: 69999.1Count: 16 + Sum: 8999.1Count: 7 + Sum: 59999.1Count: 12 + Sum: 99999.1Count: 5 + Sum: 79999.1Count: 15 + Sum: 999999.1Count: 9 + Sum: 49999.1Count: 12 + Sum: 92223Count: 9 + Sum: 81312Count: 7 + Sum: 16325.99Count: 5 + Sum: 62412Count: 9 + + + diff --git a/README.md b/README.md index 1b503b6..3bb2051 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,311 @@ # PageRankBenchmark -Stand for PageRank algorithm benchmark +Демонстрация использования алгоритма "PageRank" на графе со сложными атрибутами вершин и ребёр. +# Руководство к запуску +## Установка зависимостей +```bash +sudo apt install ccache +sudo apt install ninja-build +``` + +## Сборка проекта +```bash +git clone https://github.com/SparseLinearAlgebra/PageRankBenchmark.git +cd PageRankBenchmark +git submodule init +git submodule update —recursive +make build +``` + +## Запуск примера +``` +./build/main +``` +# Минимальный пример +Полный код разбираемого примера можно увидеть в [файле](./src/main.c). + +Цель примера --- показать, как в рамках GraphBLAS можно работать со сложными атрибутами вершин и рёбер графа. + +В качестве примера возьмём следующую задачу. +Пусть есть граф с двумя типами вершин: пользователи и карты. +Также есть два типа ориентированных рёбер: +* "Перевод": соединяет две карты (откуда и куда перевод) +* "Владеет": соединяет пользователя и карту (от владельца карты к карте) + +Каждый тип вершины имеет свой набор атрибутов. +* "Пользователь": пол, возраст +* "Карта": тип, лимит средств + +Пусть будут следующие типы карт: МИР, VISA, MASTERCARD. + +При этом рёбра типа "Перевод" в качестве атрибутов содержат общую сумму и "количество транзакций". +Рёбра типа "Владеет" не имеют атрибутов. + +Для примера возьмём следующий граф. +![Пример графа](./Figures/Graph.svg) +У каждой вершины есть уникальный идентификатор, синие рёбра отображают переводы, красные --- отношение владения, розовые вершины --- пользователи, синие --- карты. + +Хотим выбрать по некоторому критерию пользователей и их карты, а затем для анализа переводов хотим посчитать PageRank на подграфе, заданном переводами между отобранными картами. +Выбрать хотим все карты системы "МИР", которыми владеют люди старше заданного возраста. Покажем, как это можно сделать, используя матрично-векторные операции, в частности [GraphBLAS](https://github.com/GraphBLAS). + +GraphBLAS позволяет в качестве атрибутов использовать пользовательские типы (фиксированных размеров), потому объявим необходимый нам набор типов. +```c +// Тип карты +typedef enum +{ + VISA, + MIR, + MASTERCARD, +} System; + +// Пол +typedef enum +{ + MALE, + FEMALE, +} Gender; + +// Данные о пользователе +typedef struct +{ + Gender gender; + uint8_t age; +} User; + +// Данные о карте +typedef struct +{ + System system; + double limit; +} Card; + +// Данные о переводах +typedef struct +{ + double sum; + uint32_t count; +} EdgeTX; +``` + +Граф представлен как набор матриц и векторов: по одной матрице на каждый тип рёбер и по одному вектору на каждый тип вершин. +Матрицы и вектора в большинстве случаев будут разреженными и мы будем использовать символ '$.$' для обозначения отсутствующего элемента. +Считаем при этом, что все вершины, вне зависимости от типа, занумерованы с 0 подряд (id вершин на рисунке). +Таким образом, нам понадобятся две матрицы: + +$$ +\texttt{TX-Edges}= +\begin{pmatrix} +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & \\{23 \ 412; 6\\} & . & . & \\{13 \ 214.1; 5\\} & . & \\{99 \ 999.1; 5\\} & . \\ +. & . & . & . & \\{13 \ 214.1; 5\\} & . & \\{81 \ 312; 7\\} & . & \\{92 \ 223; 9\\} & . & \\{19 \ 999.1; 6\\} & . \\ +. & . & . & . & . & . & . & . & . & . & . & \\{8 \ 999.1; 7\\} \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & \\{16 \ 325.99; 5\\} & . & . & . & . & . & . & \\{49 \ 999.1; 12\\} \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & \\{79 \ 999.1; 15\\} & \\{69 \ 999.1; 16\\} & . & . & . & . & . & . \\ +. & . & . & . & . & . & \\{59 \ 999.1; 12\\} & . & \\{999 \ 999.1; 9\\} & . & . & . \\ +\end{pmatrix} +$$ + +$$ +\texttt{Owns-Edges}= +\begin{pmatrix} +. & . & . & . & . & . & . & . & 1 & . & . & . \\ +. & . & . & . & . & . & 1 & 1 & . & . & . & . \\ +. & . & . & . & . & 1 & . & . & . & . & . & . \\ +. & . & . & . & 1 & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & 1 & 1 \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +\end{pmatrix} +$$ + +И два вектора: + +$$ +\texttt{Users} = +[\\{F; 52\\} \ ; \ \\{F; 25\\} \ ; \ \\{F; 40\\} \ ; \ \\{M; 42\\} \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ \\{M; 35\\} \ ; \ . \ ; \ . ] +$$ + +$$ +\texttt{Cards} = +[. \ ; \ . \ ; \ . \ ; \ . \ ; \ \\{\text{MIR}; 60 \ 000\\} \ ; \ \\{\text{MIR}; 70 \ 000\\} \ ; \ \\{\text{VISA}; 80 \ 000\\} \ ; \ \\{\text{MASTERCARD}; 90 \ 000\\} \ ; \ \\{\text{VISA}; 10 \ 000 \ 000\\} \ ; \ . \ ; \ \\{\text{MIR}; 99 \ 000 \ 000\\} \ ; \ \\{\text{MIR}; 99 \ 000 \ 000\\} ] +$$ + +Первым делом фильтруем пользователей по возрасту. +Скажем, нас будут интересовать пользователи старше 30 лет. +Для этого в GraphBLAS есть функция ```Select```, которая фильтрует коллекции, используя функцию-предикат принимаемую в качестве аргумента. + +Так как нам предстоит работать с пользовательскими типами, то придётся написать собственный предикат. + +```c +void check_user_age(bool *z, const User *x, GrB_Index _i, GrB_Index _j, const uint8_t *y) +{ + *z = (x->age > *y); +} +``` + +Два дополнительных параметра типа ```GrB_Index``` позволяют, при необходимости, использовать в фильтре координаты рассматриваемого элемента. + +Для того, чтобы выбрать карты, принадлежащие выбранным пользователям, нам необходимо получить "концы" рёбер типа Owns, исходящие из выбранных пользователей. +Чтобы сделать это, выполним один шаг обхода в ширину, который в терминах линейной алгебры выражается через умножение вектора текущих вершин на матрицу смежности. +Текущие вершины в нашем случае --- выбранные пользователи. +То есть нам необходимо вычислить следующее произведение. + +$$ +\texttt{Filtered-Cards} = +$$ +$$ +[\\{F; 52\\} \ ; \ . \ ; \ \\{F; 40\\} \ ; \ \\{M; 42\\} \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ . \ ; \ \\{M; 35\\} \ ; \ . \ ; \ . ] +\otimes +\begin{pmatrix} +. & . & . & . & . & . & . & . & 1 & . & . & . \\ +. & . & . & . & . & . & 1 & 1 & . & . & . & . \\ +. & . & . & . & . & 1 & . & . & . & . & . & . \\ +. & . & . & . & 1 & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & 1 & 1 \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +\end{pmatrix} = +$$ +$$ +[. \ ; \ . \ ; \ . \ ; \ . \ ; \ 1 \ ; \ 1 \ ; \ . \ ; \ . \ ; \ 1 \ ; \ . \ ; \ 1 \ ; \ 1] +$$ + +Здесь нам впервые потребуется переопределить поэлементные операции для $\otimes$ (в терминах GraphBLAS необходимо сконструировать пользовательское полукольцо). + +$$ +\+ \ : \ \textit{bool} \times \textit{bool} \to \textit{bool} +$$ +$$ +\* \ : \ \textit{User} \times \textit{bool} \to \textit{bool} +$$ + +В качестве конкретных реализаций для $+$ можно взять логическое "И", а в качестве $*$ операцию $\textit{second}$ (вернуть второй элемент из пары). + +Мы получили не совсем карты, но вектор, который указывает, какие карты нас интересуют. +Вспомним, что мы хотим взять только карты "МИР". +Для этого снова будем использовать Select, а полученный вектор $\texttt{Filtered-Cards}$ будем использовать как маску, чтобы дополнительно отфильтровать результат. + + +Чтобы получить переводы только между отобранными картами, воспользуемся тем фактом, что выбор исходящих рёбер, инцидентных заданному множеству вершин --- это умножение матрицы смежности на диагональную матрицу, в которой ненулевые элементы на местах интересующих нас вершин, слева. +Для входящих рёбер нужно аналогичное умножение справа. +То есть нам необходимо вычислить следующее произведение. + +То есть данном случае нам необходимо умножить на диагональную матрицу и слева, и справа (нам важно, чтобы все рёбра шли **только между** отобранными картами). + +$$ +\texttt{Filtered-Transactions} = +\begin{pmatrix} +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & 1 & . & . & . & . & . & . & . \\ +. & . & . & . & . & 1 & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & 1 & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & 1 & . \\ +. & . & . & . & . & . & . & . & . & . & . & 1 \\ +\end{pmatrix} +\otimes_1 +\begin{pmatrix} +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & \\{23 \ 412; 6\\} & . & . & \\{13 \ 214.1; 5\\} & . & \\{99 \ 999.1; 5\\} & . \\ +. & . & . & . & \\{13 \ 214.1; 5\\} & . & \\{81 \ 312; 7\\} & . & \\{92 \ 223; 9\\} & . & \\{19 \ 999.1; 6\\} & . \\ +. & . & . & . & . & . & . & . & . & . & . & \\{8 \ 999.1; 7\\} \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & \\{16 \ 325.99; 5\\} & . & . & . & . & . & . & \\{49 \ 999.1; 12\\} \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & \\{79 \ 999.1; 15\\} & \\{69 \ 999.1; 16\\} & . & . & . & . & . & . \\ +. & . & . & . & . & . & \\{59 \ 999.1; 12\\} & . & \\{999 \ 999.1; 9\\} & . & . & . \\ +\end{pmatrix} +\otimes_2 +\begin{pmatrix} +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & 1 & . & . & . & . & . & . & . \\ +. & . & . & . & . & 1 & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & 1 & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & 1 & . \\ +. & . & . & . & . & . & . & . & . & . & . & 1 \\ +\end{pmatrix} = +\begin{pmatrix} +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & \\{23 \ 412; 6\\} & . & . & \\{13 \ 214.1; 5\\} & . & \\{99 \ 999.1; 5\\} & . \\ +. & . & . & . & \\{13 \ 214.1; 5\\} & . & . & . & \\{92 \ 223; 9\\} & . & \\{19 \ 999.1; 6\\} & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & \\{16 \ 325.99; 5\\} & . & . & . & . & . & . & \\{49 \ 999.1; 12\\} \\ +. & . & . & . & . & . & . & . & . & . & . & . \\ +. & . & . & . & \\{79 \ 999.1; 15\\} & \\{69 \ 999.1; 16\\} & . & . & . & . & . & . \\ +. & . & . & . & . & . & . & . & \\{999 \ 999.1; 9\\} & . & . & . \\ +\end{pmatrix} +$$ + +Здесь операции $\otimes_1$ и $\otimes_2$ также требует задания специфичных поэлементных операций $+$ и $*$. +Например, для $\otimes_1$: + +$$ +\+ \ : \ \textit{bool} \times \textit{bool} \to \textit{bool} +$$ +$$ +\* \ : \ \textit{bool} \times \textit{EdgeTX} \to \textit{EdgeTX} +$$ + +В качестве конкретных реализаций для $+$ можно взять логическое "И", а в качестве $*$ операцию $\textit{second}$ (вернуть второй элемент из пары). + +Для $\otimes_2$ ситуация аналогичная, +Необходимо только проследить за тем, в какие моменты надо брать первый элемент из пары, а в какие второй, чтобы в результате получилась матрица с элементами типа $\texttt{EdgeTX}$. + +Подграф готов. +Теперь необходимо сконструировать матрицу, по которой непосредственно будем считать PageRank. +Сейчас метки рёбер --- структуры, хранящие информацию о переводах, а мы хотим получить одно число. +При этом важно, чтобы сумма весов всех исходящих рёбер была равна единице. +Для примера действовать будем следующим образом: возьмём "средний размер транзакции" (вычислим как $\frac{\textit{Sum}}{\textit{Count}}$), поделим на 1000 (на всякий случай, чтобы избежать слишком больших значений) и затем построчно применим Softmax. +Иными словами, будем использовать идею функции Softmax, которая задаётся следующим образом. + +$$ +{\displaystyle \sigma (\overrightarrow{z})_{i}={\frac {e^{z_{i}}}{\displaystyle \sum _{k\mathop {=} 1}^{K}e^{z_{k}}}}} +$$ + +В нашем случае подобная функция должна быть применена к каждой строке матрицы, задающей переводы. При этом должна быть определена функция $f$, которая получает вес ребра по его атрибутам (вычисляет $\frac{\textit{Sum}}{\textit{Count} * 1000}$). +То есть итоговая формула выглядит следующим образом. + +$$ +{\displaystyle \sigma (\overrightarrow{z})_{i}={\frac {e^{f(z_{i})}}{\displaystyle \sum _{k\mathop {=} 1}^{K}e^{f(z_{k})}}}} +$$ + +Вычисления построим следующим образом. Сперва выполним редукцию по колонкам с использованием функции $f$: таким образом получим знаменатель дроби. +После этого сконструируем две квадратные матрицы: в одной нулевой столбец --- это полученный вектор, а остальные нули, в другой --- нулевая строка --- единицы, остальное --- нули. +Перемножим эти две матрицы, использую исходную матрицу $\texttt{Filtered-Transactions}$ в качестве маски. +Таким образом получим матрицу, в которой знаменатель стоит на необходимых местах. +Также нам нужна будет матрица, содержащая числители дробей (получается поэлементным применением соответствующей функции к $\texttt{Filtered-Transactions}$) +После чего поэлементно поделим эти две матрицы. + +Далее на полученной матрице запускаем [классический алгоритм PageRank](./src/main.c#L190) (правда, без "телепортации" в несвязанные вершины), который в терминах линейной алгебры реализуется по определению: итеративное умножение исходной матрицы на вектор.