Skip to main content

petls-pytorch

PyPI CI Python License

Persistent topological Laplacians in PyTorch for weighted Alpha, Rips, directed flag, and cellular-sheaf complexes.

Status: beta

PETLS-PyTorch brings persistent-Laplacian calculations into ordinary Python and PyTorch workflows while retaining the geometric and topological information needed to interpret the results. Its main capabilities include:

  • weighted and unweighted Gudhi Alpha complexes with general power weights;
  • Gudhi-backed Betti numbers and persistence intervals alongside persistent-Laplacian spectra;
  • stable simplex identities and simplex-mapped harmonic representatives;
  • explicit CPU, CUDA, dtype, and numerical-tolerance controls;
  • guarded dense calculations and sparse lowest-spectrum analysis for larger ordinary Hodge Laplacians; and
  • Rips, directed flag, and cellular-sheaf complex support.

The original C++/pybind11 PETLS implementation remains a numerical reference for shared persistent-Laplacian algorithms. The Python API itself is focused on PyTorch conventions and does not reproduce compatibility-only C++ names.

Contents

Installation

pip install petls-pytorch

PETLS-PyTorch supports CPython 3.10 through 3.14. For GPU acceleration, install a CUDA-enabled PyTorch build that matches your system using the official PyTorch installer.

The runtime dependencies are torch, numpy, scipy, gudhi, pandas, and matplotlib. Benchmark datasets use the optional benchmark extra, which adds tadasets without expanding the runtime install closure.

Quick Start

import petls_pytorch
import gudhi
import torch

# Weighted Alpha complex. Weights are general Gudhi power weights.
alpha = petls_pytorch.Alpha(
    points=[[0, 0], [1, 0], [1, 1], [0, 1]],
    weights=[0.20, 0.18, 0.20, 0.18],
    max_dim=2,
    max_alpha_square=1.0,
    device="cpu",
    dtype=torch.float64,
)

# Gudhi homology is authoritative; PETLS adds persistent-Laplacian spectra.
summary = alpha.topology_summary(dimensions=(0, 1), a=0.0, b=0.0)
intervals = alpha.persistence_intervals(dim=1)

# Advanced callers can inspect spectra and harmonic representatives directly.
eigenvalues = alpha.spectra(dim=1, a=0.0, b=0.0)
features = alpha.harmonic_features(dim=1, a=0.0, b=0.0)

# Rips complex from distance matrix
rips = petls_pytorch.Rips(distances=[[0, 1, 1], [1, 0, 1], [1, 1, 0]], max_dim=2)

# Directed flag complex from .flag file
dflag = petls_pytorch.dFlag("graph.flag", max_dim=3)

# Sheaf Laplacian
st = gudhi.SimplexTree()
st.insert([0], filtration=0.0)
st.insert([1], filtration=0.0)
st.insert([0, 1], filtration=1.0)
extra_data = {(0,): 1.0, (1,): 1.0, (0, 1): 1.0}


def restriction(simplex, coface, sst):
    return 1.0


sst = petls_pytorch.sheaf_simplex_tree(st, extra_data, restriction)
psl = petls_pytorch.PersistentSheafLaplacian(sst)
filtrations = psl.get_all_filtrations()
sheaf_eigenvalues = psl.spectra(dim=0, a=filtrations[0], b=filtrations[-1])

For any complex, choose a <= b from get_all_filtrations(). spectra(dim, a, b) computes the persistent-Laplacian spectrum; at a == b it is the ordinary Hodge spectrum.

Alpha Complexes and Power Weights

Alpha accepts Gudhi power weights directly. A weight is a finite scalar associated with a point; PETLS does not assign domain-specific meaning to it. Callers are responsible for deriving weights appropriate to their application.

alpha = petls_pytorch.Alpha(
    points=positions,
    weights=power_weights,
    max_dim=3,
    precision="safe",
    max_alpha_square=maximum_scale,
    point_labels=labels,  # optional; kept separate from topology
    device="cpu",
    dtype=torch.float64,
    zero_atol=1e-8,
    zero_rtol=1e-7,
)

The constructor validates point and weight shapes and finite values. Weighted vertex births, including negative filtration values, are retained. The max_alpha_square cutoff is passed to Gudhi during construction so simplices beyond the analysis range are never built. Passing no weights retains ordinary unweighted-alpha behavior.

Filtration enumeration includes real vertex births and merges numerical near-duplicates by default:

scales = alpha.get_all_filtrations(
    merge_tolerance=1e-10,
    include_vertex_filtrations=True,
)

Use include_vertex_filtrations=False only when reproducing workflows that intentionally ignore zero-dimensional birth scales.

Topology, Persistence, and Localization

Complexes constructed from Gudhi retain simplex_tree, simplices_by_dimension, simplex_filtrations, and simplex_to_index. Consequently, every Laplacian row and eigenvector coordinate has a stable simplex identity. If point_labels are supplied to Alpha, corresponding label tuples are available in simplex_labels_by_dimension and harmonic feature results.

summary = alpha.topology_summary(dimensions=(0, 1, 2), a=0.0, b=0.0)
intervals = alpha.persistence_intervals(dim=1)
bettis = alpha.betti_numbers_at(scale=0.0)
rank = alpha.persistent_betti(dim=1, birth_scale=0.0, death_scale=0.5)
features = alpha.harmonic_features(dim=1, a=0.0, b=0.0)

At a == b, topology_summary() reports ordinary Betti numbers. At a < b, it reports persistent Betti numbers. Gudhi persistence is the authoritative homology source when available; persistent-Laplacian nullity, least nonzero eigenvalues, the effective zero tolerance, smallest eigenvalues, matrix rows, cost estimates, and per-dimension calculation status are returned separately for numerical auditing. Requested dimensions above the complex's top_dim return Betti number zero and an empty spectrum.

harmonic_features() returns each numerical zero-mode eigenvector as simplex/coefficient records. Harmonic bases need not be unique, but their coordinates always follow the retained simplex order. For oversized ordinary calculations (a == b), the sparse path caps automatic localization at ten representatives and reports truncated_for_scale; pass max_features= to request a different explicit limit. Persistent localization (a < b) requires the dense Schur-complement Laplacian and raises LaplacianSizeError when either its output or peak intermediate allocation exceeds the configured guard.

Precision, Devices, and Scaling

Device and dtype are object-specific. The library default device is CPU; device="auto" opts in to CUDA when available, and device="cuda" requests it explicitly. Constructing one object never changes another object's placement. Alpha defaults to torch.float64; callers can select float32 or float64 explicitly on all primary complex variants.

Zero eigenvalues use the scale-aware test abs(lambda) <= zero_atol + zero_rtol * max(abs(spectrum)). Summary results report the effective tolerance and smallest eigenvalues so borderline modes can be inspected.

Dense allocations are guarded before construction. Estimates report the final matrix at filtration a and the larger Schur-complement intermediate that may be required at filtration b. The defaults are 12,000 rows and 4 GB for the largest dense matrix in the calculation, and both are configurable per object:

estimate = alpha.estimate_laplacian(dim=1, a=0.0, b=0.0)

guarded = petls_pytorch.Alpha(
    points=positions,
    weights=power_weights,
    max_matrix_rows=12_000,
    max_matrix_bytes=4_000_000_000,
    on_oversize="homology_only",  # or "raise"
)

ordinary_spectrum(dim, scale, num_eigenvalues) builds sparse boundary Gram matrices and calls SciPy's sparse symmetric eigensolver without first creating a dense Laplacian. The same path is used by spectra() for a == b after set_eigs_algorithm("sparse", ..., eigenvalue_order="SM"); SM, SA, LM, LA, and BE selection is honored consistently.

For an ordinary lowest-spectrum summary backed by a Gudhi simplex tree, PETLS uses the authoritative Betti number to size a block eigensolve. This is important when the zero eigenvalue is highly degenerate: scalar ARPACK can return converged positive eigenpairs without recovering every independent zero mode. PETLS uses block LOBPCG for feasible repeated kernels, checks normalized residuals and eigenvector orthogonality, and reports a spectral gap only when the numerical nullity agrees with Gudhi homology. The summary exposes spectrum_solver, spectrum_certified, and spectrum_max_normalized_residual for auditing. If recovery is incomplete or cannot be certified, least_nonzero_eigenvalue is None and calculation_status is spectral_nullity_mismatch, sparse_spectrum_unverified, or sparse_null_modes_only as appropriate.

Persistent Schur complements can become dense, so oversized a < b requests either raise LaplacianSizeError or return Gudhi homology-only status through topology_summary(). This distinction is intentional: large systems retain authoritative homology and ordinary sparse Hodge spectra without pretending that full persistent spectra are sparse.

Relationship to PETLS

petls-pytorch is an independent PyTorch-native implementation of the PETLS methods, with attribution to the original PETLS paper and project. Correctness tests compare numerical results against the original implementation on shared inputs. The supported interface follows Python and PyTorch conventions instead of carrying duplicate aliases for historical C++ and pybind11 names.

The full test suite includes reference/parity tests against original PETLS fixtures and variants, with default comparison tolerances of atol=1e-4 and rtol=1e-3 unless a test specifies a stricter tolerance.

This project is based on the public PETLS API, PETLS documentation, and the algorithms described in the PETLS paper. No source code from the original PETLS implementation is included in this repository.

Supported API

The package intentionally exposes one supported path for each operation:

Area API
Complexes Complex, Alpha, Rips, dFlag, PersistentSheafLaplacian
Persistent Laplacians get_L(), get_up(), get_down(), spectra(), eigenpairs()
Topology persistence_intervals(), betti_numbers_at(), persistent_betti(), topology_summary()
Localization harmonic_features(), simplex mappings, optional point labels
Scaling estimate_laplacian(), allocation guards, ordinary_spectrum()
Analysis and output Profile, Timer, summaries(), plot_summary(), storage helpers

Solver selection uses set_eigs_algorithm("eigvalsh") for complete dense spectra or set_eigs_algorithm("sparse", num_eigenvalues=...) for a partial ordinary spectrum. Device and dtype are supplied to constructors; there is no mutable package-wide configuration.

Directed Flag Files

dFlag reads weighted directed graphs from .flag files with a dim 0 vertex-weight section and an optional dim 1 directed-edge section:

dim 0
0.0 0.0 0.0 0.0
dim 1
0 1 1.25
1 2 2.50
0 2 3.00

The dim 0 line after the header contains one whitespace-separated vertex weight per vertex. Each dim 1 row is source target weight, using zero-based vertex indices. Missing directed edges are absent from the file; self-loops are not supported. Filtration values for higher-dimensional directed simplices are the maximum edge weight in each simplex.

Benchmark Notes

The benchmark runner compares petls-pytorch and the original PETLS package on identical synthetic inputs. It streams progress for every trial and writes all CSV, JSON, and plot outputs under benchmark-results/ by default.

The standard preset is the main comparison workload. It exercises Alpha complexes on torus, sphere, swiss roll, and Klein bottle point clouds, plus a bounded Rips-complex case. It samples dimensions 0, 1, and 2 and completes all standard rows by default, including empty Laplacians and the largest sampled Alpha matrices. Use --max_matrix_rows only for custom capped runs.

Final benchmark on our Windows 11 Pro machine:

  • CPU: Intel Core i7-13700K, 16 cores / 24 logical processors
  • GPU: NVIDIA GeForce RTX 4070 Ti, 12GB
  • PyTorch: 2.10.0+cu130
  • Original PETLS: petls==1.0.1, native Windows C++ build
Package Device Completed Skipped Trial Time Mean Trial Mean Eigs Complex Builds Max Completed Matrix
petls CPU 78 0 8.05 s 103.2 ms 97.7 ms 0.52 s 2399 x 2399
petls-pytorch CPU 78 0 2.20 s 28.2 ms 24.2 ms 0.57 s 2399 x 2399
petls-pytorch CUDA 78 0 1.05 s 13.4 ms 9.7 ms 0.66 s 2399 x 2399

On this workload, petls-pytorch CPU is 3.65x faster by trial time and 4.03x faster on eigensolves than native PETLS. On the RTX 4070 Ti, petls-pytorch CUDA is 7.70x faster by trial time and 10.04x faster on eigensolves. A few tiny rows are still slower row-by-row because fixed overhead dominates, but the standard CPU and CUDA aggregate comparisons are both clear wins with no skipped benchmark rows.

Running Benchmarks

From a source checkout, run the benchmark module with uv. The benchmark harness needs the benchmark extra, which provides tadasets; add --with petls when benchmarking against the original PETLS package.

# Representative CPU/GPU comparison
uv run --extra benchmark python -m benchmark --preset standard --package petls-pytorch --algorithm eigvalsh --device cpu
uv run --extra benchmark python -m benchmark --preset standard --package petls-pytorch --algorithm eigvalsh --device cuda --dtype float32

# Reference PETLS, if installed for your platform
uv run --extra benchmark --with petls python -m benchmark --preset standard --package petls --algorithm selfadjoint

# Larger GPU stress run
uv run --extra benchmark python -m benchmark --preset stress --package petls-pytorch --algorithm eigvalsh --device cuda

# Custom single run
uv run --extra benchmark python -m benchmark \
    --dataset torus --n_points 2000 --complex alpha --max_dim 3 \
    --package petls-pytorch --algorithm eigvalsh --device cuda --dtype float32 \
    --max_matrix_rows 12000

Benchmark device and dtype are passed directly to each PETLS-PyTorch object and recorded in CSV output. The benchmark CLI defaults to float32 to preserve the historical comparison workload; pass --dtype float64 to benchmark the higher-precision weighted-Alpha default used by the scientific API. Original PETLS does not expose the PyTorch dtype selection, so its result rows and dataset metadata report dtype="native".

By default, benchmark files are written under benchmark-results/results. Use --output_dir benchmark-results/<run-name> to keep named runs together.

Verify CUDA before benchmarking:

nvidia-smi
python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available(), torch.cuda.get_device_name(0) if torch.cuda.is_available() else None)"

If you are not using uv, install from the source checkout first:

python -m pip install -e ".[benchmark]"
python -m pip install petls  # only needed for --package petls
python -m benchmark --preset standard --package petls-pytorch --algorithm eigvalsh --device cpu

Test Suite

From a source checkout, run the default test suite with development dependencies:

uv run --extra dev pytest tests/ -v

Run the same release-quality checks used by CI from the active supported Python environment:

uv run --extra dev ruff format --check .
uv run --extra dev ruff check .
uv run --extra dev mypy src/petls_pytorch benchmark

By default, tests that compare against the original PETLS package are skipped when petls is not installed. To run the full parity suite against the reference implementation, use uv run --with petls to add PETLS to the temporary run environment:

uv run --extra dev --with petls pytest tests/ -v

On Windows, PETLS get_down() reference calls can trigger access violations in the original native package for the Rips, Alpha, and dFlag comparison cases. To avoid those platform-specific crashes while still running the rest of the suite, exclude those reference checks:

uv run --extra dev --with petls pytest tests/ -v -k "not test_get_down_eigenvalues_match_reference and not test_get_down_eigenvalues_match_mwe"

If you are not using uv, install the package and test dependencies first:

python -m pip install -e ".[dev]"
python -m pip install petls  # only needed for the full parity suite
pytest tests/ -v

The full parity suite covers core functionality, Rips complexes, alpha complexes, directed flag complexes, sheaf support, eigenvalue utilities, and I/O helpers.

Contributing

Issues and pull requests are welcome at https://github.com/Sylverity/petls-pytorch/issues. Contributions that expand coverage against the reference PETLS implementation are especially helpful.

License

This project is licensed under the Apache License 2.0 — see LICENSE.

petls_pytorch is an independent, clean-room implementation and contains no source code from the original PETLS project. It depends on third-party packages with their own licenses, including gudhi (MIT, with GPL-licensed dependencies such as CGAL used by some modules, e.g. alpha complexes). These are installed separately and are not redistributed as part of this project.

Citation

If you use petls-pytorch in research, please cite both this PyTorch implementation and the original PETLS paper.

petls-pytorch

@software{marston2026petlspytorch,
  title        = {petls-pytorch: A PyTorch-native implementation of persistent topological Laplacians},
  author       = {Marston, Sumner K.},
  year         = {2026},
  publisher    = {Sylverity Research},
  url          = {https://github.com/Sylverity/petls-pytorch}
}

Original PETLS paper

@misc{jones2025petlspersistenttopologicallaplacian,
    title={PETLS: PErsistent Topological Laplacian Software},
    author={Benjamin Jones and Guo-Wei Wei},
    year={2025},
    eprint={2508.11560},
    archivePrefix={arXiv},
    primaryClass={math.AT},
    url={https://arxiv.org/abs/2508.11560},
}

This project is an independent PyTorch-native implementation based on the public PETLS API, PETLS documentation, and the PETLS paper. No source code from the original PETLS implementation is included in this repository.

Metadata

Release files for petls-pytorch 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for petls-pytorch 1.1.1
File Size Uploaded
petls_pytorch-1.1.1.tar.gz 52.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for petls-pytorch 1.1.1
File Interpreter ABI Platform
petls_pytorch-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 103.7 kB

Release files / petls_pytorch-1.1.1.tar.gz

Download URL petls_pytorch-1.1.1.tar.gz
Size 52.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cfbffe4119afd00b3d8c1186cc31adfa507f38c41edac07ca4c229dab245ca2a
BLAKE2b-256 checksum
How to use checksums
dcc1708a2cdfeea23783b44d37d2f282beccdebce53877f2f7c6c837e540c4e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.

Transparency log

Release files / petls_pytorch-1.1.1-py3-none-any.whl

Download URL petls_pytorch-1.1.1-py3-none-any.whl
Size 51.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d990d10fb2086ca2e40fd3e6ebfffc6b0aeed92a6900248693156915f128363d
BLAKE2b-256 checksum
How to use checksums
45555027619ee62e29b3ca7234e9fa4b02a30da564d519ba5c920166c7433440
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.

Transparency log

Release history Release notifications | RSS feed

1.2.0

2 release files

1.1.2

2 release files

This release

1.1.1 This release

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page