Skip to main content

Topological representations and pairwise comparison of embeddings — persistence + periscope.

Project description

persiscope

Topological representations and pairwise comparison of embeddings. The name is persistence + periscope.

persiscope turns embedded data into persistence-based summaries (persistence landscapes and silhouettes) and scores how topologically similar two embedding sets are. It was extracted from the pleats research pipeline into a standalone library. Everything runs in memory through a sklearn-style API; there are no config files, and nothing touches disk unless you ask.

Install

pip install persiscope                 # core
pip install "persiscope[viz]"          # + matplotlib / plotly plotting
pip install "persiscope[metrics]"      # + DTW and Fréchet curve metrics
pip install "persiscope[frame]"        # + pandas for .to_frame()
pip install "persiscope[all]"          # everything above
pip install "persiscope[reproduce]"    # everything above + jupyter, for the dissertation notebooks

Requires Python ≥ 3.10. Core dependencies: numpy, scipy, networkx, gudhi, tqdm.

Quickstart

compare is the main entry point. It takes a list of embedding arrays and returns an all-pairs comparison:

import numpy as np
import persiscope as ps

embeddings = [emb_gpt2, emb_bert, emb_t5]   # each (n_i, d)

result = ps.compare(
    embeddings,
    method="energy",        # "energy" or a curve metric ("euclidean", "js", ...)
    summary="landscape",    # "landscape" or "silhouette"
    homology_dim=0,
    theta=-3 * np.pi / 8,   # diagram rotation, the method's nonstandard knob (this is the default)
    n_bootstrap=100,
    run_pvalue=False,
    labels=["gpt2", "bert", "t5"],
    random_state=0,
)

result.matrix               # (3, 3) symmetric dissimilarity matrix
result.to_frame()           # labeled pandas DataFrame (needs [frame] extra)
result.representations      # the fitted Representations, reused across all pairs

The pipeline, step by step

compare() chains three stages that you can also drive directly.

1. Embeddings to topological representation

Build a distance graph, compute Vietoris–Rips persistence, and summarize it as landscapes and silhouettes. A subsampling bootstrap makes the result stable and provides confidence bands.

tf = ps.TopologicalTransformer(
    homology_dim=0,
    diagram_transform=ps.RotateScale(theta=-3 * np.pi / 8, alpha=np.sqrt(2) / 2),
    n_bootstrap=100,
    silhouette_power=3,
    tenting_resolution=1000,
    random_state=0,
)
rep = tf.fit_transform(emb_gpt2)             # or distance_matrix=..., or graph=...

rep.mean_landscape          # (resolution, 2) bootstrap-mean curve
rep.bootstrapped_landscapes # (n_bootstrap, resolution, 2)
rep.landscape_band          # {"lower_bound", "upper_bound", "half_bound"}
rep.persistence_diagram     # (n, 2), includes the essential class

2. Pairwise scoring

Compare two representations, with an optional permutation p-value.

scorer = ps.Scorer(method="energy", summary="landscape", run_pvalue=True, n_permutations=100)
res = scorer.score(rep_a, rep_b)             # ScoreResult(score, pvalue, perm_scores)

matrix = ps.score_matrix([rep_a, rep_b, rep_c], method="energy")

3. Visualization (optional [viz] extra)

Each helper returns a figure object; you decide whether to show or save it.

The main figure is the baseline report. It compares every model to a reference: overlaid mean landscapes and silhouettes with 95% confidence bands, the energy statistic against the baseline for each summary type with permutation-significance stars, and Wasserstein/JS distance strips annotated the same way.

fig = ps.viz.plot_baseline_report(
    result.representations,
    baseline=0,                          # index of the reference model
    curve_metrics=("wasserstein", "js"),
    n_permutations=200,
)

Individual plotters cover the pieces:

ps.viz.plot_persistence_diagram(rep)         # matplotlib Figure
ps.viz.plot_landscape(rep)                   # matplotlib Figure, with band shading
ps.viz.plot_score_heatmap(result)            # plotly Figure
ps.viz.plot_comparison_report(result.representations)   # all-pairs report figure

Diagram transforms

The rotation applied to a persistence diagram before summaries are built is the nonstandard part of the method, so it is exposed directly alongside homology_dim:

rep = ps.TopologicalTransformer(homology_dim=0, theta=-3 * np.pi / 8).fit_transform(emb)
res = ps.compare(embeddings, theta=-np.pi / 4)   # the conventional rotation, for contrast

If you need more than an angle, pass a full transform object instead: ps.RotateScale, ps.H0Rotate, ps.Identity, or your own implementation of the small DiagramTransform protocol. The rest of the pipeline does not care which one you use.

from persiscope import transforms

tf = ps.TopologicalTransformer(diagram_transform=transforms.H0Rotate(angle=3 * np.pi / 8))

Scoring methods

method family compares
energy distribution the two bootstrap sets of curves (energy distance)
euclidean, cosine, spectral, chi_squared, kl, js, wasserstein curve the two mean curves
dtw, frechet curve the two mean curves (needs [metrics] extra)

Larger scores mean more different. The permutation test is one-sided.

Development

pip install -e ".[viz,metrics,frame,dev]"
pytest

During extraction, persiscope's landscapes and silhouettes were verified to match the original pleats implementation to numerical tolerance.

Reference

The methodology implemented here (persistence landscapes and silhouettes over embedding graphs, the rotated-diagram construction, and pairwise scoring with permutation tests) is developed in:

Ashoff, T. (2026). Persistent Convolution: A Topological Approach to Formal AI Alignment Testing (PhD dissertation, University of Virginia). DOI: 10.18130/8k9j-9k42

If you use persiscope in academic work, please cite the dissertation (see CITATION.cff).

Reproducing the dissertation results

Notebooks reproducing the dissertation's results live in examples/reproduction/ in this repository. Extras install dependencies, not files, so clone the repository to get the notebooks:

git clone https://github.com/tylerashoff/persiscope.git
cd persiscope
pip install -e ".[reproduce]"
jupyter notebook examples/reproduction/

Reproduced figures are statistically identical to the published ones but not pixel-identical: the bootstrap uses different random draws than the original pipeline.

License

MIT, see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

persiscope-0.1.0b3.tar.gz (34.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

persiscope-0.1.0b3-py3-none-any.whl (35.8 kB view details)

Uploaded Python 3

File details

Details for the file persiscope-0.1.0b3.tar.gz.

File metadata

  • Download URL: persiscope-0.1.0b3.tar.gz
  • Upload date:
  • Size: 34.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for persiscope-0.1.0b3.tar.gz
Algorithm Hash digest
SHA256 1e9e40e30a4b0f55bc0b483d06e4050732f6832d5a157521d1c2aa103832adcb
MD5 7d3b50375a119628d0163b82436d4683
BLAKE2b-256 93edb785f86c142e1c59fe6bfbe73f98a7ca86dc4b8081bb40ddedd2ebb756a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for persiscope-0.1.0b3.tar.gz:

Publisher: publish.yml on tylerashoff/persiscope

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file persiscope-0.1.0b3-py3-none-any.whl.

File metadata

  • Download URL: persiscope-0.1.0b3-py3-none-any.whl
  • Upload date:
  • Size: 35.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for persiscope-0.1.0b3-py3-none-any.whl
Algorithm Hash digest
SHA256 eec5956fa9b169b8ffe7ce045658c8c991e288963b3d6476d3a82672d56118d5
MD5 dde5a38346da43e2b68327b291d295e1
BLAKE2b-256 c1338c40bfbba12f2e4344e9decc56f623756dd90fceadc52cc22ef07eaf9adb

See more details on using hashes here.

Provenance

The following attestation bundles were made for persiscope-0.1.0b3-py3-none-any.whl:

Publisher: publish.yml on tylerashoff/persiscope

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page