Repository navigation
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
e289c3f
3c4519b
f72c245
6ff8b32
8f461db
61996d0
bb71639
5671372
dacac5b
493fbcc
99228d0
bc3acfe
3bf140c
971be16
f4a59c3
58e686c
e8db2d0
bcd01f4
8e94ad7
af7e938
db8c49b
edfea2b
63ed9a3
5c6e93d
5eb0685
8189a6e
d577d5d
0ae0662
6dde980
81987a4
4f17d94
2fb0799
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 |
|---|---|---|
|
|
@@ -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]) { | ||
|
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 these changes needed?
Author
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. There is no difference in the results, but I like upper triangles more. |
||
| A->set_int(Ai[k], Aj[k], 1); | ||
| } | ||
| } | ||
|
|
@@ -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]); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
|
||
| /** | ||
|
|
@@ -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; | ||
|
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 these changes needed?
Author
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. 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; | ||
|
|
||
| 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 | ||
| ``` |
| 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 |
| 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() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| 0 1 2 | ||
| 1 2 0 | ||
| 5 3 2 |
| 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) |
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.
Why these changes needed?
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.
To use the actual edge weights loaded from the mtx file instead of the constant weight w = 1.