Skip to content
Merged
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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,45 @@ env:
CARGO_TERM_COLOR: always

jobs:
lint:
name: Lint (fmt + clippy)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
lfs: true

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake libclang-dev clang

- name: Build and install SuiteSparse:GraphBLAS
run: |
git clone --depth 1 https://github.com/DrTimothyAldenDavis/GraphBLAS.git
cd GraphBLAS
make compact
sudo make install

- name: Build and install LAGraph
run: |
cd deps/LAGraph
make
sudo make install

- name: Install Rust toolchain (stable)
run: |
rustup update stable
rustup default stable
rustup component add rustfmt clippy

- name: cargo fmt
run: cargo fmt --all -- --check

- name: cargo clippy
run: cargo clippy --all-targets --all-features -- -D warnings

build_and_test:
name: Rust project - latest
runs-on: ubuntu-latest
Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release

on:
push:
tags:
- 'v*.*.*'

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
docker:
name: Build & push Docker image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
lfs: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
121 changes: 120 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,120 @@
# pathrex
# pathrex

[![CI](https://github.com/SparseLinearAlgebra/pathrex/actions/workflows/ci.yml/badge.svg)](https://github.com/SparseLinearAlgebra/pathrex/actions/workflows/ci.yml)
[![Container](https://img.shields.io/badge/ghcr.io-pathrex-blue?logo=docker)](https://github.com/SparseLinearAlgebra/pathrex/pkgs/container/pathrex)

**Pathrex** is a Rust library and CLI for evaluating and benchmarking
**Path Queries** over edge-labeled graphs.
## Features

- **Two RPQ evaluators** out of the box:
- `nfarpq` — runs `LAGraph_RegularPathQuery`.
- `rpqmatrix` — runs `LAGraph_RPQMatrix`.
- **Multiple input formats**: MatrixMarket directories, CSV edge lists, and
RDF (Turtle / N-Triples).
- **SPARQL frontend**: parses `SELECT` queries with a single triple/property-path
pattern.
- **Benchmarking** with [`criterion`](https://crates.io/crates/criterion):
per-query timing, JSON output, checkpoint/resume, optional HTML plots.
- **Reusable Rust library** with backend-agnostic `Graph<B>`, `GraphSource`,
`GraphBuilder`, and generic `Evaluator` traits.

## Quickstart with Docker

A pre-built image is published to GitHub Container Registry on every release tag.

```bash
docker pull ghcr.io/sparselinearalgebra/pathrex:latest
```

The image's entrypoint forwards arguments to the `pathrex` binary.

### Run a query

Mount a directory containing your graph and queries, then call `query`:

```bash
DATA=<path-to-dir-with-graph-and-queries>
docker run --rm \
-v "${DATA}:/data:ro" \
ghcr.io/sparselinearalgebra/pathrex:latest \
query \
--graph /data/my-graph \
--format mm \
--queries /data/queries.txt \
--algo nfarpq \
--algo rpqmatrix
```

### Run a benchmark

```bash
DATA=<path-to-dir-with-graph-and-queries>
RESULTS=<path-to-results-dir>
docker run --rm \
-v "${DATA}:/data:ro" \
-v "${RESULTS}:/results" \
ghcr.io/sparselinearalgebra/pathrex:latest \
bench \
--graph /data/my-graph \
--queries /data/queries.txt \
--algo nfarpq
```

The entrypoint defaults to:

- `--output /results/bench_results.json`
- `--checkpoint /results/bench_checkpoint.json`
- `--criterion-dir /results/criterion`

Pass any of those flags explicitly to override.

## CLI usage

```text
pathrex <SUBCOMMAND> [OPTIONS]

Subcommands:
query Run queries once and report result counts
bench Benchmark RPQ evaluators with criterion
```

### Common options

| Flag | Description |
|---|---|
| `-g`, `--graph <PATH>` | Path to the graph (directory for `mm`, file for `csv`/`rdf`). |
| `-f`, `--format <mm\|csv\|rdf>` | Input format. Defaults to `mm`. |
| `-q`, `--queries <FILE>` | Queries file (see format below). |
| `-a`, `--algo <nfarpq\|rpqmatrix>` | Algorithm(s). Repeat to run several. |
| `-b`, `--base-iri [<IRI>]` | Optional `BASE <iri>` to prepend to each query. Bare `--base-iri` uses `http://example.org/`. |

`query` adds `-o, --output <FILE>` to write JSON.

`bench` adds `--output`, `--checkpoint`, `--resume`, `--criterion-dir`,
`--plots`, `--sample-size`, `--warm-up`, `--measurement`. See
`pathrex bench --help` for details.

### Queries file format

One query per line:

```text
<id>,<sparql_pattern>
```

The pattern is wrapped into a full SPARQL query at load time:

- with `--base-iri <iri>`: `BASE <iri> SELECT * WHERE { <pattern> . }`
- without: `SELECT * WHERE { <pattern> . }`

Example `queries.txt`:

```text
q1,?x <knows>/<likes>* ?y
q2,?x (<a>|<b>)+ ?y
```

## License

See [`LICENSE`](LICENSE).
6 changes: 2 additions & 4 deletions src/cli/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ pub struct QueryMetadata {

impl QueryOutput {
pub fn write_to_file(&self, path: &Path) -> Result<(), std::io::Error> {
let json = serde_json::to_string_pretty(self)
.map_err(std::io::Error::other)?;
let json = serde_json::to_string_pretty(self).map_err(std::io::Error::other)?;
fs::write(path, json)
}
}
Expand Down Expand Up @@ -128,8 +127,7 @@ pub struct BenchMetadata {

impl BenchOutput {
pub fn write_to_file(&self, path: &Path) -> Result<(), std::io::Error> {
let json = serde_json::to_string_pretty(self)
.map_err(std::io::Error::other)?;
let json = serde_json::to_string_pretty(self).map_err(std::io::Error::other)?;
fs::write(path, json)
}
}
Expand Down
Loading