gspx 0.1.2

Sparse graph signal processing and spectral graph wavelets in Rust
Documentation
# gspx

`gspx` is a Rust crate for sparse graph signal processing. It provides fast
implementations of graph convolution using rational kernel fitting and
Chebyshev polynomial approximation. Supports dynamic graphs for applications
with a changing network topology.

Designed around large sparse networks such as power systems, but the API works
with any sparse Laplacian.

<p align="center">
  <img src="assets/readme/usa-bandpass-source-a.png" alt="USA bandpass from source A" width="980">
</p>

<p align="center">
  <a href="https://crates.io/crates/gspx">crates.io</a> |
  <a href="https://docs.rs/gspx">docs.rs</a>
</p>

License: `GPL-3.0-only`
MSRV: Rust `1.85`

## Install

```toml
[dependencies]
gspx = "0.1"
```

## Quick Start

Load a Laplacian from a `.mat` file and filter:

```rust,no_run
use gspx::{StaticConvolver, impulse, load_laplacian};
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let laplacian = load_laplacian(Path::new("graph.mat"))?;
    let signal = impulse(&laplacian, 0, 1)?;

    let mut convolver = StaticConvolver::new(laplacian)?;
    let filtered = convolver.bandpass(signal.view(), &[0.25, 1.0, 4.0], 2)?;
    Ok(())
}
```

Other loaders: `load_signal` for `.mat` signal matrices, `load_ply_laplacian`
and `load_ply_xyz` for PLY meshes.

## Signals

Filtering APIs accept 1D signals `(n_vertices,)` or 2D matrices
`(n_vertices, n_signals)`. For SGMA the signal matrix is
`(n_vertices, n_times)` and the time vector length must match `n_times`.

## Examples

The repository includes runnable examples under `examples/`. Larger example
Laplacians and meshes live under `resources/library` and are not packaged into
the published crate.

```rust,no_run
use gspx::{Dataset, ExampleData, GraphKind, KernelPreset};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = ExampleData::from_dir("resources/library")?;
    let texas = data.graph(Dataset::Texas, GraphKind::Delay)?;
    let coords = data.coords(Dataset::Texas)?;
    let kernel = data.vf_kernel(KernelPreset::ModifiedMorlet)?;
    Ok(())
}
```

```bash
cargo run --example basic_static_filters
cargo run --example basic_dynamic_topology
cargo run --example basic_chebyshev
cargo run --example basic_vf_fit
cargo run --example basic_sgma
cargo run --example basic_resource
cargo run --example basic_local_files
```

## Performance

`perf_core` measures end-to-end filtering calls with Criterion. Each value is
the mean time in milliseconds for one deterministic real `N x 1` signal. Exact
filters use one scale (`[1.0]`) and lowpass/bandpass use order `1`.

### Static vs Dynamic Filtering

Real power-grid Laplacians from `resources/library`. No branch edits are
applied between dynamic calls, isolating filtering cost.

<table>
  <thead>
    <tr>
      <th rowspan="2">Graph</th>
      <th rowspan="2">N</th>
      <th colspan="3">Static (ms)</th>
      <th colspan="3">Dynamic (ms)</th>
    </tr>
    <tr>
      <th>Lowpass</th>
      <th>Bandpass</th>
      <th>Highpass</th>
      <th>Lowpass</th>
      <th>Bandpass</th>
      <th>Highpass</th>
    </tr>
  </thead>
  <tbody>
    <tr><td>Hawaii</td><td><code>37</code></td><td><code>0.00051</code></td><td><code>0.00101</code></td><td><code>0.00054</code></td><td><code>0.00051</code></td><td><code>0.00118</code></td><td><code>0.00053</code></td></tr>
    <tr><td>WECC</td><td><code>240</code></td><td><code>0.00179</code></td><td><code>0.00344</code></td><td><code>0.00169</code></td><td><code>0.00171</code></td><td><code>0.00359</code></td><td><code>0.00195</code></td></tr>
    <tr><td>Texas</td><td><code>2k</code></td><td><code>0.01704</code></td><td><code>0.03621</code></td><td><code>0.01754</code></td><td><code>0.01742</code></td><td><code>0.03490</code></td><td><code>0.01720</code></td></tr>
    <tr><td>East</td><td><code>16k</code></td><td><code>1.63247</code></td><td><code>3.66904</code></td><td><code>1.42601</code></td><td><code>1.26126</code></td><td><code>2.91676</code></td><td><code>0.96470</code></td></tr>
    <tr><td>EastWest</td><td><code>65k</code></td><td><code>2.09393</code></td><td><code>3.60965</code></td><td><code>1.53460</code></td><td><code>2.02737</code></td><td><code>3.35085</code></td><td><code>1.74977</code></td></tr>
    <tr><td>USA</td><td><code>82k</code></td><td><code>2.02103</code></td><td><code>4.04234</code></td><td><code>1.45063</code></td><td><code>1.62788</code></td><td><code>3.88886</code></td><td><code>1.52146</code></td></tr>
  </tbody>
</table>

Exact filtering on 65k–82k nodes stays in the low-single-digit millisecond
range. Structure matters as much as node count.

### Constructor and Lowpass

| Graph | N | Constructor (ms) | Lowpass (ms) |
|---|---:|---:|---:|
| `Texas` | `2k` | `0.00286` | `0.01713` |
| `East` | `16k` | `0.76098` | `1.65513` |
| `EastWest` | `65k` | `0.77505` | `2.02311` |
| `USA` | `82k` | `0.80452` | `2.00556` |

### Dynamic Updates on `USA`

| Operation | Time (ms) |
|---|---:|
| Warmed `add_branch_once` | `1.78406` |
| `bandpass_one` after `1` update | `5.22890` |
| `bandpass_one` after `10` updates | `7.25095` |
| `bandpass_one` after `50` updates | `19.39280` |

## Platform Notes

- Targets Linux, macOS, and Windows
- Sparse shifted solves use SuiteSparse-backed LDL factorization via `sprs-ldl`
- The published crate does not include repository example datasets

## Citation

If you use `gspx` for research, cite the associated HICSS paper.

```bibtex
@inproceedings{lowery-gspx-2026,
  title={Using Spectral Graph Wavelets to Analyze Large Power System Oscillation Modes},
  author={Lowery, Luke and Baek, Jongoh and Birchfield, Adam},
  year={2026}
}
```

The same citation metadata is also available in [`CITATION.cff`](./CITATION.cff).

## Links

- Python `sgwt`: [github.com/lukelowry/sgwt]https://github.com/lukelowry/sgwt
- Python docs: [sgwt.readthedocs.io]https://sgwt.readthedocs.io/
- Julia: [github.com/lukelowry/SpectralGraphWavelet.jl]https://github.com/lukelowry/SpectralGraphWavelet.jl
- SuiteSparse: [github.com/DrTimothyAldenDavis/SuiteSparse]https://github.com/DrTimothyAldenDavis/SuiteSparse
- Synthetic grid cases: [electricgrids.engr.tamu.edu]https://electricgrids.engr.tamu.edu/electric-grid-test-cases/
- Author: [lukelowry.github.io]https://lukelowry.github.io/
- Google Scholar: [scholar.google.com/citations]https://scholar.google.com/citations?user=CTynuRMAAAAJ&hl=en