-
Notifications
You must be signed in to change notification settings - Fork 16
Add algorithms #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add algorithms #243
Changes from all commits
ffabf62
102eeff
0886e3a
2ac847a
b1f7bd6
5687bdd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Install ruff to check your code style : | ||
| ```bash | ||
| pip install ruff | ||
| ``` | ||
| # To format code, run: | ||
| ```bash | ||
| ruff format <filename>.py | ||
| ``` | ||
| # To check a linting problems, run: | ||
| ```bash | ||
| ruff check <filename>.py | ||
| ``` | ||
| # To fix linting problems, run: | ||
| ```bash | ||
| ruff check --fix <filename>.py | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # Graph Algorithms with SPLA | ||
|
|
||
| This project contains two implementations of graph algorithms: | ||
|
|
||
| - **Classic (CPU):** Python reference implementation | ||
| - **SPLA (GPU):** implementation using sparse linear algebra primitives from SPLA | ||
|
|
||
| Tests (`test_compare.py`) checks that both implementations produce identical results. | ||
|
|
||
| Run tests: | ||
| ```bash | ||
| python -m unittest test_compare.py -v | ||
| ``` | ||
| --- | ||
|
|
||
| ## Supported Algorithms | ||
|
|
||
| | Algorithm | Flag | | ||
| |-----------|------| | ||
| | BFS (Breadth-First Search) | `--algo bfs` | | ||
| | SSSP (Single-Source Shortest Paths) | `--algo sssp` | | ||
| | PageRank | `--algo pr` | | ||
| | Triangle Counting | `--algo tc` | | ||
|
|
||
| --- | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| cd python | ||
| python -m venv venv | ||
| source venv/bin/activate | ||
| pip install -e . | ||
| cd algorithms | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Usage | ||
|
|
||
| Two entry points are available: | ||
|
|
||
| - `main_spla.py` — GPU version (SPLA) | ||
| - `main_classic.py` — CPU reference version | ||
|
|
||
| Both scripts use the same CLI arguments. | ||
|
|
||
| ```text | ||
| usage: main_spla.py [-h] --algo {bfs,sssp,pr,tc} [-m MATRIX] [-v VECTORS] [-o OUTPUT] [-s START] [-a ALPHA] [-e EPS] | ||
| options: | ||
| -h, --help show this help message and exit | ||
| --algo {bfs,sssp,pr,tc} | ||
| Algorithm to run: | ||
| bfs - Breadth-First Search | ||
| sssp - Single-Source Shortest Paths | ||
| pr - PageRank | ||
| tc - Triangle Counting | ||
| -m MATRIX, --matrix MATRIX | ||
| Path to graph in Matrix Market format (.mtx) | ||
| -v VECTORS, --vectors VECTORS | ||
| Path to graph in vectors format (.txt) | ||
| -o OUTPUT, --output OUTPUT | ||
| Output file path (default: result.txt) | ||
| -s START, --start START | ||
| Start vertex (used in bfs, sssp; default: 0) | ||
| -a ALPHA, --alpha ALPHA | ||
| Damping factor (used in pr; default: 0.85) | ||
| -e EPS, --eps EPS | ||
| Convergence tolerance (used in pr; default: 1e-4) | ||
| ``` | ||
| --- | ||
|
|
||
| ## Examples | ||
|
|
||
| ```bash | ||
| # BFS | ||
| python main_spla.py --algo bfs -m graph.mtx -s 0 -o bfs_result.txt | ||
|
|
||
| # SSSP | ||
| python main_spla.py --algo sssp -v graph.txt -s 0 -o sssp_result.txt | ||
|
|
||
| # PageRank | ||
| python main_spla.py --algo pr -v graph.txt -a 0.85 -e 1e-6 -o pr_result.txt | ||
|
|
||
| # Triangle Counting | ||
| python main_spla.py --algo tc -m graph.mtx -o tc_result.txt | ||
|
|
||
| # Classic reference version | ||
| python main_classic.py --algo bfs -v graph.txt -s 0 -o bfs_classic.txt | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Input format | ||
|
|
||
| Two options: | ||
|
|
||
| - `-v graph.txt` — Vectors format | ||
| - `-m graph.mtx` — Matrix Market format (from https://sparse.tamu.edu/) | ||
|
|
||
|
|
||
| ### graph.txt | ||
|
|
||
| Example | ||
| ```text | ||
| 3 # number of vertices n | ||
| 0 1 2 # I: source vertex indices | ||
| 1 2 0 # J: target vertex indices | ||
| 5 3 2 # V: edge weights | ||
| ``` | ||
|
|
||
| ### graph.mtx (Matrix Market) | ||
| Example | ||
| ```text | ||
| %%MatrixMarket matrix coordinate real general | ||
| % rows cols nnz | ||
| 4 4 4 #rows, cols, non-zero values | ||
| 1 2 1.0 #source vertex, target vertex, weight | ||
| 2 3 2.0 | ||
| 3 4 3.0 | ||
| 4 1 4.0 | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| from collections import deque | ||
|
|
||
| INF = int(1e9) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why you need distance and infinite in BFS? |
||
|
|
||
|
|
||
| def bfs(start, graph, n): | ||
| visited = [0] * n | ||
| dist = [INF] * n | ||
| q = deque() | ||
| visited[start] = 1 | ||
| dist[start] = 0 | ||
| q.append(start) | ||
|
|
||
| while q: | ||
| u = q.popleft() | ||
| for v in graph[u]: | ||
| if not visited[v]: | ||
| visited[v] = 1 | ||
| dist[v] = dist[u] + 1 | ||
| q.append(v) | ||
| return dist | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from pyspla import INT, Matrix, Scalar, Vector | ||
|
|
||
|
|
||
| def bfs(s: int, A: Matrix): | ||
| """ | ||
| The following function implements single-source breadth-first search algoritm through masked matrix-vector product. | ||
| The algorithm accepts starting vertex and an adjacency matrix of a graph. It traverces graph using `vxm` and assigns depths to reached vertices. | ||
| Mask is used to update only unvisited vertices reducing number of required computations. | ||
| """ | ||
| v = Vector(A.n_rows, INT) | ||
| front = Vector.from_lists([s], [1], A.n_rows, INT) | ||
| front_size = 1 | ||
| depth = Scalar(INT, 0) | ||
| count = 0 | ||
|
|
||
| while front_size > 0: | ||
| depth += 1 | ||
| count += front_size | ||
| v.assign(front, depth, op_assign=INT.SECOND, op_select=INT.NQZERO) | ||
| front = front.vxm(v, A, op_mult=INT.LAND, op_add=INT.LOR, op_select=INT.EQZERO) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How you prevent repeated visiting of vertices? |
||
| front_size = front.reduce(op_reduce=INT.PLUS).get() | ||
|
|
||
| return v, count, depth.get() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| 3 | ||
| 0 1 2 | ||
| 1 2 0 | ||
| 5 3 2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| from collections import defaultdict | ||
|
|
||
|
|
||
| def read_mtx_unweighted(filename): | ||
| n = 0 | ||
| graph = defaultdict(list) | ||
| with open(filename, "r") as f: | ||
| for line in f: | ||
| if line.startswith("%"): | ||
| continue | ||
| parts = line.split() | ||
| if n == 0: | ||
| n = int(parts[0]) | ||
| continue | ||
| i = int(parts[0]) - 1 | ||
| j = int(parts[1]) - 1 | ||
| graph[i].append(j) | ||
| if i != j: | ||
| graph[j].append(i) | ||
| return graph, n | ||
|
|
||
|
|
||
| def read_vectors_unweighted(filename): | ||
| with open(filename, "r") as f: | ||
| n_line = f.readline().strip() | ||
| if not n_line: | ||
| return defaultdict(list), 0 | ||
| n = int(n_line) | ||
| I = list(map(int, f.readline().split())) | ||
| J = list(map(int, f.readline().split())) | ||
| graph = defaultdict(list) | ||
| for k in range(len(I)): | ||
| i = I[k] | ||
| j = J[k] | ||
| graph[i].append(j) | ||
| if i != j: | ||
| graph[j].append(i) | ||
| return graph, n | ||
|
|
||
|
|
||
| def read_mtx_weighted(filename): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it helper for MTX reading? Can we use standard functions to do it? |
||
| n = 0 | ||
| graph = defaultdict(list) | ||
| with open(filename, "r") as f: | ||
| for line in f: | ||
| if line.startswith("%"): | ||
| continue | ||
| parts = line.split() | ||
| if n == 0: | ||
| n = int(parts[0]) | ||
| continue | ||
| i = int(parts[0]) - 1 | ||
| j = int(parts[1]) - 1 | ||
| v = float(parts[2]) if len(parts) > 2 else 1.0 | ||
| graph[i].append((j, v)) | ||
| if i != j: | ||
| graph[j].append((i, v)) | ||
| return graph, n | ||
|
|
||
|
|
||
| def read_vectors_weighted(filename): | ||
| with open(filename, "r") as f: | ||
| n_line = f.readline().strip() | ||
| if not n_line: | ||
| return defaultdict(list), 0 | ||
| n = int(n_line) | ||
| I = list(map(int, f.readline().split())) | ||
| J = list(map(int, f.readline().split())) | ||
| V = list(map(float, f.readline().split())) | ||
| graph = defaultdict(list) | ||
| for k in range(len(I)): | ||
| i = I[k] | ||
| j = J[k] | ||
| v = V[k] | ||
| graph[i].append((j, v)) | ||
| if i != j: | ||
| graph[j].append((i, v)) | ||
| return graph, n | ||
|
|
||
|
|
||
| def read_mtx_pr_classic(filename): | ||
| n = 0 | ||
| adj_in = defaultdict(list) | ||
| with open(filename, "r") as f: | ||
| for line in f: | ||
| if line.startswith("%"): | ||
| continue | ||
| parts = line.split() | ||
| if n == 0: | ||
| n = int(parts[0]) | ||
| out_degree = [0] * n | ||
| continue | ||
| i = int(parts[0]) - 1 | ||
| j = int(parts[1]) - 1 | ||
| adj_in[j].append(i) | ||
| out_degree[i] += 1 | ||
| if i != j: | ||
| adj_in[i].append(j) | ||
| out_degree[j] += 1 | ||
| return adj_in, out_degree, n | ||
|
|
||
|
|
||
| def read_vectors_pr_classic(filename): | ||
| with open(filename, "r") as f: | ||
| n_line = f.readline().strip() | ||
| if not n_line: | ||
| return defaultdict(list), [], 0 | ||
| n = int(n_line) | ||
| I = list(map(int, f.readline().split())) | ||
| J = list(map(int, f.readline().split())) | ||
| adj_in = defaultdict(list) | ||
| out_degree = [0] * n | ||
| for k in range(len(I)): | ||
| i = I[k] | ||
| j = J[k] | ||
| adj_in[j].append(i) | ||
| out_degree[i] += 1 | ||
| if i != j: | ||
| adj_in[i].append(j) | ||
| out_degree[j] += 1 | ||
| return adj_in, out_degree, n | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is it for?