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
16 changes: 16 additions & 0 deletions python/RUFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Install ruff to check your code style :

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is it for?

```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
```
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
```
21 changes: 21 additions & 0 deletions python/algorithms/bfs_classic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from collections import deque

INF = int(1e9)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
23 changes: 23 additions & 0 deletions python/algorithms/bfs_spla.py
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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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()
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
121 changes: 121 additions & 0 deletions python/algorithms/graph_classic.py
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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
Loading
Loading