Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ffabf62
Add algorithms
cat923 Jun 12, 2026
102eeff
clean result files and fix SPLA algorithms
cat923 Jul 14, 2026
0886e3a
Add README.md
cat923 Jul 15, 2026
2ac847a
Fix README.md
cat923 Jul 15, 2026
b1f7bd6
Add docstrings for spla algorithms and format files using ruff
cat923 Jul 28, 2026
5687bdd
Add ruff linter and formatter
cat923 Jul 28, 2026
e289c3f
Fix algorithms
cat923 Aug 18, 2026
3c4519b
Add scalar and rename pr to pr_spla
cat923 Aug 18, 2026
f72c245
Add SSSP convergence
cat923 Aug 20, 2026
6ff8b32
remove unused idx variable
cat923 Aug 20, 2026
8f461db
fix sssp_spla
cat923 Sep 6, 2026
61996d0
sssp_spla: add transpose
cat923 Sep 17, 2026
bb71639
delete using classic algorithms
cat923 Sep 17, 2026
5671372
delete reading classic algorithms
cat923 Sep 17, 2026
dacac5b
fix graph.txt
cat923 Sep 17, 2026
493fbcc
fix tests
cat923 Sep 17, 2026
99228d0
rename algorithms functions
cat923 Sep 17, 2026
bc3acfe
add set_fill_value to Vector and SSSP
cat923 Sep 21, 2026
3bf140c
Add read weights
cat923 Sep 30, 2026
971be16
improved pr and sssp
cat923 Sep 30, 2026
f4a59c3
Add sort
cat923 Sep 30, 2026
58e686c
use upper triangle
cat923 Sep 30, 2026
e8db2d0
Add edge weights and disable automatic symmetrization in MtxLoader
cat923 Sep 30, 2026
bcd01f4
Add fill_with for pr and set_fill_value for sssp
cat923 Sep 30, 2026
8e94ad7
Simplify Vector.set_fill_value to accept Scalar only
cat923 Sep 30, 2026
af7e938
Update tests
cat923 Sep 30, 2026
db8c49b
Delete RUFF.md
cat923 Sep 30, 2026
edfea2b
apply clang-format fixes
cat923 Oct 2, 2026
63ed9a3
Delete stray endl in sssp example
cat923 Oct 4, 2026
5c6e93d
rename graph constants in tests
cat923 Oct 5, 2026
5eb0685
add argparse descriptions
cat923 Oct 5, 2026
8189a6e
Update README
cat923 Oct 8, 2026
d577d5d
Add descriptor
cat923 Oct 10, 2026
0ae0662
Update bfs_spla
cat923 Oct 10, 2026
6dde980
Fix tests and main for bfs_spla
cat923 Oct 10, 2026
81987a4
New plots
cat923 Oct 10, 2026
4f17d94
Fix crash in cl_v_count_mf on OpenCL
cat923 Oct 10, 2026
2fb0799
Fix clang format in cl_v_count_mf.hpp
cat923 Oct 10, 2026
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ add_library(spla SHARED
src/binding/c_scalar.cpp
src/binding/c_type.cpp
src/binding/c_vector.cpp
src/binding/c_descriptor.cpp
# C++ optional part
${SRC_OPENCL})

Expand Down
14 changes: 6 additions & 8 deletions examples/sssp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@

#include "common.hpp"
#include "options.hpp"

#include <spla.hpp>


int main(int argc, const char* const* argv) {
std::shared_ptr<cxxopts::Options> options = make_options("sssp", "sssp (single-source shortest path) algorithm with spla library");
cxxopts::ParseResult args;
Expand Down Expand Up @@ -73,12 +71,12 @@ int main(int argc, const char* const* argv) {
desc->set_traversal_mode(static_cast<spla::Descriptor::TraversalMode>(args[OPT_PUSH_PULL].as<int>() - 1));
desc->set_front_factor(args[OPT_FRONT_FACTOR].as<float>());

const spla::uint w = 1.0f;
const auto& Ai = loader.get_Ai();
const auto& Aj = loader.get_Aj();
const auto& Ai = loader.get_Ai();
const auto& Aj = loader.get_Aj();
const auto& Aw = loader.get_Aw();

for (std::size_t k = 0; k < loader.get_n_values(); ++k) {
A->set_float(Ai[k], Aj[k], w);
A->set_float(Ai[k], Aj[k], Aw[k]);

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 these changes needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

To use the actual edge weights loaded from the mtx file instead of the constant weight w = 1.

}

const int n_iters = args[OPT_NITERS].as<int>();
Expand Down Expand Up @@ -114,7 +112,7 @@ int main(int argc, const char* const* argv) {

for (std::size_t k = 0; k < loader.get_n_values(); ++k) {
ref_Ai[Ai[k]].push_back(Aj[k]);
ref_Ax[Ai[k]].push_back(w);
ref_Ax[Ai[k]].push_back(Aw[k]);
}

timer_ref.lap_begin();
Expand All @@ -141,4 +139,4 @@ int main(int argc, const char* const* argv) {
std::cout << std::endl;

return 0;
}
}
4 changes: 2 additions & 2 deletions examples/tc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ int main(int argc, const char* const* argv) {
const auto& Aj = loader.get_Aj();

for (std::size_t k = 0; k < loader.get_n_values(); ++k) {
if (Ai[k] > Aj[k]) {
if (Ai[k] < Aj[k]) {

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 these changes needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

There is no difference in the results, but I like upper triangles more.

A->set_int(Ai[k], Aj[k], 1);
}
}
Expand Down Expand Up @@ -118,7 +118,7 @@ int main(int argc, const char* const* argv) {

// for tc it is important to have no duplicates
for (std::size_t k = 0; k < loader.get_n_values(); ++k) {
if (Ai[k] > Aj[k]) {
if (Ai[k] < Aj[k]) {
ref_A_set[Ai[k]].insert(Aj[k]);
}
}
Expand Down
8 changes: 8 additions & 0 deletions include/spla.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ SPLA_API spla_Status spla_Vector_get_float(spla_Vector v, spla_uint row_id, floa
SPLA_API spla_Status spla_Vector_build(spla_Vector v, spla_MemView keys, spla_MemView values);
SPLA_API spla_Status spla_Vector_read(spla_Vector v, spla_MemView* keys, spla_MemView* values);
SPLA_API spla_Status spla_Vector_clear(spla_Vector v);
SPLA_API spla_Status spla_Vector_fill_with(spla_Vector v, spla_Scalar value);

//////////////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -389,6 +390,13 @@ SPLA_API spla_Status spla_Exec_v_count_mf(spla_Scalar r, spla_Vector v, spla_Des

//////////////////////////////////////////////////////////////////////////////////////

/* Descriptor object methods */

SPLA_API spla_Status spla_Descriptor_make(spla_Descriptor* descriptor);
SPLA_API spla_Status spla_Descriptor_set_struct_only(spla_Descriptor descriptor, spla_bool value);
SPLA_API spla_Status spla_Descriptor_set_early_exit(spla_Descriptor descriptor, spla_bool value);

//////////////////////////////////////////////////////////////////////////////////////
#if defined(__cplusplus)
}
#endif
Expand Down
8 changes: 5 additions & 3 deletions include/spla/io.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ namespace spla {
*/
SPLA_API bool load(std::filesystem::path file_path,
bool offset_indices = true,
bool make_undirected = true,
bool make_undirected = false,
bool remove_loops = true);

/**
Expand All @@ -80,15 +80,17 @@ namespace spla {

[[nodiscard]] SPLA_API const std::vector<uint>& get_Ai() const;
[[nodiscard]] SPLA_API const std::vector<uint>& get_Aj() const;
[[nodiscard]] SPLA_API uint get_n_rows() const;
[[nodiscard]] SPLA_API uint get_n_cols() const;
[[nodiscard]] SPLA_API const std::vector<float>& get_Aw() const;

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 these changes needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

To use the actual edge weights loaded from the mtx file instead of the constant weight w = 1.

[[nodiscard]] SPLA_API uint get_n_rows() const;
[[nodiscard]] SPLA_API uint get_n_cols() const;
[[nodiscard]] SPLA_API std::size_t get_n_values() const;

private:
std::string m_name;
std::filesystem::path m_file_path;
std::vector<uint> m_Ai;
std::vector<uint> m_Aj;
std::vector<float> m_Aw;
bool m_base_is_zero = false;
uint m_n_rows = 0;
uint m_n_cols = 0;
Expand Down
111 changes: 111 additions & 0 deletions python/algorithms/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Graph Algorithms with SPLA

This project provides a Python implementation of graph algorithms using sparse
linear algebra primitives from SPLA on GPU

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` |

---

SSSP should be used with undirected graphs only

## Installation

```bash
cd python
python -m venv venv
source venv/bin/activate
pip install -e .
cd algorithms
```

---

## Usage
```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
```

---

## Input format

Two options:

- `-v graph.txt` — Vectors format
- `-m graph.mtx` — Matrix Market format (from https://sparse.tamu.edu/)


### graph.txt

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

def bfs_naive(start, graph, n):
depth = [None] * n
q = deque()
depth[start] = 0
q.append(start)

while q:
u = q.popleft()
for v in graph[u]:
if depth[v] is None:
depth[v] = depth[u] + 1
q.append(v)

return depth
41 changes: 41 additions & 0 deletions python/algorithms/bfs_spla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from pyspla import INT, Matrix, Scalar, Vector, Descriptor

def bfs_spla(s: int, A: Matrix,
push_only: bool = False,
pull_only: bool = False,
push_pull: bool = True,
front_factor: float = 0.05):
n = A.n_rows
if not (push_only or pull_only or push_pull):
push_only = True

v = Vector(n, INT)
front_prev = Vector(n, INT)
front_new = Vector(n, INT)
front_prev.set(s, 1)

depth = Scalar(INT, 0)
count = 0
front_size = 1

desc = Descriptor()
desc.set_early_exit(True)
desc.set_struct_only(True)

while front_size > 0:
depth += 1
count += front_size
v.assign(front_prev, depth, op_assign=INT.SECOND, op_select=INT.NQZERO)
front_density = front_size / n
is_push_better = front_density <= front_factor

if push_only or (push_pull and is_push_better):
front_prev.vxm(v, A,op_mult=INT.LAND, op_add=INT.LOR, op_select=INT.EQZERO, out=front_new, desc=desc)
else:
A.mxv(v, front_prev, op_mult=INT.LAND, op_add=INT.LOR, op_select=INT.EQZERO, out=front_new, desc=desc)

front_size = front_new.count_mf().get()

front_prev, front_new = front_new, front_prev

return v, count, depth.get()
3 changes: 3 additions & 0 deletions python/algorithms/graph.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0 1 2
1 2 0
5 3 2
56 changes: 56 additions & 0 deletions python/algorithms/graph_spla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from scipy.io import mmread
from pyspla import Matrix, INT, FLOAT


def sort_by_ij(I, J, V):
return map(list, zip(*sorted(zip(I, J, V))))


def read_spla(filepath, dtype=FLOAT):
coo = mmread(filepath).tocoo()
n = int(max(coo.shape))
rows = coo.row.astype(int).tolist()
cols = coo.col.astype(int).tolist()
vals = coo.data.astype(float).tolist()
if dtype == INT:
vals = [int(v) for v in vals]
rows, cols, vals = sort_by_ij(rows, cols, vals)
return Matrix.from_lists(rows, cols, vals, shape=(n, n), dtype=dtype)


def read_vectors_spla(filepath, dtype=FLOAT):
with open(filepath) as f:
I = list(map(int, f.readline().split()))
J = list(map(int, f.readline().split()))
V = list(map(float, f.readline().split()))
if dtype == INT:
V = [int(v) for v in V]
n = max(max(I), max(J)) + 1
I, J, V = sort_by_ij(I, J, V)
return Matrix.from_lists(I, J, V, shape=(n, n), dtype=dtype)


def read_mtx_pr_spla(file_path, alpha=0.85):
coo = mmread(file_path).tocoo()
n = int(max(coo.shape))
rows = coo.row.astype(int).tolist()
cols = coo.col.astype(int).tolist()
out_deg = [0] * n
for i in rows:
out_deg[i] += 1
weights = [alpha / out_deg[i] if out_deg[i] > 0 else 0.0 for i in rows]
rows, cols, weights = sort_by_ij(rows, cols, weights)
return Matrix.from_lists(rows, cols, weights, shape=(n, n), dtype=FLOAT)


def read_vectors_pr_spla(file_path, alpha=0.85):
with open(file_path) as f:
I = list(map(int, f.readline().split()))
J = list(map(int, f.readline().split()))
n = max(max(I), max(J)) + 1
out_deg = [0] * n
for i in I:
out_deg[i] += 1
V = [alpha / out_deg[i] if out_deg[i] > 0 else 0.0 for i in I]
I, J, V = sort_by_ij(I, J, V)
return Matrix.from_lists(I, J, V, shape=(n, n), dtype=FLOAT)
Loading