mirror of
https://github.com/lukefleed/ShfitedPowGMRES.git
synced 2026-10-18 03:02:05 +00:00
big restyle, last fixes before final release
This commit is contained in:
+507
@@ -0,0 +1,507 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Importing the libraries
|
||||
import os
|
||||
import wget
|
||||
import gzip
|
||||
import time
|
||||
import scipy as sp
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import networkx as nx
|
||||
from os.path import exists
|
||||
from scipy.sparse import *
|
||||
import plotly.graph_objs as go
|
||||
from typing import Literal
|
||||
|
||||
def load_data(dataset: Literal["Stanford", "NotreDame", "BerkStan"]) -> nx.Graph:
|
||||
"""Load the dataset and return a graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dataset : Literal["Stanford", "BerkStan"]
|
||||
The dataset to load.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nx.Graph
|
||||
The graph of the dataset.
|
||||
data/web-Stanford.txt
|
||||
|
||||
"""
|
||||
|
||||
# check if there is a data folder
|
||||
if not exists(os.path.join(os.getcwd(), "data")):
|
||||
os.mkdir(os.path.join(os.getcwd(), "data"))
|
||||
|
||||
if dataset not in ["Stanford", "NotreDame", "BerkStan"]:
|
||||
raise ValueError("Invalid dataset. Please choose a valid dataset.")
|
||||
|
||||
# Download the dataset
|
||||
if not exists(f"../data/Web-{dataset}.txt.gz"):
|
||||
print(f"\nDownloading the dataset {dataset}...")
|
||||
wget.download(f"http://snap.stanford.edu/data/web-{dataset}.txt.gz", out=f"data/Web-{dataset}.txt.gz")
|
||||
else:
|
||||
print(f"\nThe dataset {dataset} is already downloaded.")
|
||||
|
||||
# unzip the dataset
|
||||
if not exists(f"../data/Web-{dataset}.txt"):
|
||||
print(f"\nUnzipping the dataset {dataset}...")
|
||||
with gzip.open(f"../data/Web-{dataset}.txt.gz", "rb") as f_in:
|
||||
with open(f"../data/Web-{dataset}.txt", "wb") as f_out:
|
||||
f_out.write(f_in.read())
|
||||
|
||||
# create the graph
|
||||
print(f"\nCreating the graph of the dataset {dataset}...\n")
|
||||
G_dataset = nx.read_edgelist(f"../data/Web-{dataset}.txt", create_using=nx.DiGraph(), nodetype=int)
|
||||
print(f"\tNumber of nodes: {G_dataset.number_of_nodes()}")
|
||||
print(f"\tNumber of edges: {G_dataset.number_of_edges()}")
|
||||
|
||||
return G_dataset
|
||||
|
||||
def google_matrix(G, alpha=0.85, personalization=None, nodelist=None, weight="weight", dangling=None) -> np.matrix:
|
||||
|
||||
|
||||
"""Returns the Google matrix of the graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph. Undirected graphs will be converted to a directed
|
||||
graph with two directed edges for each undirected edge.
|
||||
|
||||
alpha : float
|
||||
The damping factor.
|
||||
|
||||
personalization: dict, optional
|
||||
The "personalization vector" consisting of a dictionary with a
|
||||
key some subset of graph nodes and personalization value each of those.
|
||||
At least one personalization value must be non-zero.
|
||||
If not specfiied, a nodes personalization value will be zero.
|
||||
By default, a uniform distribution is used.
|
||||
|
||||
nodelist : list, optional
|
||||
The rows and columns are ordered according to the nodes in nodelist.
|
||||
If nodelist is None, then the ordering is produced by G.nodes().
|
||||
|
||||
weight : key, optional
|
||||
Edge data key to use as weight. If None weights are set to 1.
|
||||
|
||||
dangling: dict, optional
|
||||
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
||||
any outedges. The dict key is the node the outedge points to and the dict
|
||||
value is the weight of that outedge. By default, dangling nodes are given
|
||||
outedges according to the personalization vector (uniform if not
|
||||
specified) This must be selected to result in an irreducible transition
|
||||
matrix (see notes below). It may be common to have the dangling dict to
|
||||
be the same as the personalization dict.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A : NumPy matrix
|
||||
Google matrix of the graph
|
||||
|
||||
Notes
|
||||
-----
|
||||
The matrix returned represents the transition matrix that describes the
|
||||
Markov chain used in PageRank. For PageRank to converge to a unique
|
||||
solution (i.e., a unique stationary distribution in a Markov chain), the
|
||||
transition matrix must be irreducible. In other words, it must be that
|
||||
there exists a path between every pair of nodes in the graph, or else there
|
||||
is the potential of "rank sinks."
|
||||
|
||||
"""
|
||||
if nodelist is None:
|
||||
nodelist = list(G)
|
||||
|
||||
A = nx.to_numpy_array(G, nodelist=nodelist, weight=weight)
|
||||
N = len(G)
|
||||
if N == 0:
|
||||
# TODO: Remove np.asmatrix wrapper in version 3.0
|
||||
return np.asmatrix(A)
|
||||
|
||||
# Personalization vector
|
||||
if personalization is None:
|
||||
p = np.repeat(1.0 / N, N)
|
||||
else:
|
||||
p = np.array([personalization.get(n, 0) for n in nodelist], dtype=float)
|
||||
if p.sum() == 0:
|
||||
raise ZeroDivisionError
|
||||
p /= p.sum()
|
||||
|
||||
# Dangling nodes
|
||||
if dangling is None:
|
||||
dangling_weights = p
|
||||
else:
|
||||
# Convert the dangling dictionary into an array in nodelist order
|
||||
dangling_weights = np.array([dangling.get(n, 0) for n in nodelist], dtype=float)
|
||||
dangling_weights /= dangling_weights.sum()
|
||||
dangling_nodes = np.where(A.sum(axis=1) == 0)[0]
|
||||
|
||||
# Assign dangling_weights to any dangling nodes (nodes with no out links)
|
||||
A[dangling_nodes] = dangling_weights
|
||||
|
||||
A /= A.sum(axis=1)[:, np.newaxis] # Normalize rows to sum to 1
|
||||
|
||||
return np.asmatrix(alpha * A + (1 - alpha) * p)
|
||||
|
||||
def google_matrix_sparse(G, alpha=0.85, personalization=None, nodelist=None, weight="weight", dangling=None) -> np.matrix:
|
||||
|
||||
|
||||
"""Returns the Google matrix of the graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph. Undirected graphs will be converted to a directed
|
||||
graph with two directed edges for each undirected edge.
|
||||
|
||||
alpha : float
|
||||
The damping factor.
|
||||
|
||||
personalization: dict, optional
|
||||
The "personalization vector" consisting of a dictionary with a
|
||||
key some subset of graph nodes and personalization value each of those.
|
||||
At least one personalization value must be non-zero.
|
||||
If not specfiied, a nodes personalization value will be zero.
|
||||
By default, a uniform distribution is used.
|
||||
|
||||
nodelist : list, optional
|
||||
The rows and columns are ordered according to the nodes in nodelist.
|
||||
If nodelist is None, then the ordering is produced by G.nodes().
|
||||
|
||||
weight : key, optional
|
||||
Edge data key to use as weight. If None weights are set to 1.
|
||||
|
||||
dangling: dict, optional
|
||||
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
||||
any outedges. The dict key is the node the outedge points to and the dict
|
||||
value is the weight of that outedge. By default, dangling nodes are given
|
||||
outedges according to the personalization vector (uniform if not
|
||||
specified) This must be selected to result in an irreducible transition
|
||||
matrix (see notes below). It may be common to have the dangling dict to
|
||||
be the same as the personalization dict.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A : NumPy matrix
|
||||
Google matrix of the graph
|
||||
|
||||
Notes
|
||||
-----
|
||||
The matrix returned represents the transition matrix that describes the
|
||||
Markov chain used in PageRank. For PageRank to converge to a unique
|
||||
solution (i.e., a unique stationary distribution in a Markov chain), the
|
||||
transition matrix must be irreducible. In other words, it must be that
|
||||
there exists a path between every pair of nodes in the graph, or else there
|
||||
is the potential of "rank sinks."
|
||||
|
||||
"""
|
||||
if nodelist is None:
|
||||
nodelist = list(G)
|
||||
|
||||
A = nx.to_scipy_sparse_array(G, nodelist=nodelist, weight=weight, format="lil", dtype=int)
|
||||
|
||||
N = len(G)
|
||||
if N == 0:
|
||||
return np.asmatrix(A)
|
||||
|
||||
# Personalization vector
|
||||
if personalization is None:
|
||||
p = np.repeat(1.0 / N, N)
|
||||
p = sp.sparse.lil_array(p)
|
||||
else:
|
||||
p = np.array([personalization.get(n, 0) for n in nodelist], dtype=float)
|
||||
if p.sum() == 0:
|
||||
raise ZeroDivisionError
|
||||
p /= p.sum()
|
||||
|
||||
# Dangling nodes
|
||||
if dangling is None:
|
||||
dangling_weights = np.ones(N, dtype=int)
|
||||
dangling_weights = sp.sparse.lil_array(dangling_weights, dtype=int)
|
||||
else:
|
||||
# Convert the dangling dictionary into an array in nodelist order
|
||||
dangling_weights = np.array([dangling.get(n, 0) for n in nodelist], dtype=float)
|
||||
dangling_weights /= dangling_weights.sum()
|
||||
|
||||
# # Assign dangling_weights to any dangling nodes (nodes with no out links).
|
||||
# for i in range(N):
|
||||
# if A[[i],:].sum(axis=1) == 0:
|
||||
# A[[i],:] = dangling_weights
|
||||
|
||||
# replace rows with all zeros with dangling_weights
|
||||
A[[A.sum(axis=1)==0],:] = dangling_weights
|
||||
|
||||
# Normalize rows
|
||||
row_sums = A.sum(axis=1) # row sums
|
||||
r_inv = np.power(row_sums.astype(float), -1).flatten() # inverse of row sums
|
||||
r_inv[np.isinf(r_inv)] = 0.0 # replace inf with 0
|
||||
R = sp.sparse.diags(r_inv) # create diagonal matrix
|
||||
A = R.dot(A) # normalize rows
|
||||
|
||||
return A, p
|
||||
|
||||
def pagerank_numpy(G, alpha=0.85, personalization=None, weight="weight", dangling=None):
|
||||
"""Returns the PageRank of the nodes in the graph.
|
||||
|
||||
PageRank computes a ranking of the nodes in the graph G based on
|
||||
the structure of the incoming links. It was originally designed as
|
||||
an algorithm to rank web pages.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph. Undirected graphs will be converted to a directed
|
||||
graph with two directed edges for each undirected edge.
|
||||
|
||||
alpha : float, optional
|
||||
Damping parameter for PageRank, default=0.85.
|
||||
|
||||
personalization: dict, optional
|
||||
The "personalization vector" consisting of a dictionary with a
|
||||
key some subset of graph nodes and personalization value each of those.
|
||||
At least one personalization value must be non-zero.
|
||||
If not specfiied, a nodes personalization value will be zero.
|
||||
By default, a uniform distribution is used.
|
||||
|
||||
weight : key, optional
|
||||
Edge data key to use as weight. If None weights are set to 1.
|
||||
|
||||
dangling: dict, optional
|
||||
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
||||
any outedges. The dict key is the node the outedge points to and the dict
|
||||
value is the weight of that outedge. By default, dangling nodes are given
|
||||
outedges according to the personalization vector (uniform if not
|
||||
specified) This must be selected to result in an irreducible transition
|
||||
matrix (see notes under google_matrix). It may be common to have the
|
||||
dangling dict to be the same as the personalization dict.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pagerank : dictionary
|
||||
Dictionary of nodes with PageRank as value.
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
The eigenvector calculation uses NumPy's interface to the LAPACK
|
||||
eigenvalue solvers. This will be the fastest and most accurate
|
||||
for small graphs.
|
||||
|
||||
"""
|
||||
if len(G) == 0:
|
||||
return {}
|
||||
M = google_matrix(
|
||||
G, alpha, personalization=personalization, weight=weight, dangling=dangling
|
||||
)
|
||||
# use numpy LAPACK solver
|
||||
eigenvalues, eigenvectors = np.linalg.eig(M.T)
|
||||
ind = np.argmax(eigenvalues)
|
||||
# eigenvector of largest eigenvalue is at ind, normalized
|
||||
largest = np.array(eigenvectors[:, ind]).flatten().real
|
||||
norm = largest.sum()
|
||||
return dict(zip(G, map(float, largest / norm)))
|
||||
|
||||
def pagerank(G, alpha=0.85, personalization=None, max_iter=10000, tol=1.0e-9, nstart=None, weight="weight", dangling=None,):
|
||||
|
||||
"""
|
||||
Returns the PageRank of the nodes in the graph.
|
||||
|
||||
PageRank computes a ranking of the nodes in the graph G based on
|
||||
the structure of the incoming links. It was originally designed as
|
||||
an algorithm to rank web pages.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph. Undirected graphs will be converted to a directed
|
||||
graph with two directed edges for each undirected edge.
|
||||
|
||||
alpha : float, optional
|
||||
Damping parameter for PageRank, default=0.85.
|
||||
|
||||
personalization: dict, optional
|
||||
The "personalization vector" consisting of a dictionary with a
|
||||
key some subset of graph nodes and personalization value each of those.
|
||||
At least one personalization value must be non-zero.
|
||||
If not specfiied, a nodes personalization value will 1/N where N is the
|
||||
number of nodes in G.
|
||||
|
||||
max_iter : integer, optional
|
||||
Maximum number of iterations in power method eigenvalue solver.
|
||||
|
||||
tol : float, optional
|
||||
Error tolerance used to check convergence in power method solver.
|
||||
|
||||
nstart : dictionary, optional
|
||||
Starting value of PageRank iteration for each node.
|
||||
|
||||
weight : key, optional
|
||||
Edge data key to use as weight. If None weights are set to 1.
|
||||
|
||||
dangling: dict, optional
|
||||
The outedges to be assigned to any "dangling" nodes, i.e., nodes without
|
||||
any outedges. The dict key is the node the outedge points to and the dict
|
||||
value is the weight of that outedge. By default, dangling nodes are given
|
||||
outedges according to the personalization vector (uniform if not
|
||||
specified) This must be selected to result in an irreducible transition
|
||||
matrix (see notes under google_matrix). It may be common to have the
|
||||
dangling dict to be the same as the personalization dict.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pagerank : dictionary
|
||||
Dictionary of nodes with PageRank as value
|
||||
|
||||
Notes
|
||||
-----
|
||||
The eigenvector calculation uses power iteration with a SciPy
|
||||
sparse matrix representation.
|
||||
|
||||
Raises
|
||||
------
|
||||
PowerIterationFailedConvergence
|
||||
If the algorithm fails to converge to the specified tolerance
|
||||
within the specified number of iterations of the power iteration
|
||||
method.
|
||||
"""
|
||||
|
||||
N = len(G)
|
||||
if N == 0:
|
||||
return {}
|
||||
|
||||
nodelist = list(G)
|
||||
A = nx.to_scipy_sparse_array(G, nodelist=nodelist, weight=weight, dtype=float)
|
||||
S = A.sum(axis=1) # S[i] is the sum of the weights of edges going out of node i
|
||||
S[S != 0] = 1.0 / S[S != 0] # S[i] is now the sum of the weights of edges going into node i
|
||||
Q = sp.sparse.csr_array(sp.sparse.spdiags(S.T, 0, *A.shape)) # Q is the matrix of edge weights going into each node
|
||||
A = Q @ A # A is now the "stochastic matrix"
|
||||
|
||||
# initial vector
|
||||
if nstart is None: # if no initial vector is specified, start with a uniform vector
|
||||
x = np.repeat(1.0 / N, N) # x is the vector of PageRank values
|
||||
else: # if an initial vector is specified, normalize it
|
||||
x = np.array([nstart.get(n, 0) for n in nodelist], dtype=float) # x is the vector of PageRank values
|
||||
x /= x.sum() # normalize x
|
||||
|
||||
# Personalization vector
|
||||
if personalization is None: # if no personalization vector is specified, use a uniform vector
|
||||
p = np.repeat(1.0 / N, N) # p is the personalization vector
|
||||
else: # if a personalization vector is specified, normalize it
|
||||
p = np.array([personalization.get(n, 0) for n in nodelist], dtype=float) # p is the personalization vector
|
||||
if p.sum() == 0: # if the personalization vector is all zeros, use a uniform vector
|
||||
raise ZeroDivisionError
|
||||
p /= p.sum() # normalize p
|
||||
# Dangling nodes
|
||||
if dangling is None: # if no dangling nodes are specified, use a uniform vector
|
||||
dangling_weights = p # dangling_weights is the vector of dangling node weights
|
||||
else:
|
||||
# Convert the dangling dictionary into an array in nodelist order
|
||||
dangling_weights = np.array([dangling.get(n, 0) for n in nodelist], dtype=float) # dangling_weights is the vector of dangling node weights
|
||||
dangling_weights /= dangling_weights.sum() # normalize dangling_weights
|
||||
is_dangling = np.where(S == 0)[0] # is_dangling is the list of dangling nodes
|
||||
|
||||
# power iteration: make up to max_iter iterations
|
||||
iter = 1
|
||||
for _ in range(max_iter):
|
||||
iter += 1
|
||||
xlast = x # xlast is the previous vector of PageRank values
|
||||
x = alpha * (x @ A + sum(x[is_dangling]) * dangling_weights) + (1 - alpha) * p # x is the current vector of PageRank values
|
||||
# check convergence, l1 norm
|
||||
err = np.absolute(x - xlast).sum() # err is the error between the current and previous vectors of PageRank values
|
||||
print("Iteration: ", _, "\r", end="")
|
||||
|
||||
if err < N * tol: # if the error is small enough, stop iterating
|
||||
return dict(zip(nodelist, map(float, x))), iter, tol # return the current vector of PageRank values'
|
||||
|
||||
# other wise, return a Null dictionary, the number of iterations, and the tolerance
|
||||
# this is a failure to convergeS
|
||||
|
||||
return {}, iter, tol
|
||||
|
||||
def shifted_pow_pagerank(G, alphas=[0.85, 0.9, 0.95, 0.99], max_iter=10000, tol=1.0e-9):
|
||||
|
||||
"""
|
||||
Compute the PageRank of each node in the graph G.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph. Undirected graphs will be converted to a directed graph.
|
||||
|
||||
alphas : list, optional
|
||||
A list of alpha values to use in the shifted power method. The default is [0.85, 0.9, 0.95, 0.99].
|
||||
|
||||
max_iter : integer, optional
|
||||
Maximum number of iterations in power method eigenvalue solver.
|
||||
|
||||
tol : float, optional
|
||||
Error tolerance used to check convergence in power method solver.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pagerank : dictionary
|
||||
Dictionary of nodes with PageRank as value
|
||||
|
||||
mv : integer
|
||||
The number of matrix-vector multiplications used in the power method
|
||||
|
||||
Notes
|
||||
-----
|
||||
The eigenvector calculation uses power iteration with a SciPy sparse matrix representation. The shifted power method is described as Algorithm 1 in the paper located in the sources folders.
|
||||
|
||||
"""
|
||||
|
||||
N = len(G)
|
||||
if N == 0:
|
||||
raise nx.NetworkXException("Empty graph.")
|
||||
|
||||
A, v = google_matrix_sparse(G)
|
||||
|
||||
# initialize a sparse matrix of dimension N x len(alphas). The cols of this matrix are the page rank vectors for each alpha. Each col is v
|
||||
x = sp.sparse.lil_matrix((N, len(alphas)))
|
||||
for i in range(len(alphas)):
|
||||
# reshape v to a column vector
|
||||
v = v.reshape((N,1))
|
||||
x[:, [i]] = v
|
||||
|
||||
# make v sparse
|
||||
v = sp.sparse.lil_array(v)
|
||||
|
||||
mu = A @ v - v # column vector
|
||||
|
||||
r = sp.sparse.lil_matrix((N, len(alphas))) # residual matrix
|
||||
res = np.ones(len(alphas)) # residual norm vector
|
||||
|
||||
for i in range(len(alphas)):
|
||||
r[:,[i]] = alphas[i] * mu # residual vector for the i-th alpha
|
||||
# r_i = r[:,[i]].toarray()
|
||||
res[i] = sp.sparse.linalg.norm(r[:,[i]]) # residual norm for the i-th alpha
|
||||
if res[i] >= tol:
|
||||
x[:, [i]] = r[:,[i]] + v # update the i-th column of x
|
||||
|
||||
mv = 1 # number of matrix-vector multiplications
|
||||
for _ in range(max_iter): # starting of the while loop in the paper
|
||||
# print("Iteration: ", _)
|
||||
err = np.max(res)
|
||||
# print("Error: ", err)
|
||||
if err < tol:
|
||||
print("Convergence reached with, ", mv, " matrix-vector multiplications")
|
||||
return x, mv, alphas, tol
|
||||
|
||||
print("Iteration: ", _, "\r", end="")
|
||||
|
||||
|
||||
mu = A @ mu
|
||||
mv += 1
|
||||
for i in range(len(alphas)):
|
||||
if res[i] >= tol:
|
||||
r[:,[i]] = np.power(alphas[i], mv+1) * mu
|
||||
# r_i = r[:,[i]].toarray()
|
||||
res[i] = sp.sparse.linalg.norm(r[:,[i]])
|
||||
|
||||
if res[i] >= tol:
|
||||
x[:, [i]] = r[:,[i]] + v
|
||||
|
||||
raise nx.PowerIterationFailedConvergence(max_iter) # if the error is not small enough, raise an error
|
||||
Vendored
-257
@@ -1,257 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import networkx as nx\n",
|
||||
"import time\n",
|
||||
"import math\n",
|
||||
"import pandas as pd\n",
|
||||
"import scipy as sp\n",
|
||||
"import plotly.express as px\n",
|
||||
"import plotly.graph_objs as go\n",
|
||||
"from scipy.sparse import *\n",
|
||||
"from scipy import linalg\n",
|
||||
"from scipy.sparse.linalg import norm\n",
|
||||
"from scipy.optimize import least_squares"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Arnoldi \n",
|
||||
"\n",
|
||||
"This is a copy of the algorithm defined and tested in the notebook `algo2_testing`. It's an implementation of the Algorithm 2 from the paper. It's needed in this notebook since this function is called by the `algo4` function. It's implemented to return exactly what's needed in the `algo4` function.\n",
|
||||
"\n",
|
||||
"Everything will be reorganized in the main.py file once everything is working."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def Arnoldi(A,v0,m):\n",
|
||||
" v = v0\n",
|
||||
" beta = norm(v)\n",
|
||||
" v = v/beta\n",
|
||||
" H = sp.sparse.lil_matrix((m+1,m)) \n",
|
||||
" V = sp.sparse.lil_matrix((A.shape[0],m+1))\n",
|
||||
" V[:,0] = v # each column of V is a vector v\n",
|
||||
"\n",
|
||||
" for j in range(m):\n",
|
||||
" w = A @ v \n",
|
||||
" for i in range(j):\n",
|
||||
" tmp = v.T @ w # tmp is a 1x1 matrix, so it's O(1) in memory\n",
|
||||
" H[i,j] = tmp[0,0] \n",
|
||||
" w = w - H[i,j]*v \n",
|
||||
" \n",
|
||||
" H[j+1,j] = norm(w)\n",
|
||||
"\n",
|
||||
" if H[j+1,j] == 0:\n",
|
||||
" # print(\"Arnoldi breakdown\")\n",
|
||||
" m = j\n",
|
||||
" v = 0\n",
|
||||
" break\n",
|
||||
" else:\n",
|
||||
" if j < m-1:\n",
|
||||
" v = w/H[j+1,j]\n",
|
||||
" V[:,j+1] = v\n",
|
||||
"\n",
|
||||
" return V, H, v, beta, j "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Algorithm 4 testing\n",
|
||||
"\n",
|
||||
"This algorithm is based on the \"Algorithm 4\" of the paper, the pseudocode provided by the authors is the following \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Line 14 is particularly tricky to understand, not working for now. Need to figure out how to solve that linear system. My idea was to do something like that\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"And use the `sp.sparse.linalg.spsolve` function to solve the linear system as $Ax=0$ where $A$ is $[\\bar H_m^i ~ | ~ z]$ but it returns an array of zeros. So the idea it's wrong"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def Algo4(Pt, v, m, a: list, tau, maxit: int, x):\n",
|
||||
" \n",
|
||||
" # I'm using a non declared variable n here , it's declared in the next cell when I call this function. This will be fixed later in the main.py file\n",
|
||||
"\n",
|
||||
" iter = 1\n",
|
||||
" mv = 0\n",
|
||||
" I = sp.sparse.eye(n, n, format='lil')\n",
|
||||
" r = sp.sparse.lil_matrix((n,1))\n",
|
||||
" res = np.zeros(len(a)) \n",
|
||||
"\n",
|
||||
" # I'm defining 3 canonical vectors of different sizes. It's probably stupid, will be fixed once the algorithm actually works\n",
|
||||
"\n",
|
||||
" H_e1 = np.zeros((m+1,1)) # canonical basis vector of size H.shape[0]\n",
|
||||
" H_e1[0] = 1\n",
|
||||
"\n",
|
||||
" V_e1 = np.zeros((n,1)) # canonical basis vector of size V.shape[0]\n",
|
||||
" V_e1[0] = 1\n",
|
||||
"\n",
|
||||
" s_e1 = np.zeros((len(a),1)) # canonical basis vector of size s.shape[0]\n",
|
||||
" s_e1[0] = 1\n",
|
||||
"\n",
|
||||
" def find_k(res): # function to find the index of the largest element in res\n",
|
||||
" k = 0\n",
|
||||
" for i in range(len(a)):\n",
|
||||
" if res[i] == max(res):\n",
|
||||
" k = i\n",
|
||||
" break\n",
|
||||
" return k\n",
|
||||
"\n",
|
||||
" def compute_gamma(res, a, k): # function to compute gamma\n",
|
||||
" gamma = np.zeros(len(a))\n",
|
||||
" for i in range(len(a)):\n",
|
||||
" if i != k:\n",
|
||||
" gamma[i] = (res[i]*a[k])/(res[k]*a[i])\n",
|
||||
" else:\n",
|
||||
" gamma[i] = 0\n",
|
||||
" return gamma\n",
|
||||
"\n",
|
||||
" # compute the residual vector\n",
|
||||
" for i in range(len(a)):\n",
|
||||
" r = ((1-a[i])/a[i])*v - ((1/a[i])*I - Pt) @ x\n",
|
||||
" res[i] = a[i]*norm(r)\n",
|
||||
"\n",
|
||||
" while max(res) >= tau and iter <= maxit:\n",
|
||||
" k = find_k(res)\n",
|
||||
" gamma = compute_gamma(res, a, k)\n",
|
||||
" V, H, v, beta, j = Arnoldi((1/a[k])*I - Pt, r, m)\n",
|
||||
"\n",
|
||||
" mv = mv + j\n",
|
||||
"\n",
|
||||
" # compute y as the minimizer of || beta*e1 - Hy ||_2 using the least squares method\n",
|
||||
" y = sp.sparse.linalg.lsqr(H, beta*H_e1)[0]\n",
|
||||
"\n",
|
||||
" # reshape y to be a column vector\n",
|
||||
" y = y.reshape(y.shape[0],1)\n",
|
||||
"\n",
|
||||
" # update x \n",
|
||||
" x += V[:,0:y.shape[0]] @ y\n",
|
||||
"\n",
|
||||
" # compute the residual vector\n",
|
||||
" res[k] = a[k]*np.linalg.norm(beta*V_e1 - V[:,0:y.shape[0]] @ y)\n",
|
||||
" \n",
|
||||
" # for i in range(len(a)) but not k\n",
|
||||
" for i in range(len(a)):\n",
|
||||
" if i != k and res[i] >= tau:\n",
|
||||
" # Compute H as described in the paper\n",
|
||||
" H = H + ((1-a[i])/a[i] - (1-a[k])/a[k])*sp.sparse.eye(H.shape[0], H.shape[1], format='lil') \n",
|
||||
"\n",
|
||||
" z = beta*H_e1 - H @ y # define z as in the paper (page 9)\n",
|
||||
" A_tmp = sp.sparse.hstack([H, z]) # stack H and z, as in the paper, to solve the linear system (?)\n",
|
||||
" A_tmp = A_tmp.tocsc() # Convert A to CSC format for sparse solver\n",
|
||||
"\n",
|
||||
" # What should I put here? What does it mean in the paper the line 14 of the pseudocode?\n",
|
||||
" result = sp.sparse.linalg.spsolve(A_tmp, np.zeros(A_tmp.shape[0])) # if I solve this, I get a vector of zeros.\n",
|
||||
" print(result)\n",
|
||||
" \n",
|
||||
" # I don't know if the code below is correct since I don't get how to solve the linear system above, so I'm unsure about what y and gamma should be. For now it's commented out.\n",
|
||||
"\n",
|
||||
" # # update x\n",
|
||||
" # x += V[:,0:y.shape[0]] @ y\n",
|
||||
" # # update the residual vector\n",
|
||||
" # res[i] = (a[i]/a[k])*gamma[k]*res[k] \n",
|
||||
"\n",
|
||||
" iter = iter + 1\n",
|
||||
"\n",
|
||||
" return x, iter, mv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Basic test case with random numbers to test the algorithm."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"n = 100\n",
|
||||
"m = 110\n",
|
||||
"maxit = 100\n",
|
||||
"tau = 1e-6\n",
|
||||
"a = [0.85, 0.9, 0.95, 0.99]\n",
|
||||
"\n",
|
||||
"x = sp.sparse.lil_matrix((n,1))\n",
|
||||
"x[0,0] = 1\n",
|
||||
"\n",
|
||||
"# generate a random graph\n",
|
||||
"G = nx.gnp_random_graph(n, 0.1, seed=1, directed=True)\n",
|
||||
"\n",
|
||||
"P = sp.sparse.lil_matrix((n,n))\n",
|
||||
"for i in G.nodes():\n",
|
||||
" for j in G[i]: #G[i] is the list of nodes connected to i, it's neighbors\n",
|
||||
" P[i-1,j-1] = 1/len(G[i])\n",
|
||||
"\n",
|
||||
"# generate a probability vector, with all the entries as 1/n\n",
|
||||
"v = sp.sparse.lil_matrix((n,1))\n",
|
||||
"for i in range(n):\n",
|
||||
" v[i] = 1/n\n",
|
||||
"\n",
|
||||
"# dangling nodes vector\n",
|
||||
"d = sp.sparse.lil_matrix((n,1))\n",
|
||||
"for i in range(n):\n",
|
||||
" if P[i].sum() == 0:\n",
|
||||
" d[i] = 1\n",
|
||||
"\n",
|
||||
"# compute the transition matrix\n",
|
||||
"Pt = P + v @ (d.T)\n",
|
||||
"\n",
|
||||
"# run the algorithm\n",
|
||||
"Algo4(Pt, v, m, a, tau, maxit, x)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.10.6 64-bit",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.6"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
#! /usr/bin/python
|
||||
|
||||
from algo import *
|
||||
import warnings
|
||||
import argparse
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
# df = pd.DataFrame(columns=["method" "alpha", "cpu_time", "mv", "tol"])
|
||||
|
||||
def run_standard_pagerank(G, alphas):
|
||||
print("\nStarting the standard pagerank algorithm...\n")
|
||||
|
||||
iter_dict = dict.fromkeys(alphas, 0)
|
||||
list_of_pageranks = []
|
||||
|
||||
start1 = time.time()
|
||||
for alpha in alphas:
|
||||
x, iter, tol = pagerank(G, alpha, tol=1e-6)
|
||||
iter_dict[alpha] = iter
|
||||
list_of_pageranks.append(x)
|
||||
end1 = time.time()
|
||||
|
||||
total_iter = sum(iter_dict.values())
|
||||
cpu_time = round(end1 - start1,1)
|
||||
mv = total_iter
|
||||
|
||||
print("\nSTANDARD PAGERANK ALGORITHM\n")
|
||||
print("\tCPU time (s):", cpu_time)
|
||||
print("\tMatrix-vector multiplications:", mv)
|
||||
print("\tAlpha:", alphas)
|
||||
print("\tTolerance:", tol)
|
||||
|
||||
for i in range(len(list_of_pageranks)):
|
||||
if not list_of_pageranks[i]:
|
||||
print("The algorithm did not converge for alpha =", alphas[i])
|
||||
|
||||
|
||||
# df.loc[len(df)] = ["Power Method", alphas, cpu_time, mv, tol]
|
||||
# df.to_csv(args.dataset + "_results.tsv", sep="\t", index=False)
|
||||
# print("\nThe results are saved in the file:", args.dataset + "_results.tsv")
|
||||
|
||||
|
||||
def run_shifted_powe(G, alphas):
|
||||
print("\nStarting the shifted pagerank algorithm...\n")
|
||||
|
||||
start2 = time.time()
|
||||
x, mv, alphas, tol = shifted_pow_pagerank(G, alphas, tol=1e-6)
|
||||
end2 = time.time()
|
||||
cpu_time = round(end2 - start2,1)
|
||||
|
||||
print("\nSHIFTED PAGERANK ALGORITHM\n")
|
||||
print("\tCPU time (s):", cpu_time)
|
||||
print("\tMatrix-vector multiplications:", mv)
|
||||
print("\tAlphas:", alphas)
|
||||
print("\tTolerance:", tol)
|
||||
|
||||
# df.loc[len(df)] = ["Shifted Power Method", alphas, cpu_time, mv, tol]
|
||||
# df.to_csv(args.dataset + "_results.tsv", sep="\t", index=False)
|
||||
# print("\nThe results are saved in the file:", args.dataset + "_results.tsv")
|
||||
|
||||
# main function
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset", type=str, default="Stanford", help="Choose the dataset to work with. The options are: Stanford, NotreDame, BerkStan")
|
||||
parser.add_argument("--algo", type=str, default="both", help="Choose the algorithm to use. The options are: pagerank, shifted_pagerank, both")
|
||||
args = parser.parse_args()
|
||||
|
||||
G = load_data(args.dataset)
|
||||
|
||||
alphas = [0.85, 0.86, 0.87, 0.88, 0.89, 0.90, 0.91, 0.92, 0.93, 0.94, 0.95, 0.96, 0.97, 0.98, 0.99]
|
||||
|
||||
if args.algo == "pagerank":
|
||||
run_standard_pagerank(G, alphas)
|
||||
elif args.algo == "shifted_pagerank":
|
||||
run_shifted_powe(G, alphas)
|
||||
elif args.algo == "both":
|
||||
run_standard_pagerank(G, alphas)
|
||||
run_shifted_powe(G, alphas)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user