Metadata-Version: 2.5
Name: flodr
Version: 0.2.0
Summary: FloDR: exactly invertible dimensionality reduction built on a normalizing flow
Author-email: Daniel Probst <daenuprobst@gmail.com>
License: MIT License
        
        Copyright (c) 2026 Daniel Probst
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: matplotlib>=3.7
Requires-Dist: numpy>=1.26
Requires-Dist: scikit-learn>=1.6
Requires-Dist: scipy>=1.11
Requires-Dist: threadpoolctl>=3.1
Requires-Dist: torch>=2.1
Requires-Dist: tqdm>=4.66
Requires-Dist: typing-extensions>=4.15
Requires-Dist: usearch>=2.25.3
Provides-Extra: dev
Requires-Dist: black>=24.0; extra == 'dev'
Requires-Dist: pandas>=2.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: notebook
Requires-Dist: ipykernel>=6.29; extra == 'notebook'
Requires-Dist: nbconvert>=7.0; extra == 'notebook'
Requires-Dist: nbformat>=5.9; extra == 'notebook'
Provides-Extra: paper
Requires-Dist: aiohttp>=3.9; extra == 'paper'
Requires-Dist: anndata>=0.10; extra == 'paper'
Requires-Dist: drfp>=0.3; extra == 'paper'
Requires-Dist: faiss-cpu>=1.8; extra == 'paper'
Requires-Dist: fsspec>=2024.2; extra == 'paper'
Requires-Dist: h5py>=3.10; extra == 'paper'
Requires-Dist: opentsne>=1.0; extra == 'paper'
Requires-Dist: pacmap>=0.7; extra == 'paper'
Requires-Dist: pandas>=2.0; extra == 'paper'
Requires-Dist: phate>=1.0; extra == 'paper'
Requires-Dist: pillow>=10.0; extra == 'paper'
Requires-Dist: pymde>=0.1; extra == 'paper'
Requires-Dist: scanpy>=1.10; extra == 'paper'
Requires-Dist: trimap>=1.1; extra == 'paper'
Requires-Dist: umap-learn>=0.5; extra == 'paper'
Requires-Dist: umato>=1.0; extra == 'paper'
Description-Content-Type: text/markdown

# FloDR

**[Documentation](https://daenuprobst.github.io/flodr/)**

Dimensionality reduction built on an invertible normalising flow. The first `n_components` coordinates of the flow
are the map and the rest are a residual, so the fitted map has an exact inverse and an exact density. FloDR follows
the scikit-learn estimator interface, like UMAP.

![](assets/flow_morph_bmarrow_white.gif)

## Installation

Python 3.11 or newer. Training uses the GPU when PyTorch sees one.

```bash
pip install flodr
```

## Usage

```python
import matplotlib.pyplot as plt
from flodr import FloDR, audit, viz

model = FloDR(random_state=0).fit(X)
Y = model.embedding_                         # the map

Y_new = model.transform(X_new)               # one pass through the flow
Y_opt = model.transform_opt(X_new)           # optimised against the training map, as UMAP's transform
X_back = model.inverse_transform(Y[:10])     # positions back to the input
Z = model.transform_latent(X_new)            # the map, then the residual
X_same = model.inverse_latent(Z)             # exact up to float precision

logp = model.score_samples(X_new)            # log density
cert = model.coverage_certificate(X_new)     # is the density a law of new points?

res = audit(Y, X=X, labels=labels)           # what the map hides about the labels

fig, ax = plt.subplots(1, 3, figsize=(12, 4))
viz.plot_embedding(Y, labels, ax=ax[0])
viz.plot_ranks([cert["fibre"]["ranks"], cert["map"]["ranks"]], ax=ax[1])
viz.plot_audit(res, axes=ax[2], show_labels=False)
```

| Parameter | Default | |
|---|---|---|
| `n_components` | `2` | dimension of the map |
| `w` | `2.0` | weight of the distance order: 0 keeps neighbourhoods only, larger keeps more global structure |
| `axes` | `"variance"` | start from the leading principal components, or `"heat"` from the heat geometry of the neighbour graph |
| `density` | `True` | fit the density, needed by `score_samples` and the certificates |
| `max_iter` | `None` | training steps, 6,400 by default |
| `random_state` | `0` | seed |
| `device` | `None` | the GPU when there is one |

`examples/quickstart.ipynb` runs these calls on Fashion-MNIST.

## Development

```bash
git clone https://github.com/daenuprobst/flodr
cd flodr
uv sync --extra dev
uv run pytest
```

The documentation is a Starlight site built from `docs/docs/*.md` and the signatures in `src/flodr`:
`cd docs && npm install && npm run dev`.

## Reproducing the paper

`scripts/` holds one script per figure and table of the paper, named after it (`fig2.py`, `ed_table1.py`,
`supp_fig1.py`, ...), and `scripts/common.py` with the data, the methods and the scores they share. Each script fits
what it needs, caches it under `scripts/cache`, so scripts that share fits reuse them, and writes its figure or
table to `scripts/out`.

```bash
uv sync --extra paper
uv pip install git+https://github.com/hyhuang00/ParamRepulsor.git
uv run python scripts/fig2.py
```

Raw data go into `data/` (or the folder named by `DATA`); the data availability statement of the paper lists the
sources, and each script names the files it reads. Three tools need interpreters of their own, named by
`PCUMAP_PYTHON` (Parametric UMAP: TensorFlow 2.15 with umap-learn 0.5.5), `DREAMS_PYTHON` (DREAMS: the authors'
openTSNE fork) and `SCVI_PYTHON` (scvi-tools, for the zebrafish latent); the population-genetics test needs `plink2`.
