Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions python/algorithms/README.md
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
```
20 changes: 20 additions & 0 deletions python/algorithms/bfs_classic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from collections import deque

INF = int(1e9)

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
17 changes: 17 additions & 0 deletions python/algorithms/bfs_spla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pyspla import *

def bfs(s: int, A: Matrix):
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)
front_size = front.reduce(op_reduce=INT.PLUS).get()

return v, count, depth.get()
4 changes: 4 additions & 0 deletions python/algorithms/graph.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
3
0 1 2
1 2 0
5 3 2
115 changes: 115 additions & 0 deletions python/algorithms/graph_classic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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):
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
Loading