diff --git a/doc/PageRanks-Example.jpg b/doc/PageRanks-Example.jpg new file mode 100644 index 0000000..309617c Binary files /dev/null and b/doc/PageRanks-Example.jpg differ diff --git a/doc/inp_n7.png b/doc/inp_n7.png new file mode 100644 index 0000000..840e942 Binary files /dev/null and b/doc/inp_n7.png differ diff --git a/doc/rapport.tex b/doc/rapport.tex new file mode 100644 index 0000000..6ebd71a --- /dev/null +++ b/doc/rapport.tex @@ -0,0 +1,255 @@ +\documentclass[a4paper]{article} +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{graphicx} +\usepackage{amsmath} +\usepackage{amsfonts} +\usepackage{amssymb} +\usepackage{color} +\usepackage[french]{babel} +\usepackage{hyperref} + +\usepackage{minted} +%\usemintedstyle{borland} +\newminted{ada}{linenos, numbersep=6pt, frame=leftline, xleftmargin=6mm, firstnumber=last} +\newminted{bash}{numbersep=6pt, frame=leftline, xleftmargin=6mm} + +\usepackage[ + top=1.5cm, + bottom=1.5cm, + left=1.5cm, + right=1.5cm +]{geometry} + +\begin{document} + +\begin{figure}[t] + \centering + \includegraphics[width=5cm]{inp_n7.png} +\end{figure} + +\title{\vspace{4cm} \textbf{Rapport de projet de programmation impérative} \\ Implémentation d'un algorithme de pageRank \vspace{1cm}} +\author{Maxime Dubaux \\ Laurent Fainsin} +\date{\vspace{7cm} Département Sciences du Numérique - Première année \\ +2020 - 2021 } + +\maketitle + +\newpage +\tableofcontents + +\newpage + +\section{Introduction} + +Lors de ce projet nous avons implémenté un algorithme permettant de caculer le PageRank pour un réseau donné. +Nous l'avons construit à partir de la methode des raffinages. De même, nous utilisons le langage de programmation compilé Ada. + +\section{Résumé du sujet} + +Le PageRank est un algorithme d'analyse des liens entre des pages Web dans un réseau, il mesure la popularité des pages dans celui-ci. En outre il trie les pages internet de la plus populaire à la moins populaire selon des principes simples : + +\begin{itemize} + \item Une page est respectable si d'autre page la reference, et d'autant plus si elle meme sont populaire. + \item Néanmois il faut ajuster la popularité selon la page qui reference, plus une page possède de lien vers d'autres sites, moins ses référencements ont de valeur. +\end{itemize} + +\begin{figure}[ht!] + \centering + \includegraphics[width=10cm]{PageRanks-Example.jpg} + \caption{Exemple d'un réseau et de ses poids calculés via le pageRank \cite{wiki_pageRank}. \label{fig : ex_pageRank}} +\end{figure} + +À l'aide du théoreme de point fixe, il peut se résume en un calcul répété d'un produit vecteur-matrice. + +Toute la difficulté provient de la taille massive que peut avoir le réseau. Il faut donc choisir les bonnes de structures de données. Cet algorithme est notamment utilisé par le moteur de recherche Google qui estime son propre réseaux à une cinquantaine de milliards de pages. + +\section{Architecture du programme} + +Nous pouvons diviser notre programme en trois sous-parties. + +\subsection{Gestion des vecteurs} + +Ada est un langage fortement typé, comme nous manipulons ici plusieurs types de données, il est logique de créer un type "Vector" générique dont l'unique but sera de stocker des informations. Nous aurons aussi besoin à plusieurs moments lors du calcul du pagerank de trier, sommer, initialiser des Vectors, d'où le choix de placer cette structure de données dans son propre module. + +Lorsque nous trierons les données nous ferons appelle à l'algorithme QuickSort + +\subsection{Gestion des matrice de Google} + +TODO + +\subsection{Gestion du calcul du Pagerank} + +Cette dernière partie s'occupe de regrouper tous les éléments présents dans les deux modules cités précédemment pour ainsi calculer itérativement le pageRank du réseaux. Cette sous-partie gère de plus le traitement des arguments de la ligne de commande ainsi que la lecture et l'écriture des résultats dans des fichiers. + +\section{Structures de données} + +Nous avons besoin d'une structure pour stocker le poids de chaque pages (i.e. sa popularité), nous appellerons cette structure "pi". +De même, nous avons besoin d'une structure pour stocker le réseau "network" décrivant les liens entre chaque pages du réseau. +Enfin il nous faudra créer une structure de données adapté à notre utilisation pour stocker la matrice de Google, notée G. + +Nous connaissons à l'avance les tailles des différentes structures, car le réseau est connu. Il n'est donc pas nécéssaire de créer des types dynamiques (i.e. liste chainées, vecteurs à taille variable stockés dans le heap...), un simple stockage statique dans le stack suffit. + +Notons N le nombre de pages dans le réseau, et N\_Links le nombre de liens total dans le réseau. + +Ainsi pour stocker pi, nous choissons la structure d'un vecteur statique (array) de dimension 1xN. + +\begin{adacode} + +yeet + +\end{adacode} + +De même, pour stocker network nous choisissons la structure d'une matrice de dimension 2xN\_Links. + +Pour stocker G, la matrice de Google, nous avons 2 choix: +\begin{itemize} + \item Une Matrice naive + \item Une Matrice creuse (éventuellement compressée) +\end{itemize} + +\subsection{Implémentation Naive} +Le premier choix consiste à stocker la matrice très naivement, c'est-à-dire en stockant l'ensemble de ses valeurs dans une matrice de taille NxN. + +L'avantage principale de cette structure est que sa construction et sa manipulation (produit vecteur/matrice) est simple. +Pour la construire à partir du réseau nous assignons à chaque lien une valeur dans la matrice. De même celle-ci à l'avantage d'être robuste par défaut à la présence de doublons dans le réseau. + +L'inconvénient est que nous stockons beaucoup de valeurs inutiles (i.e. des éléments qui se répètent ou bien qui sont égals à 0). Ainsi puisque un produit vecteur/matrice est de compléxité N\^2, nous perdons beaucoup de temps à effectuer des opérations inutiles. + +\subsection{Implémentation Creuse} +Il faut alors nous orienter vers une autre solution + +Pour alléger la taille mémoire de la matrice G, nous pouvons la rendre creuse, c'est-à-dire de ne pas stocker les valeurs nulles de celle-ci. + +Cependant nous pouvons aller encore plus loin en compressant G via un algorithme de compression simple appelé CSR \cite{wiki_CSR}. + +De cette manière nous ne gardons que les informations qui sont essentielles. + +L'inconvénient de cette méthode est que la création de ce type de matrice et son utilisation est plus compliqué que pour une matrice naive. + +Mais l'avantage de cette structure de donnée est son gain d'espace non négligeable ainsi que la rapidité qu'elle propose. En effet, nous n'effectuons plus que N\_Links opérations lors du produit vecteur/matrice, au lieu de N\^2 pour la version naive. De même nous, nous stockons uniquement des entiers dans G, au lieu de nombres flottants pour la version naive. + +\section{Benchmark} + +Voici l'ensemble des tests réalisés avec la commande time et valgrind sur l'ordinateur c202-02 de l'ENSEEIHT. + +La commande suivante a été éxécutée pour élargir la taille maximale du stack: +\begin{bashcode} +$ ulimit -s unlimited +\end{bashcode} + +Valgrind génère une sortie similaire à celle-ci pour l'ensemble des programmes: +\begin{bashcode} +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 22 allocs, 22 frees, 27,308 bytes allocated + +All heap blocks were freed -- no leaks are possible + +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) +\end{bashcode} + +Comme le heap n'est pas utilisé par le programme (mis à part pour quelques variables dont nous n'avons pas le controle direct), sachant le PID du programme, nous pouvons inspecter le fichier suivant pour connaitre la taille qu'occupe le programme dans le stack: + +\begin{bashcode} +$ grep -A 1 stack /proc//smaps +\end{bashcode} + +\subsection{Version Naive} + +\subsubsection{exemple\_sujet.net} +\begin{bashcode} +$ time build/pagerank fichiers_test/Exemple_sujet/exemple_sujet.net -n +real 0m0,009s +user 0m0,000s +sys 0m0,006s +\end{bashcode} + +\subsubsection{worm.net} +\begin{bashcode} +$ time build/pagerank fichiers_test/Worm/worm.net -n +real 0m0,073s +user 0m0,061s +sys 0m0,009s +\end{bashcode} + +\subsubsection{brainlinks.net} +\begin{bashcode} +$ time build/pagerank fichiers_test/Brainlinks/brainlinks.net -n +real 2m41,387s +user 2m41,074s +sys 0m0,280s +\end{bashcode} + +\subsection{Version Creuse} + +\subsubsection{exemple\_sujet.net} +\begin{bashcode} +$ time build/pagerank fichiers_test/Exemple_sujet/exemple_sujet.net +real 0m0,168s +user 0m0,001s +sys 0m0,009s +\end{bashcode} + +\subsubsection{worm.net} +\begin{bashcode} +$ time build/pagerank fichiers_test/Worm/worm.net +real 0m0,034s +user 0m0,016s +sys 0m0,004s +\end{bashcode} + + +\subsubsection{brainlinks.net} +\begin{bashcode} +$ time build/pagerank fichiers_test/Brainlinks/brainlinks.net +real 1m55,939s +user 1m55,909s +sys 0m0,012s +\end{bashcode} + + +\subsubsection{Linux26.net} +\begin{bashcode} +$ time build/pagerank fichiers_test/Linux26/Linux26.net +real 437m38,234s +user 437m34,783s +sys 0m0,440s +\end{bashcode} + +\section{Conclusion} + +On remaque facilement la supériorité temporelle et spatiale de la version creuse contre la version naive, surtout lorsque N et N\_links sont grands. + +\subsection{Améliorations encore possible} + +Lors de l'implémentation de la matrice compressé, nous avons choisis de compressé les lignes puisque celle-ci peuvent parfois être entièrement vide, cela est bénéfique d'un point de vu espace. Cepedant ils serait aussi intéressant de compresser G selon les colonnes puisque, bien que l'on perde en espace mémoire, on gagnerait en efficacité temporelle grâce au nombre réduit d'accès mémoire que l'on effecturait par rapport à la compression par ligne. + +Il est important de noter qu'un compression par colonne permettrait aussi de paralléliser la problème, le rendant encore plus efficace temporellement. Il est assez simple d'implémenter la parallélisation en Ada puisque cette notion fait partie à part entière du langage de programmation, mais nous ne l'avons pas implémenté par manque de temps. + +\section{Apports personnels} + +Ce projet nous a permis de solidifer nos connaissances sur les structures de données, car nous avons longuement réfléchi sur lequelles étaient les plus adaptées au problème. De même nous avons eu l'occasion de revoir plusieurs algorithme classique, nous permettant de mieux les maitriser. + +\newpage + +\begin{thebibliography}{9} + \bibitem{wiki_pageRank} + Wikipedia, PageRank \\ + \href{https://en.wikipedia.org/wiki/PageRank}{https://en.wikipedia.org/wiki/PageRank} + + \bibitem{wiki_CSR} + Wikipedia, Sparse matrix \\ + \href{https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format)}{https://en.wikipedia.org/wiki/Sparse\_matrix\#Compressed\_sparse\_row\_(CSR,\_CRS\_or\_Yale\_format)} + + \bibitem{wiki_ada} + Wikibooks, Ada Programming \\ + \href{https://en.wikibooks.org/wiki/Ada_Programming}{https://en.wikibooks.org/wiki/Ada\_Programming} + + \bibitem{rosetta_code} + Rosetta Code, Ada \\ + \href{https://rosettacode.org/wiki/Category:Ada}{https://rosettacode.org/wiki/Category:Ada} +\end{thebibliography} + +\end{document} \ No newline at end of file diff --git a/src/google_creux.ads b/src/google_creux.ads index a341f47..b756c2b 100644 --- a/src/google_creux.ads +++ b/src/google_creux.ads @@ -24,8 +24,9 @@ package Google_Creux is rows: T_Rows; end record; - procedure create_H(mat: in out T_Google; network: in Vector_Link.T_Vecteur); - function calcul(vec: in Vector_Element.T_Vecteur; mat: in T_Google; alpha: in T_Element) return Vector_Element.T_Vecteur; + procedure create_H(mat: in out T_Google; network: in Vector_Link.T_Vecteur); + function calcul(vec: in Vector_Element.T_Vecteur; mat: in T_Google; alpha: in T_Element) return Vector_Element.T_Vecteur with + Pre => alpha > 0.0 and alpha < 1.0; procedure put(mat: in T_Google); diff --git a/src/google_naive.ads b/src/google_naive.ads index 9960e61..f011275 100644 --- a/src/google_naive.ads +++ b/src/google_naive.ads @@ -30,7 +30,8 @@ package Google_Naive is procedure create_H(mat: in out T_Google; network: in Vector_Link.T_Vecteur); procedure create_S(mat: in out T_Google); - procedure create_G(mat: in out T_Google; alpha: in T_Element); + procedure create_G(mat: in out T_Google; alpha: in T_Element) with + Pre => alpha > 0.0 and alpha < 1.0; procedure put(mat: in T_Google); diff --git a/src/pagerank.adb b/src/pagerank.adb index c6f20f5..508e5d3 100644 --- a/src/pagerank.adb +++ b/src/pagerank.adb @@ -119,7 +119,7 @@ procedure pageRank is elsif Argument(i) = "-i" or Argument(i) = "--ite-max" then raise ERROR_ite; else - put_line("Unexpected contrain_error"); + put_line("Unexpected constraint_error"); raise ERROR_args; end if; @@ -178,46 +178,11 @@ begin use Vector_Double; use Vector_Entier; use Vector_Link; - - - - network: Vector_Link.T_Vecteur; - row, col: Natural; - - sorted: Boolean; - dupe: Natural; pi: Vector_Double.T_Vecteur; pi_index: Vector_Entier.T_Vecteur; begin - new_line; - -- on charge le réseau en mémoire - for i in 0..N_links-1 loop - get(file, row); - get(file, col); - network(i) := T_Link'(row, col); - end loop; - close(file); - put_line("loaded in memory the network"); - - -- on trie le réseau, si besoin - is_sorted_and_uniq(network, dupe, sorted); - if not naif then - if not sorted then - put_line("network is not sorted"); - quicksort(network); - put_line("sorted the network"); - end if; - if dupe > 0 then - put_line("deleted the duplicates ####### TODO #######"); - end if; - end if; - - -- on compte le nombre de doublons - -- count_dupe(network, dupe); - -- TODO - new_line; initialize(pi, 1.0/T_Double(N)); @@ -250,7 +215,7 @@ begin new_line; - create_H(G, network); + create_H(G, create_network(file, N_links, naif)); put_line("created H"); -- put(G); new_line; @@ -291,7 +256,7 @@ begin H: T_Google; begin - create_H(H, network); + create_H(H, create_network(file, N_Links, naif)); put_line("created H"); -- put(H); new_line; new_line; diff --git a/src/vector.adb b/src/vector.adb index fb82911..0d64b72 100644 --- a/src/vector.adb +++ b/src/vector.adb @@ -230,13 +230,41 @@ package body Vector is package body Link is - procedure initialize(vec: in out T_Vecteur; value: in Natural) is + function create_network(file: in out Ada.Text_IO.File_Type; N_links: in Positive; naif: in Boolean) return T_Vecteur is + + network: T_Vecteur; + row, col: Natural; + + sorted: Boolean; + dupe: Natural; + begin - for i in 0..Capacite-1 loop - vec(i).from := value; - vec(i).to := value; + + -- on charge le réseau en mémoire + for i in 0..N_links-1 loop + get(file, row); + get(file, col); + network(i) := T_Link'(row, col); end loop; - end initialize; + close(file); + put_line("loaded in memory the network"); + + -- on trie le réseau, si besoin + is_sorted_and_uniq(network, dupe, sorted); + if not naif then + if not sorted then + put_line("network is not sorted"); + quicksort(network); + put_line("sorted the network"); + end if; + if dupe > 0 then + put_line("deleted the duplicates ####### TODO #######"); + end if; + end if; + + return network; + + end create_network; procedure put(vec: in T_Vecteur) is begin @@ -272,7 +300,7 @@ package body Vector is end "<"; procedure quicksort(vec: in out T_Vecteur; low: Natural := 0; high: Natural := Capacite-1) is - + procedure swap(left, right: Natural) is tmp : constant T_Link := T_Link'(vec(left).from, vec(left).to); begin diff --git a/src/vector.ads b/src/vector.ads index 15de0ac..c7ff58a 100644 --- a/src/vector.ads +++ b/src/vector.ads @@ -67,8 +67,8 @@ package Vector is type T_Vecteur is array (0..Capacite-1) of T_Link; - procedure initialize(vec: in out T_Vecteur; value: in Natural); - + function create_network(file: in out Ada.Text_IO.File_Type; N_links: in Positive; naif: in Boolean) return T_Vecteur; + procedure put(vec: in T_Vecteur); procedure is_sorted_and_uniq(vec: in T_Vecteur; dupe: out Natural; sorted: out Boolean);