viewgit/index.php:465 Only variables should be passed by reference [2048]

viewgit/index.php:466 Non-static method GeSHi::get_language_name_from_extension() should not be called statically [2048]

  1. \chapter{Page Rank}
  2. In questo capitolo ci porremo il problema di calcolare il \emph{page rank} di un determinato
  3. insieme di pagine Web (o, analogamente, documenti \emph{ipertestuali}). \\
  4. Con Page Rank intendiamo un ``valutazione'' data ad una determinata pagina o documento con lo scopo
  5. di stimare la sua importanza\footnote{che è ovviamente un concetto poco matematico, ma ci occuperemo
  6. di formalizzarlo meglio in seguito.}. Un algoritmo derivato da quello che verrà in seguito
  7. esposto è utilizzato attualmente da Google (\href{http://www.google.it}{http://www.google.it})
  8. per ordinare i risultati di una query nel suo motore di ricerca, così come da tutti i principali
  9. \emph{search engine} presenti sul Web.
  10.  
  11. \section{Presentazione del modello}
  12. Prima di tutto dobbiamo occuparci di definire il modello matematico che vogliamo usare
  13. per schematizzare la nostra valutazione di importanza. \\
  14. In generale l'idea che sta alla base del Page Rank è dare importanza ad una pagina
  15. basandosi sui link\footnote{Un link in una pagina Web, ma anche in un documento PDF come questo,
  16. è un ``puntatore'' ad un altro documento. In genere l'utente può attivare il puntatore semplicemente
  17. cliccandoci sopra con il mouse} che in essa sono presenti.
  18.  
  19. Alcune idee che potrebbero sembrare valide potrebbero essere valutare l'importanza di una pagina
  20. in base al numero di link presenti nella pagina, oppure al numero di link che puntano ad essa.
  21.  
  22. Dopo una breve riflessione ci si rende conto che entrambi i metodi introducono svariate problematiche
  23. di affidabilità. In generale, infatti, il creatore di una pagina ha il controllo di quanti link
  24. sono presenti in una pagina, e non fatica molto a creare delle pagine fittizie che puntano a quella
  25. appena creata. La valutazione sarebbe quindi falsata e molto vulnerabile a frode.
  26.  
  27. Si può però cercare di affinare l'idea di base in modo da risolvere questi inconvenienti.
  28. Per convenzione assumiamo di numerare tutte le pagine presenti nel Web con dei numeri naturali
  29. $1 \ldots N$ (dove $N$ è un numero dell'ordine di $10^{10}$).
  30. Fissata una certa pagina $j$ consideriamo i link che puntano a lei. Risulta chiaro che non solo
  31. il \textbf{numero} dei link è importante per valutare la sua importanza, ma a sua volta anche l'\textbf{importanza}
  32. di questi dovrebbe influire. Per esempio, se la homepage di Yahoo! punta
  33. alla pagina di una data persona, è naturale che questa pagina acquisisca una certa rilevanza. Viceversa, se sulla
  34. mia pagina personale metto un link al sito della stessa persona l'importanza di questo varia di poco\footnote{
  35. a meno che nel momento in cui voi leggiate queste pagine io non sia diventato una persona con una certa influenza,
  36. ma questo è probabilmente un caso da scartare}.
  37. Un'ultima e non meno importante considerazione è che se anche Yahoo! punta al mio sito ma lo fa in una pagina
  38. con un altro centinaio di link è una faccenda molto diversa dall'essere puntati dall'unico link in homepage.
  39.  
  40. Stiamo quindi arrivando ad un altro modello, in cui le pagine \emph{distribuiscono la loro importanza fra i link al
  41. loro interno}. Questo modello è molto più ragionevole del precedente, ed è molto meno predisposto a dare
  42. risultati falsati. Vorremo però averne una descrizione matematica precisa.
  43.  
  44. \section{Un po' di matematica}
  45. Consideriamo la matrice quadrata $H = (h_{ij})$, che chiameremo \emph{matrice delle connessioni}, definita
  46. nel seguente modo
  47. \[
  48. h_{ij} = \left\{ \begin{array}{ll}
  49. 1 & \text{se esiste un link dalla pagina} \ i \ \text{alla pagina} \ j \\
  50. 0 & \text{altrimenti}
  51. \end{array} \right.
  52. \]
  53. Questa matrice avrà dimensione $N \times N$ ma sarà sparsa, ovvero la maggior parte dei suoi valori saranno $0$.
  54. Per fissare le idee consideriamo la Figura~\ref{fig:web}, un modello (molto) semplificato del Web, in cui esistono solamente
  55. 3 pagine.
  56. \begin{figure}[hb]
  57. \[
  58. \xymatrix{
  59. {\bullet{1}} \ar@/_/[dr] \ar@/^1pc/[drr] & \\
  60. & {\bullet{2}} \ar@/_/[ul]& {\bullet{3}} \ar@/^/[l]}
  61. \]
  62. \caption{Esempio di una rete composta da 3 pagine}
  63. \label{fig:web}
  64. \end{figure}
  65.  
  66. La matrice di connessione $H$ associata a questo grafo è la seguente
  67. \[
  68. H = \left[ \begin{array}{ccc}
  69. 0 & 1 & 1 \\
  70. 1 & 0 & 0 \\
  71. 0 & 1 & 0
  72. \end{array} \right]
  73. \]
  74. \begin{os} \label{os:pageranknumcoll}
  75. \`E facile verificare che valgono le seguenti uguaglianze
  76. \[
  77. \left\{ \begin{array}{ll}
  78. \sharp \{ \text{collegamenti uscenti da} \ i \} & = \sum_{j=1}^{N} h_{ij} \\
  79. \sharp \{ \text{collegamente che arrivano a} \ j \} & = \sum_{i=1}^{N} h_{ij}
  80. \end{array} \right.
  81. \]
  82. \end{os}
  83. In particolare sarebbe piuttosto immediato calcolare, partendo dalla matrice $H$, i valori
  84. del Page Rank con i metodi ipotizzati all'inizio del capitolo (per altro reputati incompleti)
  85.  
  86. Siamo dunque interessati a calcolare il vettore $W$ dove $w_i$ è l'importanza della pagina $i$,
  87. in modo che rispetti le linee guide definite precedentemente.
  88.  
  89. Chiamiamo $d_i$ il numero di link uscenti dalla pagina $i$
  90. (facilmente calcolabile come visto nell'Osservazione~\ref{os:pageranknumcoll}). Chiameremo inoltre
  91. $D$ la matrice con i $d_i$ sulla diagonale e nulla per i restanti valori.
  92. Introdotta questa notazione, secondo il nostro modello ogni componente di $W$ dovrebbe rispettare
  93. la seguente equazione
  94. \[
  95. w_j = \sum_{i=1}^{N} w_i \frac{h_{ij}}{d_i}
  96. \]
  97. che rappresenta esattamente l'idea che l'importanza di ogni pagina è la somma delle importanze di quelle
  98. che la linkano, in cui ogni addendo è normalizzato sul numero di link uscenti\footnote{probabilmente l'equazione
  99. è molto più chiara di questa frase, ma non commentare mi sarebbe sembrato inopportuno}.
  100. \`E quindi chiaro come il nostro problema si sia trasformato in un problema di autovalori ed autovettori. In
  101. particolare, se poniamo $M = D^{-1}H$ la nostra richiesta si può scrivere nella forma più compatta
  102. $\trasp{W} = \trasp{W}M$, ovvero $\trasp{W}$ è un autovettore sinistro di $M$. Sorgono spontanee alcune
  103. domande a cui dobbiamo rispondere prima di procedere oltre.
  104. \begin{enumerate}[(a)]
  105. \item \label{en:pagerank:a} Cosa succede se per qualche $i$ si ha $d_i = 0$? Stiamo tacitamente assumendo che $D$ sia invertibile
  106. ma questo non è garantito a priori;
  107. \item \label{en:pagerank:b} L'autovalore $1$ fa parte dello spettro delle matrice $M$? Se questo non fosse vero la nostra
  108. richiesta non ammette soluzione, e quindi possiamo rinunciare in partenza alla nostra idea;
  109. \item \label{en:pagerank:c} Se l'autovettore $W$ esiste, è unico\footnote{In realtà ci stiamo chiedendo se il suo
  110. autospazio ha dimensione $1$. Siamo ben consci che se ne troviamo uno ogni suo multiplo scalare continuerà
  111. ad essere un autovettore. Per altro, moltiplicare le valutazioni omogeneamente per lo stesso cofficiente non
  112. sarebbe in nessun modo un problema}?. Se così non fosse
  113. sorgerebbero dei seri problemi su come interpretare i risultati ottenuti;
  114. \item \label{en:pagerank:d} Possiamo scegliere l'autovettore $W$ in modo che $w_i \geq 0$ per ogni $i$? Questo non è necessario,
  115. a priori, ma sembrerebbe coerente fare in modo che delle votazioni siano numeri positivi;
  116. \end{enumerate}
  117.  
  118. Cominciamo col rispondere alla domanda~(\ref{en:pagerank:a}). Ovviamente potrebbe benissimo succedere
  119. che per qualche $i$ $d_i = 0$. Questi ``nodi'' vengono chiamati \emph{dangling node}, ovvero ``nodi penzolanti'',
  120. che non portano a niente. Per risolvere questo problema si sostituisce la matrice $H$ con una matrice $\hat H$
  121. tale che se $h_{ij} = 0 \: \forall j$ allora $\hat h_{ij} = 1 \: \forall j$, e in caso contrario $\hat h_{ij} = h_{ij}$.
  122. Questa matrice risolve evidentemente il problema e cambia di poco il modello. Supporre che una determinata pagina
  123. non distribuisca la sua importanza a nessuno o la distribuisca in maniera analoga a tutti non è un gran cambiamento.
  124.  
  125. La domanda~(\ref{en:pagerank:b}) ha invece una risposta immediata. Sì, l'autovalore $1$ fa parte dello spettro
  126. ed un autovettore (destro) a lui associato è quello composto di tutti $1$. La verifica è un banale conto.
  127.  
  128. Per rispondere alle domande~(\ref{en:pagerank:c}) e (\ref{en:pagerank:d}) ci appoggiamo ad un risultato
  129. che non dimostreremo (in questa sede) ottenuto da Perron-Frobenius sulla Teoria delle Matrici non negative.
  130. \begin{te}[di Perron-Frobenius] \label{te:PerronFrobenius}
  131. Se $A$ è irriducibile allora $\rs{A} > 0$ è un autovalore semplice ed
  132. esistono $x,y$ autovettori destro e sinistro
  133. con componenti strettamente positive.
  134. \end{te}
  135. \begin{os}
  136. Sembrerebbe, a priori, che non siamo in grado di determinare il raggio spettrale di $A$. Consideriamo però
  137. che $1$ è nello spettro e che $||A||_{\infty} = 1$ e ogni norma indotta supera il raggio spettrale. Si può
  138. quindi concludere che $\rs{A} = 1$ e applicare il Teorema~\ref{te:PerronFrobenius}.
  139. \end{os}
  140. Sfortunatamente si può osservare che, pur con tutte queste buone proprietà, in generale i metodi applicati
  141. per trovare l'autovettore (il metodo delle potenze) potrebbe non essere convergente, perché gli autovalori
  142. diversi da $1$ potrebbero ancora avere modulo $1$\footnote{Come visto nella teoria, la velocità di convergenza
  143. del metodo di iterazione è data dal rapporto fra il più grande autovalore diverso da quello di cui si sta cercando
  144. l'autovettore e l'autovalore in questione. In questo caso il rapporto potrebbe essere $1$, che darebbe una situazione
  145. di non convergenza}.
  146. Introduciamo una definizione che ci permetterà di formalizzare meglio il problema.
  147. \begin{de}
  148. Una matrice $A$ si dice \emph{ciclica} se esiste un sottoinsieme dei suoi autovalori $J \subseteq \spe{A}$
  149. tale che $\forall \lambda,\mu \in J$ si ha che $|\lambda|=|\mu|$.
  150. \end{de}
  151. Per superare questa difficoltà si può, ancora una volta, modificare il modello. Fissiamo $\gamma > 0$ e consideriamo
  152. che il visitatore
  153. possa in ogni momento, con probabilità $1-\gamma$, decidere di aprire una pagina a caso. Potremmo rappresentare
  154. questa nuova situazione sostituendo $M$ con la matrice
  155. \[
  156. A = \gamma M + (1-\gamma) \left[ \begin{array}{c}
  157. 1 \\
  158. \vdots \\
  159. 1
  160. \end{array} \right] \left[ 1 \: \hdots \: 1 \right]
  161. \]
  162. ovvero con una combinazione convessa della matrice $M$ con la matrice di soli $1$. Il valore di $\gamma$ si può
  163. scegliere a piacimento; Google usa un valore $\gamma = 0.85$. In questo caso la matrice $A$ non ha elementi nulli
  164. e quindi è irriducibile e non è ciclica\footnote{La condizione di ciclicità è quella che crea problemi
  165. con gli autovalori di modulo uguale al raggio spettrale}. Possiamo infine utilizzare un'altra versione del Teorema~\ref{te:PerronFrobenius}
  166. che dice
  167. \begin{te}
  168. Sia $A$ una matrice con elementi strettamente positivi. Allora per ogni suo autovalore $\lambda \neq \rs{A}$ si ha
  169. che vale la diseguaglianza $|\lambda| < |\rs{A}|$.
  170. \end{te}
  171. Quest'ultimo ci assicura la convergenza del nostro metodo.
  172.  
  173. \subsection{Implementazione del metodo delle potenze}
  174. Presenteremo in questa sezione una versione del metodo delle potenze adattata alle nostre esigenze.
  175. Consideriamo la matrice $A$ di cui vogliamo trovare l'autovettore sinistro relativo all'autovalore $1$.
  176. Sappiamo che esiste la forma di Jordan di $A$ ed in particolare una matrice $S$ tale che
  177. \[
  178. S^{-1} A S = J \qquad J = \left[ \begin{array}{c|cc}
  179. 1 & \multicolumn{2}{|c}{\herm{0}} \\ \hline
  180. \multirow{2}{*}{$0$} & \sblocke{\hat J}{2} \\
  181. & &
  182. \end{array} \right]
  183. \]
  184. Osserviamo ora che $e_1$ è autovettore destro di $J$ ed anche autovettore sinistro. Entrambi sono relativi
  185. all'autovalore $1$. Sappiamo quindi che $e = Se_1$ è autovalore destro di $A$ e quindi è il vettore
  186. con tutte le componenti uguali ad $1$, mentre $\trasp{w} = \trasp{e_1}S^{-1}$ è autovettore sinistro di $A$,
  187. ovvero il vettore che stiamo cercando.
  188. In particolare quanto visto nella precedente sezione ci assicura che $\rs{\hat J} < 1$ e quindi possiamo osservare
  189. che
  190. \[
  191. \lim_{k\to\infty} J^{k} = e_1\trasp{e_1} \quad \text{e quindi} \quad \lim_{k\to\infty} A^{k} =
  192. \lim_{k\to\infty} SJ^kS^{-1} = Se_1 \trasp{e_1}S^{-1} = e\trasp{w}
  193. \]
  194. Questo (apparentemente) risolve il nostro problema in quanto $e\trasp{w}$ non è altro che la matrice le cui
  195. righe coincidono con il vettore $\trasp{w}$ che è precisamente quanto stiamo cercando. \\
  196. La complessità di questo calcolo non è però trascurabile, in quanto la moltiplicazione di una matrice
  197. costa in generale $O(n^3)$, decisamente troppo se $n \approx 10^{10}$ come nel nostro caso.
  198. \begin{os}
  199. Consideriamo dunque un vettore $x_0$ tale che $\trasp{e}x_0 = 1 = ||x_0||_{1}$, e definiamo la successione per ricorrenza
  200. \[
  201. \left\{ \begin{array}{ll}
  202. \trasp{x_0} & = \trasp{x_0} \\
  203. \trasp{x_{k+1}} & = \trasp{x_{k}} A
  204. \end{array} \right.
  205. \]
  206. Possiamo notare che in generale $x_k = x_0 A^{k}$ e quindi data la nostra scelta oculata di $x_0$ abbiamo
  207. che
  208. \[
  209. \lim_{k\to\infty} \trasp{x_k} = \lim_{k\to\infty} \trasp{x_0} A^{k} = x_0 e \trasp{w} = \trasp{w}
  210. \]
  211. In questo modo siamo scesi da una complessità $O(n^3)$ ad una $O(n^2)$.
  212. \end{os}
  213. Sfortunatamente $O(n^2)$ nel nostro caso continua ad essere un numero troppo grande; arrivati a questo
  214. punto ci accorgiamo che possiamo sfruttare la particolarità di $A$, che non è assolutamente
  215. una matrice qualsiasi! Sappiamo infatti che è composta quasi esclusivamente di zeri, e quindi una
  216. memorizzazione efficiente ci permetterebbe anche un'implementazione efficiente dei procedimenti
  217. di moltiplicazione.
  218. %% TODO: QUalcosa di più sull'implementazione del metodo
  219.  
  220. \subsection{Note tecniche}
  221. Analizzato il problema matematicamente, sorge il bisogno di analizzare le difficoltà di implementazione dovute
  222. all'hardware attualmente disponibile. Una matrice delle dimensioni di $A$ non è realmente memorizzabile
  223. elemento per elemento su nessun calcolatore, e non è pensabile nemmeno in alcun tipo di cluster. \\
  224. Un rapido conto mostra come, anche ipotizzando di usare dei tipi \verb-float- a 4 byte (in ogni
  225. caso presumibilmente troppo imprecisi) otteniamo uno spazio necessario di $N^2 \cdot 4$ byte, ovvero
  226. circa $10^{20}$ byte = $10^{11}$ GB di RAM, probabilmente un quantitativo superiore a quella di tutti i computer
  227. del mondo messi assieme\footnote{può darsi che nei prossimi anni quest'affermazione smetta di essere vera, ma
  228. non penso sia rilevante ai fini del problema}.
  229. Dobbiamo quindi adottare degli stratagemmi per gestire la memoria in modo efficiente.
  230.  
  231. Una prima osservazione è che la nostra matrice $A$ si può scrivere nel seguente modo
  232. \[
  233. A = \gamma D^{-1} \hat H + (1-\gamma) e\trasp{v}
  234. \]
  235. dove $\gamma$ e $w$ sono rispettivamente il parametro ed il vettore di personalizzazione. Per memorizzare
  236. $A$ possiamo quindi memorizzare $D$ ed $\hat H$, $\gamma$ e $w$. \\
  237. Questo ci dà dei vantaggi perché la matrice $\hat H$ è ``quasi nulla'' nel senso che è fatta solamente di $0$ ed $1$,
  238. ma la percentuale di $0$ è molto alta\footnote{è infatti presumibile che il numero di link uscenti da una pagina
  239. sia trascurabile rispetto al numero di pagine esistenti}. Possiamo quindi limitarci a memorizzare i seguenti dati: \\[5pt]
  240. \begin{tabular}{|lll|} \hline
  241. \textbf{Variabile} & \textbf{Descrizione} & \textbf{Spazio occupato} \\ \hline \hline
  242. $N$ & l'ordine della matrice $A$, ovvero il numero di pagine esistenti & 4 byte \\
  243. $m$ & il numero di campi di $\hat H$ diversi da $0$ & \textasciitilde $N$ byte \\
  244. $i, j$ & vettori delle coordinate dei campi diversi da $0$ di $\hat H$ & \textasciitilde $N$ byte \\
  245. $\gamma$ & coefficiente di personalizzazione & 4 byte \\
  246. $w$ & Vettore di personalizzazione & \textasciitilde $N$ byte \\
  247. $d$ & il vettore con gli elementi diagonali di $D$ & \textasciitilde $N$ byte \\
  248. \hline \end{tabular} \\
  249.  
  250. Possiamo ora porci il problema di calcolare $d$. Questo è reso apparentemente più complicato dal modo in cui abbiamo salvato
  251. la matrice. In realtà possiamo usare un algoritmo di questo tipo
  252.  
  253. \begin{lstlisting}[frame=tb,caption=Calcolo di $d$,label=lst:calcolovettorepers]{}
  254. ! Inizializziamo il vettore d a 0
  255. d = 0;
  256. ! Questo ciclo calcola i link uscenti da i per ogni i
  257. DO k=1,m
  258. d(i(k)) = d(i(k)) + 1
  259. END DO
  260. \end{lstlisting}
  261.  
  262. \begin{os}
  263. L'aver scelto questo tipo di memorizzazione per la matrice non solo non complica i generali procedimenti
  264. di moltiplicazione, ma riduce addirittura la complessità di questi perché iteriamo sugli elementi diversi da
  265. $0$ invece che su tutti.
  266. \end{os}
  267.  
  268.  
  269. \section{Una prima implementazione}
  270. In questa sezione ci occuperemo di presentare una prima implementazione della soluzione al problema.
  271. Non ci preoccuperemo troppo di ottimizzare il codice, compito che affronteremo in seguito.
  272. Esporremo inizialmente la struttura generale del programma e poi costruiremo tutte le subroutine necessarie.
  273.  
  274. \subsection{La struttura}
  275. Il programma dovrà comporsi delle seguenti parti
  276. \begin{description}
  277. \item[Lettura della matrice] Supporremo di avere un file da cui leggere i dati della matrice. Dobbiamo
  278. definire la sintassi del file e scrivere un subroutine per caricarlo in memoria;
  279. \item[Calcolo del vettore d] Data la matrice di connessione $H$ vorremo calcolare la somma di ogni
  280. riga che ci servirà in seguito per rinormalizzare il vettore di iterazione;
  281. \item[Iterazione] Generiamo un vettore $x$ casuale e lo riscaliamo in modo che $\trasp{e} x = 1$. Fatto
  282. questo cominciamo a calcolare l'iterazione, fino a che l'errore non è piccolo o ragguingiamo il massimo
  283. numero di iterazioni impostato;
  284. \item[Salvataggio] Salviamo su un file il vettore calcolato;
  285. \end{description}
  286.  
  287. Per questo avremo bisogno anche di alcune subroutine, ad esempio quella che ci calcoli il prodotto matrice
  288. vettore data la matrice sparsa come l'abbiamo intesa noi.
  289.  
  290. \subsection{Prodotto di matrice sparsa per un vettore}
  291. Esponiamo qui un'idea di codice per calcolare questo prodotto matrice vettore. Ricordiamo che per noi una
  292. matrice sparsa è composta di due scalari \lstinline-n-,\lstinline-m- (rispettivamente dimensione e numero di elementi diversi da $0$)
  293. e due vettori \lstinline-i-, \lstinline-j- di dimensione $m$ che sono le coordinate dei punti che valgono $1$.\\
  294. Il codice è il seguente
  295. \begin{lstlisting}[frame=tb,caption=Calcolo del prodotto matrice vettore,label=lst:matrixvec]
  296. SUBROUTINE dotprod(n,m,i,j,d,x,v,y)
  297. IMPLICIT NONE
  298. ! Il parametro dp dei tipi a doppia precisione
  299. INTEGER, PARAMETER :: dp = KIND(0.D0)
  300.  
  301. ! Dichiarazione degli argomenti della funzione
  302. INTEGER, INTENT(IN) :: n,m
  303. INTEGER, DIMENSION(m), INTENT(IN) :: i,j
  304. INTEGER, DIMENSION(n), INTENT(IN) :: d
  305. REAL(dp), DIMENSION(n), INTENT(IN) :: x, v
  306.  
  307. ! Dato di output
  308. REAL(dp), DIMENSION(n) :: y
  309.  
  310. ! Alcune variabili di comodo
  311. REAL(dp), DIMENSION(n) :: xx
  312. REAL(dp) :: s, gamma = 0.85
  313. INTEGER :: k
  314.  
  315. ! Calcoliamo x trasposto per la matrice D con i d(i) sulla
  316. ! diagonale, e salviamolo in xx, in modo da non modificare x
  317. ! Ricordiamo che i dati in fortrano vengono sempre passati per
  318. ! riferimento! Se modificassimo x qui dentro risulterebbe cambiato
  319. ! anche all'esterno.
  320. DO k=1,m
  321. IF(d(k)/=0) THEN
  322. xx(i(k)) = x(i(k))/d(k)
  323. ELSE
  324. xx(i(k)) = x(i(k))/n
  325. END IF
  326. END DO
  327.  
  328. ! Ora eseguiamo la moltiplicazione di xx per la parte non dangling
  329. ! della matrice H cappuccio e salviamo il risultato in y
  330. y = 0.D0
  331. DO k=1,m
  332. y(j(k)) = y(j(k)) + xx(i(k))
  333. END DO
  334.  
  335. ! Ora calcoliamo le righe dangling dalle quali prima non siamo "passati"
  336. ! perche' i(k) scorre solo sulle righe dove ci sono elementi non nulli
  337. DO k=1,n
  338. IF(d(k) == 0)
  339. s = s + xx(k)
  340. END IF
  341. END DO
  342.  
  343. ! Sommiamo i due contributi
  344. y = y + s
  345.  
  346. ! Calcolo la personalizzazione
  347. y = y * gamma + (1-gamma)/n
  348.  
  349. END SUBROUTINE dotprod
  350. \end{lstlisting}
  351.  
  352. %TODO: Presentare un'implementazione completa del programma